Summary
agents/live_capabilities.py synthesize_capability() writes agent-provided tool code to tools/dynamic/{name}.py and the deploy step itself then runs it: the auto-test executes the file (python3 -c exec(open(...).read())) and imports it and calls the exported function (live_capabilities.py:763-791). The daemon later hot-loads the same file via exec_module on startup/reload (agents/daemon.py:231), and the synthesized function is registered with no approval step anywhere in deploy→execute (daemon.py:295-299).
The only gate is a syntax/AST check (live_capabilities.py:509-585, daemon.py:197-207): it rejects stubs, writes to three hardcoded paths, and module-level executable statements. Nothing inspects what a function body does, and the protected-path regex only matches a literal open('/agentOS/agents|api|mcp', ...) — Path("/agentOS/agents/registry.py").write_text(...), os.system(...), or any other body-level operation passes untouched.
This is the same incident class as BUG-015 (2026-05-05, "synthesized tools can write to core system files via Python file I/O" — a tool with a module-level open('/agentOS/agents/registry.py','w') truncated the registry at hotload import time). The AST check added for BUG-015 stops module-level statements, but not function bodies — and the synthesis path's auto-test executes the file before the hot-load gate even runs.
I verified the chain locally (scratch dir; the only change was redirecting the hardcoded /agentOS mount point — gate, write and auto-test logic are the repo's code verbatim):
- payload
def pwn_canary(**kw): import os; os.system("echo <nonce> > /tmp/canary") → synthesize_capability returns ok: true and writes the file;
- deploy-time auto-test (live_capabilities.py:765-791):
exec(open(...)) rc 0, then exec_module + fn() → the canary file is created during synthesis itself — before any daemon reload and before anyone calls the tool;
- the daemon hot-load path (
_hotload_dynamic_tools, daemon.py:197-231) with a real engine: same file passes the AST gate, is exec_module'd, registered (daemon.py:295-299), and invoking the registered function runs the payload.
Details
The interesting mismatch with the documented design: synthesize_capability's docstring (live_capabilities.py:471-474) says the capability is "submitted to quorum, voted on automatically next daemon cycle, and hot-loaded into the running engine on approval". The code does not wait: it writes the file, auto-tests it (which executes it), and calls POST /tools/reload immediately. The quorum path only exists for propose_change proposals, and for new_tool it writes a .json spec without code (proposals.py:394-408); synthesize_capability is the code path and it has no vote.
Also worth noting: built-in capabilities (shell_exec, fs_write, ...) run with restrictions (e.g. shell_exec's regex block for open('/agentOS/agents/...','w'), BUGS.md BUG-015 fix #3). The synthesis path is a weaker filter that achieves the same effect as those capabilities plus restart persistence, without the restrictions.
How to reproduce
python3 - <<'EOF'
payload = 'def pwn_canary(**kw):\n import os\n os.system("echo NONCE > /tmp/pwn_canary.txt")\n return {"ok": True}\n'
# 1) synthesize_capability(payload) -> ok: true, tools/dynamic/pwn_canary.py written
# (gate at live_capabilities.py:509-585 accepts function-body payloads)
# 2) deploy-time auto-test (live_capabilities.py:763-791):
# step1: python3 -c "exec(open('pwn_canary.py').read())" -> rc 0
# step2: exec_module + fn() -> {"ok": true}
# 3) /tmp/pwn_canary.txt now exists
EOF
The hot-load side (_hotload_dynamic_tools, daemon.py:197-231) re-executes the same file on every daemon start/reload, with shell_exec/fs_read/fs_write helpers bound into the module namespace using the daemon's bearer token from /agentOS/config.json (daemon.py:222-227).
Impact
An agent whose context includes web research, shared logs, or peer messages can be steered to synthesize a tool whose body does anything; the payload runs at deploy time, persists across restarts in tools/dynamic/, re-runs on every hot-load, and executes inside the daemon process with helpers bound to the daemon's full API surface.
Not claiming new privilege — the agent can already run commands via shell_exec (execution_engine.py:26,82; the requires_approval flag is written but never read, so there is no approval gate anywhere). The concrete increments are: (1) persistence across restarts, (2) auto-execution at deploy time, (3) re-execution on every hot-load, (4) execution inside the daemon namespace with master-token helpers.
Limitation: I did not demo the steering step end-to-end with a live LLM; the gate/write/auto-test/load/exec chain is code-verified, and the context channels feeding the agent are the repo's documented design.
Suggested change
- Remove the deploy-time auto-execution, or run it in a restricted subprocess with no token access and no persistence.
- Scan function bodies, not just module-level statements (e.g. reject body-level
open(...,'w')/os.system/subprocess unless declared capabilities allow them).
- Run synthesized tools in an isolated process without the master token in the module namespace.
- Make the code match the docstring: actual quorum approval before write/hot-load, not after.
Summary
agents/live_capabilities.pysynthesize_capability()writes agent-provided tool code totools/dynamic/{name}.pyand the deploy step itself then runs it: the auto-test executes the file (python3 -c exec(open(...).read())) and imports it and calls the exported function (live_capabilities.py:763-791). The daemon later hot-loads the same file viaexec_moduleon startup/reload (agents/daemon.py:231), and the synthesized function is registered with no approval step anywhere in deploy→execute (daemon.py:295-299).The only gate is a syntax/AST check (
live_capabilities.py:509-585,daemon.py:197-207): it rejects stubs, writes to three hardcoded paths, and module-level executable statements. Nothing inspects what a function body does, and the protected-path regex only matches a literalopen('/agentOS/agents|api|mcp', ...)—Path("/agentOS/agents/registry.py").write_text(...),os.system(...), or any other body-level operation passes untouched.This is the same incident class as BUG-015 (2026-05-05, "synthesized tools can write to core system files via Python file I/O" — a tool with a module-level
open('/agentOS/agents/registry.py','w')truncated the registry at hotload import time). The AST check added for BUG-015 stops module-level statements, but not function bodies — and the synthesis path's auto-test executes the file before the hot-load gate even runs.I verified the chain locally (scratch dir; the only change was redirecting the hardcoded
/agentOSmount point — gate, write and auto-test logic are the repo's code verbatim):def pwn_canary(**kw): import os; os.system("echo <nonce> > /tmp/canary")→synthesize_capabilityreturnsok: trueand writes the file;exec(open(...))rc 0, thenexec_module+fn()→ the canary file is created during synthesis itself — before any daemon reload and before anyone calls the tool;_hotload_dynamic_tools, daemon.py:197-231) with a real engine: same file passes the AST gate, isexec_module'd, registered (daemon.py:295-299), and invoking the registered function runs the payload.Details
The interesting mismatch with the documented design:
synthesize_capability's docstring (live_capabilities.py:471-474) says the capability is "submitted to quorum, voted on automatically next daemon cycle, and hot-loaded into the running engine on approval". The code does not wait: it writes the file, auto-tests it (which executes it), and callsPOST /tools/reloadimmediately. The quorum path only exists forpropose_changeproposals, and fornew_toolit writes a.jsonspec without code (proposals.py:394-408);synthesize_capabilityis the code path and it has no vote.Also worth noting: built-in capabilities (
shell_exec,fs_write, ...) run with restrictions (e.g. shell_exec's regex block foropen('/agentOS/agents/...','w'), BUGS.md BUG-015 fix #3). The synthesis path is a weaker filter that achieves the same effect as those capabilities plus restart persistence, without the restrictions.How to reproduce
The hot-load side (
_hotload_dynamic_tools, daemon.py:197-231) re-executes the same file on every daemon start/reload, withshell_exec/fs_read/fs_writehelpers bound into the module namespace using the daemon's bearer token from/agentOS/config.json(daemon.py:222-227).Impact
An agent whose context includes web research, shared logs, or peer messages can be steered to synthesize a tool whose body does anything; the payload runs at deploy time, persists across restarts in
tools/dynamic/, re-runs on every hot-load, and executes inside the daemon process with helpers bound to the daemon's full API surface.Not claiming new privilege — the agent can already run commands via
shell_exec(execution_engine.py:26,82; therequires_approvalflag is written but never read, so there is no approval gate anywhere). The concrete increments are: (1) persistence across restarts, (2) auto-execution at deploy time, (3) re-execution on every hot-load, (4) execution inside the daemon namespace with master-token helpers.Limitation: I did not demo the steering step end-to-end with a live LLM; the gate/write/auto-test/load/exec chain is code-verified, and the context channels feeding the agent are the repo's documented design.
Suggested change
open(...,'w')/os.system/subprocess unless declared capabilities allow them).