Description
A skill that declares x-requires-secrets: <name> in SKILL.md is supposed to have the corresponding ZEPH_SECRET_<NAME> vault value injected into the shell subprocess as env var <NAME> (uppercase) for the duration of its activation (see crates/zeph-core/src/agent/tool_execution/mod.rs:542-582). In practice, the env var never reaches the subprocess in the production executor layout.
Verified live in TUI (zeph --tui --config ~/.config/zeph/gonkagate.toml, commit b668009):
skill=github confidence=0.98 # github skill matched and activated
bash $ env | grep -E '^GITHUB|^GH_' || echo NO_TOKEN
→ NO_TOKEN # GITHUB_TOKEN absent in subprocess env
Vault contains ZEPH_SECRET_GITHUB_TOKEN. zeph skill list confirms github — ... [trusted] (requires: github_token) — parsing and trust are fine. The "skill deactivated: missing required secrets" filter (crates/zeph-core/src/agent/context/assembly.rs:690-696) does NOT fire, so available_custom_secrets["github_token"] IS populated. Yet the env never lands in bash.
Root cause
CompositeExecutor in crates/zeph-tools/src/composite.rs implements ToolExecutor but does not override set_skill_env or set_effective_trust. It therefore inherits the default ToolExecutor impls (crates/zeph-tools/src/executor.rs:651 and :656), which are silent no-ops.
Production wiring (src/agent_setup.rs:534-541):
let base_executor = CompositeExecutor::new(
file_executor,
CompositeExecutor::new(
shell_executor,
CompositeExecutor::new(scrape_executor, cwd_executor),
),
);
let composite = CompositeExecutor::new(base_executor, mcp_executor);
When inject_active_skill_env calls self.tool_executor.set_skill_env(Some(env)), the call enters the outermost CompositeExecutor and is dropped on the floor — it never reaches ShellExecutor::set_skill_env (crates/zeph-tools/src/shell/mod.rs:433), so skill_env: RwLock<Option<HashMap>> stays None and the spawn-time resolve_context (shell/mod.rs:1097) finds nothing to insert.
The same defect breaks set_effective_trust: trust-gating for active skills is bypassed in the production executor layout. Any reasoning that relies on effective_trust reflecting the active skill's trust level (e.g., crates/zeph-tools/src/trust_gate.rs:107) is unreachable when CompositeExecutor is the outermost layer — which is always, in the current bootstrap.
Reproduction
cargo run --features full -- vault set ZEPH_SECRET_GITHUB_TOKEN <token>.
- Add
x-requires-secrets: github_token to ~/.config/zeph/skills/github/SKILL.md frontmatter.
zeph skill trust github trusted.
zeph --tui --config ~/.config/zeph/gonkagate.toml.
- Prompt the agent to run any bash command that introspects env, e.g.
env | grep -E '^GITHUB|^GH_'.
- Observe: tool output shows no
GITHUB_TOKEN. Logs show skill=github confidence=... (skill is active). No skill deactivated: missing required secrets line.
Expected Behaviour
Subprocess env contains GITHUB_TOKEN=<value> while github is among active_skill_names. Removing the skill from active set (or removing the secret from vault) reverts to no env injection.
Logs / Evidence
Saved artifacts:
.local/testing/debug/gh-tui-final.txt — TUI pane capture showing NO_TOKEN
.local/testing/debug/gh-skill-env-log-delta.log — log slice over the test turns
Fix Direction
CompositeExecutor must forward set_skill_env, set_effective_trust, and any other state-mutating non-default methods to both first and second (or to both with deduplication). A blanket-forwarding approach is safer than per-method overrides:
fn set_skill_env(&self, env: Option<HashMap<String, String>>) {
self.first.set_skill_env(env.clone());
self.second.set_skill_env(env);
}
fn set_effective_trust(&self, level: SkillTrustLevel) {
self.first.set_effective_trust(level);
self.second.set_effective_trust(level);
}
Test gap: there is no integration test asserting that set_skill_env propagates through the production layered executor. Adding one (e.g., spy at ShellExecutor level via the existing test infrastructure in crates/zeph-core/src/agent/tool_execution/tests/parallel_and_handle_tests.rs:549-642) would have caught this.
Environment
- Version: commit b668009 on
main
- Config:
~/.config/zeph/gonkagate.toml (full feature set, sandbox network-allow-all, age vault)
- Platform: macOS 25.4.0
Related security implication
Quarantined skills currently appear to permit bash (and other QUARANTINE_DENIED tools) because set_effective_trust is silently dropped at the CompositeExecutor boundary — TrustGateExecutor::effective_trust stays at its initial Trusted value regardless of the active skill's trust level. Worth verifying in a separate test before deciding whether to file a security advisory.
Description
A skill that declares
x-requires-secrets: <name>inSKILL.mdis supposed to have the correspondingZEPH_SECRET_<NAME>vault value injected into the shell subprocess as env var<NAME>(uppercase) for the duration of its activation (seecrates/zeph-core/src/agent/tool_execution/mod.rs:542-582). In practice, the env var never reaches the subprocess in the production executor layout.Verified live in TUI (
zeph --tui --config ~/.config/zeph/gonkagate.toml, commit b668009):Vault contains
ZEPH_SECRET_GITHUB_TOKEN.zeph skill listconfirmsgithub — ... [trusted] (requires: github_token)— parsing and trust are fine. The "skill deactivated: missing required secrets" filter (crates/zeph-core/src/agent/context/assembly.rs:690-696) does NOT fire, soavailable_custom_secrets["github_token"]IS populated. Yet the env never lands inbash.Root cause
CompositeExecutorincrates/zeph-tools/src/composite.rsimplementsToolExecutorbut does not overrideset_skill_envorset_effective_trust. It therefore inherits the defaultToolExecutorimpls (crates/zeph-tools/src/executor.rs:651and:656), which are silent no-ops.Production wiring (
src/agent_setup.rs:534-541):When
inject_active_skill_envcallsself.tool_executor.set_skill_env(Some(env)), the call enters the outermostCompositeExecutorand is dropped on the floor — it never reachesShellExecutor::set_skill_env(crates/zeph-tools/src/shell/mod.rs:433), soskill_env: RwLock<Option<HashMap>>staysNoneand the spawn-timeresolve_context(shell/mod.rs:1097) finds nothing to insert.The same defect breaks
set_effective_trust: trust-gating for active skills is bypassed in the production executor layout. Any reasoning that relies oneffective_trustreflecting the active skill's trust level (e.g.,crates/zeph-tools/src/trust_gate.rs:107) is unreachable whenCompositeExecutoris the outermost layer — which is always, in the current bootstrap.Reproduction
cargo run --features full -- vault set ZEPH_SECRET_GITHUB_TOKEN <token>.x-requires-secrets: github_tokento~/.config/zeph/skills/github/SKILL.mdfrontmatter.zeph skill trust github trusted.zeph --tui --config ~/.config/zeph/gonkagate.toml.env | grep -E '^GITHUB|^GH_'.GITHUB_TOKEN. Logs showskill=github confidence=...(skill is active). Noskill deactivated: missing required secretsline.Expected Behaviour
Subprocess env contains
GITHUB_TOKEN=<value>whilegithubis amongactive_skill_names. Removing the skill from active set (or removing the secret from vault) reverts to no env injection.Logs / Evidence
Saved artifacts:
.local/testing/debug/gh-tui-final.txt— TUI pane capture showingNO_TOKEN.local/testing/debug/gh-skill-env-log-delta.log— log slice over the test turnsFix Direction
CompositeExecutormust forwardset_skill_env,set_effective_trust, and any other state-mutating non-default methods to bothfirstandsecond(or to both with deduplication). A blanket-forwarding approach is safer than per-method overrides:Test gap: there is no integration test asserting that
set_skill_envpropagates through the production layered executor. Adding one (e.g., spy atShellExecutorlevel via the existing test infrastructure incrates/zeph-core/src/agent/tool_execution/tests/parallel_and_handle_tests.rs:549-642) would have caught this.Environment
main~/.config/zeph/gonkagate.toml(full feature set, sandboxnetwork-allow-all, age vault)Related security implication
Quarantined skills currently appear to permit
bash(and otherQUARANTINE_DENIEDtools) becauseset_effective_trustis silently dropped at theCompositeExecutorboundary —TrustGateExecutor::effective_truststays at its initialTrustedvalue regardless of the active skill's trust level. Worth verifying in a separate test before deciding whether to file a security advisory.