From 88ea073135369b976154623a3874c06550a320da Mon Sep 17 00:00:00 2001 From: Logan Neely Date: Thu, 7 May 2026 22:27:27 +0200 Subject: [PATCH] olliesrv: add -strict and -yolo flags -strict: only tool steps allowed, inline code rejected. -yolo: skip landrun sandbox for execute_code. --- README.md | 15 +++++++++++++++ main.go | 17 ++++++++++++++++- server.go | 15 ++++++++++++++- store/session.go | 13 ++++++++++++- 4 files changed, 57 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7a4e12b..64477e5 100644 --- a/README.md +++ b/README.md @@ -123,6 +123,21 @@ The server listens on a Unix socket in the Plan 9 namespace (`$NAMESPACE/ollie`) `olliesrv mount` is for remote instances: it calls `9pfuse ` and defaults the mountpoint to `$HOME/mnt/`. +### Flags + +| Flag | Effect | +|------|--------| +| `-strict` | Only `{tool}` steps are allowed in `execute_code`; inline `{code}` steps are rejected. | +| `-yolo` | Skip the landrun sandbox for all `execute_code` execution. | +| `-tcp ` | Also listen on a TCP address (e.g. `:564`). | +| `-mount ` | Override the FUSE mount path. | + +```sh +olliesrv start -strict # tools only, sandboxed +olliesrv start -yolo # arbitrary code, no sandbox +olliesrv start -strict -yolo # tools only, no sandbox +``` + ## Sessions ### Create diff --git a/main.go b/main.go index 04d068d..3260cde 100644 --- a/main.go +++ b/main.go @@ -20,6 +20,8 @@ const serviceName = "ollie" var mountPath = flag.String("mount", "", "FUSE mount path (default: $HOME/mnt/ollie)") var tcpAddr = flag.String("tcp", "", "also listen on TCP address (e.g. :564)") +var strict = flag.Bool("strict", false, "only allow tool steps; reject inline code") +var yolo = flag.Bool("yolo", false, "skip landrun sandbox for execute_code") func main() { if len(os.Args) < 2 { @@ -115,6 +117,12 @@ func daemonize(pidPath string) { if *tcpAddr != "" { args = append(args, "-tcp", *tcpAddr) } + if *strict { + args = append(args, "-strict") + } + if *yolo { + args = append(args, "-yolo") + } cmd := exec.Command(exe, args...) cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true} if err := cmd.Start(); err != nil { @@ -152,7 +160,14 @@ func runServer(sockPath, pidPath string) { os.WriteFile(pidPath, []byte(fmt.Sprintf("%d", os.Getpid())), 0644) //nolint:errcheck sink := olog.NewSink(os.Stdout, os.Stderr, olog.ParseLevel(os.Getenv("OLLIE_LOG"), olog.LevelWarn)) - srv := New(sink) + var srvOpts []ServerOption + if *strict { + srvOpts = append(srvOpts, WithStrict()) + } + if *yolo { + srvOpts = append(srvOpts, WithYolo()) + } + srv := New(sink, srvOpts...) listener, err := net.Listen("unix", sockPath) if err != nil { diff --git a/server.go b/server.go index 308cc1a..c289d46 100644 --- a/server.go +++ b/server.go @@ -226,10 +226,18 @@ type Server struct { sessionStore *SessionStore transcriptStore Store tmpStore Store + strict bool + yolo bool } +// ServerOption configures the 9P server. +type ServerOption func(*Server) + +func WithStrict() ServerOption { return func(s *Server) { s.strict = true } } +func WithYolo() ServerOption { return func(s *Server) { s.yolo = true } } + // New creates a new Server. -func New(sink *olog.Sink) *Server { +func New(sink *olog.Sink, opts ...ServerOption) *Server { memDir := defaultMemDir() os.MkdirAll(memDir, 0755) //nolint:errcheck transcriptDir := defaultTranscriptDir() @@ -252,11 +260,16 @@ func New(sink *olog.Sink) *Server { transcriptStore: NewFlatDirStore(transcriptDir, 0444), tmpStore: NewFlatDirStore(tmpDir, 0600), } + for _, o := range opts { + o(s) + } s.sessionStore = store.NewSessionStore(store.SessionStoreConfig{ AgentsDir: agentsDir, SessionsDir: sessionsDir, Log: s.log, Sink: s.sink, + Strict: s.strict, + Yolo: s.yolo, SaveTranscript: func(data []byte) error { name := time.Now().Format("20060102T150405") + "-chat.md" return storeWrite(s.transcriptStore, name, data) diff --git a/store/session.go b/store/session.go index 3565b43..0a1bbda 100644 --- a/store/session.go +++ b/store/session.go @@ -110,6 +110,10 @@ type SessionStoreConfig struct { // NewCore, if non-nil, replaces the default backend.New + agent.NewAgentCore // path. It receives the session ID, agent name, and cwd, and returns a Core. NewCore func(sessionID, agentName, cwd string) (agent.Core, error) + // Strict rejects inline code steps; only tool steps are allowed. + Strict bool + // Yolo skips the landrun sandbox. + Yolo bool } // SessionStore implements Store for session management. @@ -371,8 +375,15 @@ func (s *SessionStore) createSession(args []string) error { cfg := LoadAgentConfig(s.cfg.AgentsDir, agentName, nil) + var execOpts []execute.Option + if s.cfg.Strict { + execOpts = append(execOpts, execute.WithStrict()) + } + if s.cfg.Yolo { + execOpts = append(execOpts, execute.WithYolo()) + } newDisp := tools.NewDispatcherFunc(map[string]func() tools.Server{ - "execute": execute.Decl(cwd), + "execute": execute.Decl(cwd, execOpts...), }) env := agent.BuildAgentEnv(cfg, newDisp(), cwd)