diff --git a/docs/ROADMAP.org b/docs/ROADMAP.org index a9c9010..538bfa3 100644 --- a/docs/ROADMAP.org +++ b/docs/ROADMAP.org @@ -207,6 +207,33 @@ Every subsequent release ships with automated regression protection. The eval ha - Task suite grows with codebase: every bug fix adds a regression task ~200 lines. +*** TODO Jailed-package sandbox hardening +:PROPERTIES: +:ID: id-v090-sandbox +:CREATED: [2026-05-20 Wed] +:END: + +The skill loader's sandbox (~core-skills.org~) scans for ~"uiop:run-program"~ as text. This misses indirect calls: + +#+begin_src lisp +(eval (read-from-string "(uiop:run-program \"rm -rf /\")")) +#+end_src + +The jailed package ~:use~s ~:cl~ and ~:passepartout~, so ~eval~, ~find-symbol~, ~intern~, ~funcall~, ~read~ are all available. The post-eval ~symbol-function~ equality check is also bypassable by wrapping. + +Fix: add ~eval~, ~funcall~, ~apply~, ~find-symbol~, ~intern~, ~read~, ~read-from-string~ to the text-level ~*skill-restricted-symbols*~ list, and shadow them in the jailed package at load time: + +#+begin_src lisp +(dolist (sym-name '("EVAL" "READ" "READ-FROM-STRING" + "FIND-SYMBOL" "INTERN" "FUNCALL" "APPLY")) + (let ((sym (find-symbol sym-name :cl))) + (when sym (shadow sym jailed-pkg)))) +#+end_src + +This makes the symbols unfindable in the jailed package without an explicit ~cl:eval~ prefix, which the text scan then catches. Text scan + shadowing is the combo that closes the bypass. + +~20 lines across ~core-skills.org~. Ships with the eval harness so the harness itself tests the sandbox before use. + ** v0.10.0: Emacs Development Environment — Secondary Client cl-tty is the primary TUI (v0.8.0). The Emacs major mode is an optional secondary client for users who prefer Emacs-based workflows. Both clients communicate with the same daemon over the same TCP protocol — they are interchangeable frontends, not competing architectures. @@ -297,6 +324,10 @@ The Dispatcher gate stack currently prevents self-modification through pattern m 5. Add ~dispatcher-check-self-termination~: scan shell commands for patterns targeting the Passepartout process (~kill -9 ~, ~rm -rf ~/.cache/passepartout/~, ~sudo apt remove sbcl~). Return ~:reject-self-termination~ with a diagnostic message explaining which command matched and why it would destroy the agent. Human override is possible via HITL — the gate does not prevent the human from issuing the command in a terminal. It prevents the /LLM/ from issuing it accidentally. ~20 lines. 6. Add ~integrity-verify-core-files~: on heartbeat, hash the eight core files against known-good values stored at daemon startup. On mismatch, inject an integrity alert into the signal queue. ~25 lines, uses existing SHA-256 infrastructure from v0.2.0 Merkle memory. +7. Add CI tangle verification: after checkout, tangle all ~.org~ files, then ~git diff --exit-code~. CI fails if committed ~.lisp~ files don't match canonical ~.org~ source. One CI step in ~.github/workflows/lint.yml~. Also stop ~.gitignore~-ing ~lisp/~ and commit tangled output — lets ~ql:quickload~ work on fresh clone without Emacs. + +8. Add daemon connection thread limits: ~*max-client-connections*~ (default 32), ~*active-client-count*~ with lock, reject connections when at capacity. Add read timeout (~sb-ext:with-timeout~, default 300s) on ~read-framed-message~ to reap silent clients. ~25 lines in ~core-transport.org~. + *** Verification Existing FiveAM gate tests continue to pass. New test: signal at type-level 5 targeting a gate at type-level 4 returns ~:reject-type-violation~ without evaluating the gate predicate. New test: signal at type-level 1 passing through a gate at type-level 3 proceeds to predicate evaluation. New test: ~kill -9 ~ returns ~:reject-self-termination~. New test: modified core file is detected by integrity hash check. @@ -543,6 +574,12 @@ Inject a system message: "Memory critical (94% of 16GB). Unloading embedding-nat Skill shed order is determined by a new ~:preservation-priority~ slot on ~defskill~ (default ~:normal~). Core safety skills carry ~:critical~ and are never shed. Heavy skills (embedding-native with its model in memory, channel gateways with connection pools) carry ~:low~. +**** Snapshot budget enforcement — extends ~core-memory.lisp~ + +The current ~snapshot-memory~ deep-copies the entire store. For stores exceeding ~MEMORY_SNAPSHOT_MAX_OBJECTS~ (default 50,000), skip full copies and switch to a journaling mode: record only the objects changed since the last snapshot. Implemented as a conditional branch in ~snapshot-memory~. ~20 lines. + +Add this to the resource self-monitoring heartbeat: before each snapshot, check store size. If over threshold, use journaling path. Keeps the snapshot model viable for the claimed 10M-object use case without requiring 30GB of RAM for 20 snapshots. + **** External watchdog — extends ~passepartout~ bash entry point The bash script spawns a watchdog subprocess that polls the daemon port every ~WATCHDOG_TIMEOUT~ seconds (default 30). If the port stops responding, the watchdog snapshots the last known-good Merkle root, kills the stale process, and restarts the daemon with ~--snapshot ~. diff --git a/org/channel-tui-main.org b/org/channel-tui-main.org index ed8ba56..71c3b1e 100644 --- a/org/channel-tui-main.org +++ b/org/channel-tui-main.org @@ -946,17 +946,13 @@ Returns T on success, nil on failure. Does NOT wait or retry." (*error-output* (make-string-output-stream))) (funcall (find-symbol "CREATE-SERVER" "SWANK") :port swank-port :dont-close t)) - (add-msg :system - (format nil "* Swank ~d M-x slime-connect *" swank-port))) + (values)) (error () (add-msg :system "* Swank unavailable *")))) (cl-tty.backend:with-terminal (be w h) - ;; stty -icanon -echo -ixon is set by the bash script. - ;; We read directly from SBCL's stdin (fd 0) since the - ;; terminal is in raw mode — no cat subprocess needed. - (add-msg :system (format nil "* ~a backend ~dx~d *" - (if (typep be 'cl-tty.backend:modern-backend) "modern" "simple") - w h)) + ;; stty -icanon -echo -ixon is set by the bash script. + ;; We read directly from SBCL's stdin (fd 0) since the + ;; terminal is in raw mode — no cat subprocess needed. ;; Initial dirty all to trigger first redraw in loop (setq w (or (and (numberp w) (> w 0) w) 80) h (or (and (numberp h) (> h 0) h) 24))