Symptom
15 of the 17 bundled hands declare shell_exec in their tools list. For a large subset of them, the only shell usage documented anywhere in the system prompt is a Phase 0 platform-detection one-liner:
## Phase 0 — Platform Detection & Context (ALWAYS DO THIS FIRST)
Detect the operating system:
python -c "import platform; print(platform.system())"
That command can never succeed. crates/librefang-runtime/src/dangerous_command.rs:123-126 in the core blocks it unconditionally:
dp!(
"script execution via -e/-c flag",
r"\b(python[23]?|perl|ruby|node)\s+-[ec]\s+"
),
python -c "import platform; ..." matches that regex. We observed it firing live in production — the agent dutifully ran the Phase 0 instruction on each activation and got shell_exec blocked: dangerous command detected (script execution via -e/-c flag) every time, then improvised alternatives.
Scope
Hands declaring shell_exec: analytics, apitester, clip, collector, devops, devteam, lead, linkedin, predictor, reddit, researcher, strategist, trader, twitter, wiki (15/17 — only browser and creator do not).
Hands whose prompt contains the blocked python -c line: analytics, apitester, clip, collector, devops, lead, linkedin, predictor, reddit, researcher, strategist, trader, twitter (13).
Of those, 9 carry the identical "Platform Detection" section verbatim (collector, lead, predictor, linkedin, clip, researcher, trader, reddit, twitter) — it reads as a copy-pasted prompt template rather than a per-hand requirement.
Why this matters beyond a dead instruction
Declaring shell_exec is not free. In the core, crates/librefang-kernel/src/kernel/hands_lifecycle.rs:357-365 keys off exactly this list:
if manifest.exec_policy.is_none() && def.tools.iter().any(|t| t == "shell_exec") {
manifest.exec_policy = Some(librefang_types::config::ExecPolicy {
mode: librefang_types::config::ExecSecurityMode::Full,
...
So a hand that names shell_exec in tools and does not declare an explicit [exec_policy] is granted ExecSecurityMode::Full — no allowlist filtering, and (per tool_runner/dispatch.rs:1708-1712) no approval prompt either — regardless of the operator's global exec_policy.mode. I filed that core behaviour separately as librefang/librefang#6594; none of the bundled hands declare [exec_policy], so every one of the 15 inherits it.
The result for a research/social/analytics hand is unrestricted host shell as a side effect of a one-line OS check that cannot run.
Suggested change
- Drop the Phase 0 platform-detection block from the prompts where it appears. It never executes, and the hands using it (research, social posting, analytics) do not branch on OS anywhere else in their prompt.
- Remove
shell_exec from tools for hands that have no other shell usage. For the ones that genuinely need it (devops, devteam, apitester plausibly do), keep it.
- Add an explicit
[exec_policy] to any hand that keeps shell_exec, so the grant is intentional and visible rather than inherited from the core default:
[exec_policy]
mode = "allowlist" # or "deny"
The core only injects Full when the field is absent; an explicit value is respected. This also makes the hand's shell posture self-documenting for operators reviewing it before activation.
- If platform detection is actually wanted,
uname -s is not caught by the guard — but it is worth asking whether these hands need it at all.
Environment
- Registry at
ff69767 (2026-07-16)
- Core
v2026.7.21; the guard and lifecycle code cited above are byte-identical at v2026.7.27 (latest tag)
Symptom
15 of the 17 bundled hands declare
shell_execin theirtoolslist. For a large subset of them, the only shell usage documented anywhere in the system prompt is a Phase 0 platform-detection one-liner:python -c "import platform; print(platform.system())"
That command can never succeed.
crates/librefang-runtime/src/dangerous_command.rs:123-126in the core blocks it unconditionally:python -c "import platform; ..."matches that regex. We observed it firing live in production — the agent dutifully ran the Phase 0 instruction on each activation and gotshell_exec blocked: dangerous command detected (script execution via -e/-c flag)every time, then improvised alternatives.Scope
Hands declaring
shell_exec: analytics, apitester, clip, collector, devops, devteam, lead, linkedin, predictor, reddit, researcher, strategist, trader, twitter, wiki (15/17 — onlybrowserandcreatordo not).Hands whose prompt contains the blocked
python -cline: analytics, apitester, clip, collector, devops, lead, linkedin, predictor, reddit, researcher, strategist, trader, twitter (13).Of those, 9 carry the identical "Platform Detection" section verbatim (collector, lead, predictor, linkedin, clip, researcher, trader, reddit, twitter) — it reads as a copy-pasted prompt template rather than a per-hand requirement.
Why this matters beyond a dead instruction
Declaring
shell_execis not free. In the core,crates/librefang-kernel/src/kernel/hands_lifecycle.rs:357-365keys off exactly this list:So a hand that names
shell_execintoolsand does not declare an explicit[exec_policy]is grantedExecSecurityMode::Full— no allowlist filtering, and (pertool_runner/dispatch.rs:1708-1712) no approval prompt either — regardless of the operator's globalexec_policy.mode. I filed that core behaviour separately as librefang/librefang#6594; none of the bundled hands declare[exec_policy], so every one of the 15 inherits it.The result for a research/social/analytics hand is unrestricted host shell as a side effect of a one-line OS check that cannot run.
Suggested change
shell_execfromtoolsfor hands that have no other shell usage. For the ones that genuinely need it (devops,devteam,apitesterplausibly do), keep it.[exec_policy]to any hand that keepsshell_exec, so the grant is intentional and visible rather than inherited from the core default:Fullwhen the field is absent; an explicit value is respected. This also makes the hand's shell posture self-documenting for operators reviewing it before activation.uname -sis not caught by the guard — but it is worth asking whether these hands need it at all.Environment
ff69767(2026-07-16)v2026.7.21; the guard and lifecycle code cited above are byte-identical atv2026.7.27(latest tag)