Summary
zeroclaw agent -mblocks shell tool execution when running non-interactively (Docker container, bash loop, CI). Even with[autonomy] level = "full", the SecurityPolicy's is_command_allowed() rejects commands that contain semicolons (;`) in quoted SQL arguments, and the approval prompt reads EOF from stdin and denies the command.
Affected component
security/sandbox
Severity
S1 - workflow blocked
Current behavior
Running zeroclaw agent -m "search leads" inside a Docker container with level = "full":
- Shell tool calls show
[Y]es / [N]o / [A]lways for shell: prompt on stderr
- stdin reads EOF → treated as denial → command never executes
- LLM hallucinates
<tool_result> with fake data, DB file remains 0 bytes
is_command_allowed() also blocks SQL commands containing ; because it splits on ;
without respecting quoted strings (e.g. sqlite3 db.db "CREATE TABLE t(id INT); INSERT INTO t VALUES(1);")
Expected behavior
With level = "full", shell commands execute immediately without interactive approval.
Semicolons inside quoted arguments should not be treated as command separators.
Steps to reproduce
# config.toml
[autonomy]
level = "full"
allowed_commands = ["sqlite3", "python3", "echo"]
forbidden_paths = []
# Non-interactive — no TTY (Docker container, CI, bash loop)
docker exec mycontainer bash -c \
'ZEROCLAW_WORKSPACE=/root/.zeroclaw/workspace zeroclaw agent -m \
"run: sqlite3 /tmp/test.db \"CREATE TABLE t(id INT); INSERT INTO t VALUES(1); SELECT * FROM t;\""'
# stderr shows:
# 🔧 Agent wants to execute: shell
# command: sqlite3 /tmp/test.db "CREATE TABLE t(id INT); INSERT INTO t..."
# [Y]es / [N]o / [A]lways for shell: ← blocks, reads EOF, denies
# DB file remains empty:
# sqlite3 /tmp/test.db ".tables" → (no output, 0 bytes)
Root cause (found in source)
Issue 1 — approval prompt reads stdin even with level = "full":
needs_approval() in src/approval/mod.rs correctly returns false for Full:
if self.autonomy_level == AutonomyLevel::Full {
return false;
}
But is_command_allowed() in src/security/policy.rs blocks the command before
approval is even checked, because it splits on ; without respecting quoted strings:
for sep in ['\n', ';', '|'] {
normalized = normalized.replace(sep, "\x00");
}
sqlite3 db.db "INSERT INTO t VALUES(1);" → splits into sqlite3 db.db "INSERT INTO t VALUES(1)
and empty string → second segment fails allowed_commands check → is_command_allowed returns false
→ validate_command_execution errors → shell tool returns failure before approval prompt.
Issue 2 — approval prompt fires anyway due to the LLM retrying:
The LLM retries with a simpler command, eventually hitting needs_approval() which — despite
level = "full" returning false — still shows the prompt because the security error happens
at the tool execution layer, not the approval layer, causing confusing mixed output.
My config.toml
[autonomy]
level = "full"
workspace_only = false
allowed_commands = ["curl", "python3", "sqlite3", "git", "npm", "echo", "ls", "cat", "grep", "find", "pwd", "wc", "head", "tail"]
forbidden_paths = []
max_actions_per_hour = 20
max_cost_per_day_cents = 500
require_approval_for_medium_risk = true
block_high_risk_commands = true
auto_approve = ["file_read", "memory_recall"]
always_ask = []
Suggested fix
In src/security/policy.rs, is_command_allowed() — skip shell operator guards for Full autonomy,
and fix semicolon parsing to respect quoted strings:
pub fn is_command_allowed(&self, command: &str) -> bool {
if self.autonomy == AutonomyLevel::ReadOnly {
return false;
}
// Full autonomy: trust the allowlist, skip pipe/semicolon/redirect guards
if self.autonomy == AutonomyLevel::Full {
return true;
}
// ... rest of existing checks
}
And in validate_command_execution(), skip risk gating for Full:
let risk = self.command_risk_level(command);
// Full autonomy: skip medium/high risk gating
if self.autonomy == AutonomyLevel::Full {
return Ok(risk);
}
Impact
Blocks all autonomous agent operation in non-interactive environments (Docker, CI, bash loops).
level = "full" is effectively broken for the primary use case of headless agents.
Logs / stack traces
Logs
INFO zeroclaw::agent::loop_: Memory initialized backend="sqlite"
WARN zeroclaw::skills: failed to run git clone for open-skills: No such file or directory
🔧 Agent wants to execute: shell
command: sqlite3 /workspace/leads.db "CREATE TABLE leads(...); INSERT INTO..."
[Y]es / [N]o / [A]lways for shell:
DB after run: -rw-r--r-- 1 root root 0 Feb 18 20:00 /workspace/leads.db
ZeroClaw version
v0.1.0 (commit b43e9eb)
Rust version
rustc 1.93.0 (ARM64, EC2 Graviton2)
Operating system
Linux (Docker container, Alpine/Ubuntu)
Regression?
No, first-time setup
Pre-flight checks
Summary
zeroclaw agent -m
blocks shell tool execution when running non-interactively (Docker container, bash loop, CI). Even with[autonomy] level = "full", the SecurityPolicy'sis_command_allowed()rejects commands that contain semicolons (;`) in quoted SQL arguments, and the approval prompt reads EOF from stdin and denies the command.Affected component
security/sandbox
Severity
S1 - workflow blocked
Current behavior
Running
zeroclaw agent -m "search leads"inside a Docker container withlevel = "full":[Y]es / [N]o / [A]lways for shell:prompt on stderr<tool_result>with fake data, DB file remains 0 bytesis_command_allowed()also blocks SQL commands containing;because it splits on;without respecting quoted strings (e.g.
sqlite3 db.db "CREATE TABLE t(id INT); INSERT INTO t VALUES(1);")Expected behavior
With
level = "full", shell commands execute immediately without interactive approval.Semicolons inside quoted arguments should not be treated as command separators.
Steps to reproduce
Impact
Blocks all autonomous agent operation in non-interactive environments (Docker, CI, bash loops).
level = "full" is effectively broken for the primary use case of headless agents.
Logs / stack traces
ZeroClaw version
v0.1.0 (commit b43e9eb)
Rust version
rustc 1.93.0 (ARM64, EC2 Graviton2)
Operating system
Linux (Docker container, Alpine/Ubuntu)
Regression?
No, first-time setup
Pre-flight checks