diff --git a/pkg/tools/execute/code.go b/pkg/tools/execute/code.go index 84b1e2d..a7cf912 100644 --- a/pkg/tools/execute/code.go +++ b/pkg/tools/execute/code.go @@ -187,6 +187,12 @@ func dispatchExecuteCode(ctx context.Context, e *Server, args json.RawMessage) ( return "", fmt.Errorf("execute_code: at least one step is required") } + if e.Strict { + if err := enforceStrict(stages); err != nil { + return "", err + } + } + label := buildExecLabel(stages) if !e.allowed("execute_code", label) { return "", fmt.Errorf("execute_code: denied by user") @@ -268,6 +274,22 @@ func buildExecLabel(stages []CodeStep) string { return sb.String() } +// enforceStrict rejects any step that uses inline code rather than a named tool. +func enforceStrict(stages []CodeStep) error { + for i, s := range stages { + if len(s.Parallel) > 0 { + if err := enforceStrict(s.Parallel); err != nil { + return err + } + continue + } + if s.Tool == "" { + return fmt.Errorf("execute_code: step %d rejected: inline code not allowed in strict mode", i) + } + } + return nil +} + func execCodeArgs(args json.RawMessage) (steps []CodeStep, timeout int, sandboxName string, err error) { var a struct { Steps []CodeStep `json:"steps"` diff --git a/pkg/tools/execute/server.go b/pkg/tools/execute/server.go index 501f290..edf0d93 100644 --- a/pkg/tools/execute/server.go +++ b/pkg/tools/execute/server.go @@ -43,6 +43,12 @@ type Server struct { // step dispatch. Set once at session init via SetLockDir; empty disables locking. lockDir string + // Strict rejects inline {code} steps; only {tool} steps are allowed. + Strict bool + + // Yolo skips the landrun sandbox for all execution. + Yolo bool + // rate limiting state (per-Server) rateLimitMu sync.Mutex validationFailures int @@ -50,10 +56,23 @@ type Server struct { blockedUntil time.Time } +// Option configures a Server. +type Option func(*Server) + +// WithStrict rejects inline {code} steps; only {tool} steps are allowed. +func WithStrict() Option { return func(s *Server) { s.Strict = true } } + +// WithYolo skips the landrun sandbox. +func WithYolo() Option { return func(s *Server) { s.Yolo = true } } + // Decl returns a factory for an execute Server with the given working directory. -func Decl(cwd string) func() tools.Server { +func Decl(cwd string, opts ...Option) func() tools.Server { return func() tools.Server { - return New(cwd) + s := New(cwd) + for _, o := range opts { + o(s) + } + return s } } @@ -334,11 +353,15 @@ func (e *Server) executeWithStdin(ctx context.Context, code, language string, ti default: return "", fmt.Errorf("unsupported language: %s (supported: bash, python3, perl, awk, sed, ed, jq, expect, bc, lua)", language) } - wrapped, wrapErr := sandbox.WrapCommand(cfg, interpreter, workDir) - if wrapErr != nil { - return "", wrapErr + if e.Yolo { + cmd = exec.CommandContext(ctx, interpreter[0], interpreter[1:]...) + } else { + wrapped, wrapErr := sandbox.WrapCommand(cfg, interpreter, workDir) + if wrapErr != nil { + return "", wrapErr + } + cmd = exec.CommandContext(ctx, wrapped[0], wrapped[1:]...) } - cmd = exec.CommandContext(ctx, wrapped[0], wrapped[1:]...) cmd.Dir = workDir switch { case codeStdin != "":