Skip to content

fix(skills): gate load_skill through the same trust pipeline as invoke_skill#6062

Merged
bug-ops merged 1 commit into
mainfrom
fix/6050-load-skill-trust-check
Jul 11, 2026
Merged

fix(skills): gate load_skill through the same trust pipeline as invoke_skill#6062
bug-ops merged 1 commit into
mainfrom
fix/6050-load-skill-trust-check

Conversation

@bug-ops

@bug-ops bug-ops commented Jul 11, 2026

Copy link
Copy Markdown
Owner

Summary

  • SkillLoaderExecutor (the load_skill tool) returned raw SKILL.md bodies for any registered skill, including ones classified Blocked or Quarantined, with no refusal or sanitization. The turn-level TrustGateExecutor gate does not close this gap: it only denies load_skill when the turn's folded effective_trust is Quarantined, and never inspects the skill_name argument itself — so a load_skill call naming a Blocked/Quarantined skill bypassed the entire trust defense-in-depth pipeline that invoke_skill already enforced.
  • Extracted the shared trust-gating pipeline (Blocked refusal before any body read, sanitize non-Trusted bodies, wrap Quarantined bodies, blake3 integrity re-check, skill_name sanitization on every output path) into a new SkillTrustGate, used by both SkillLoaderExecutor and SkillInvokeExecutor so the two tools cannot drift apart in the future.
  • Added an agent_setup::build_skill_executors helper that constructs both executors around one shared trust_snapshot Arc, replacing five duplicated inline construction sites across src/runner.rs, src/acp.rs, and src/daemon.rs (production and test helpers).

Test plan

  • cargo +nightly fmt --check
  • cargo clippy --profile ci --workspace --all-targets --features "desktop,ide,server,chat,pdf,scheduler,testing" -- -D warnings
  • cargo nextest run --config-file .github/nextest.toml --workspace --features "desktop,ide,server,chat,pdf,scheduler" --lib --bins (12835 passed, 35 skipped, 0 failed)
  • cargo test --doc --workspace --features "desktop,ide,server,chat,pdf,scheduler"
  • RUSTFLAGS="-D warnings" RUSTDOCFLAGS="--deny rustdoc::broken_intra_doc_links" cargo doc --no-deps --workspace --features "desktop,ide,server,chat,pdf,scheduler"
  • New unit test suite in skill_loader.rs mirroring skill_invoker.rs's trust tests: blocked-refused-without-body-read, non-trusted-sanitized, quarantined-wrapped, trusted-verbatim, missing-row-defaults-to-Trusted, not-found sanitizes skill_name, tampered requires_trust_check skill caught by integrity re-check
  • .local/testing/playbooks/skill-trust-cli.md and .local/testing/coverage-status.md updated

Closes #6050
Closes #6049

@github-actions github-actions Bot added bug Something isn't working documentation Improvements or additions to documentation rust Rust code changes core zeph-core crate size/XL Extra large PR (500+ lines) labels Jul 11, 2026
@bug-ops
bug-ops enabled auto-merge (squash) July 11, 2026 17:45
…e_skill

SkillLoaderExecutor returned raw SKILL.md bodies for any registered skill,
including Blocked and Quarantined ones, with no refusal or sanitization —
the turn-level TrustGateExecutor gate never inspects the skill_name argument
itself, so a load_skill call naming a Blocked/Quarantined skill bypassed the
entire trust defense-in-depth pipeline that invoke_skill already enforced.

Extract the shared trust-gating pipeline (Blocked refusal, sanitize/wrap by
trust level, blake3 integrity re-check, skill_name sanitization) into a new
SkillTrustGate used by both SkillLoaderExecutor and SkillInvokeExecutor, and
add an agent_setup::build_skill_executors helper that constructs both
executors around one shared trust_snapshot Arc, replacing five duplicated
inline construction sites across runner.rs, acp.rs, and daemon.rs.

Closes #6050
Closes #6049
@bug-ops
bug-ops force-pushed the fix/6050-load-skill-trust-check branch from db6ba43 to a752453 Compare July 11, 2026 17:50
@bug-ops
bug-ops merged commit bd3ad2a into main Jul 11, 2026
43 checks passed
@bug-ops
bug-ops deleted the fix/6050-load-skill-trust-check branch July 11, 2026 17:59
bug-ops added a commit that referenced this pull request Jul 11, 2026
…rough SkillTrustGate

SqliteStore::set_requires_trust_check had zero production callers, so the
per-invocation blake3 integrity re-check could never be armed on any real
deployment despite being fully wired on the consumption side since #4306/
#6062. Adds --require-check to `zeph skill trust <name> <level>` (CLI) and
`/skill trust <name> <level> --require-check` (in-session), both calling
the previously-unreachable setter.

Separately, `zeph skill invoke <name>` was a hand-rolled reimplementation
of skill trust gating that predated #6062's consolidation onto the shared
SkillTrustGate: it never checked requires_trust_check and echoed the raw
unsanitized skill name into its not-found error. SkillTrustGate and
SkillBodyResolution are now pub at the zeph-core crate root, and
SkillCommand::Invoke calls SkillTrustGate::resolve_body directly so the
CLI preview can no longer drift from the agent-facing tools.
bug-ops added a commit that referenced this pull request Jul 11, 2026
…rough SkillTrustGate

SqliteStore::set_requires_trust_check had zero production callers, so the
per-invocation blake3 integrity re-check could never be armed on any real
deployment despite being fully wired on the consumption side since #4306/
#6062. Adds --require-check to `zeph skill trust <name> <level>` (CLI) and
`/skill trust <name> <level> --require-check` (in-session), both calling
the previously-unreachable setter.

Separately, `zeph skill invoke <name>` was a hand-rolled reimplementation
of skill trust gating that predated #6062's consolidation onto the shared
SkillTrustGate: it never checked requires_trust_check and echoed the raw
unsanitized skill name into its not-found error. SkillTrustGate and
SkillBodyResolution are now pub at the zeph-core crate root, and
SkillCommand::Invoke calls SkillTrustGate::resolve_body directly so the
CLI preview can no longer drift from the agent-facing tools.
bug-ops added a commit that referenced this pull request Jul 11, 2026
…rough SkillTrustGate (#6090)

SqliteStore::set_requires_trust_check had zero production callers, so the
per-invocation blake3 integrity re-check could never be armed on any real
deployment despite being fully wired on the consumption side since #4306/
#6062. Adds --require-check to `zeph skill trust <name> <level>` (CLI) and
`/skill trust <name> <level> --require-check` (in-session), both calling
the previously-unreachable setter.

Separately, `zeph skill invoke <name>` was a hand-rolled reimplementation
of skill trust gating that predated #6062's consolidation onto the shared
SkillTrustGate: it never checked requires_trust_check and echoed the raw
unsanitized skill name into its not-found error. SkillTrustGate and
SkillBodyResolution are now pub at the zeph-core crate root, and
SkillCommand::Invoke calls SkillTrustGate::resolve_body directly so the
CLI preview can no longer drift from the agent-facing tools.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working core zeph-core crate documentation Improvements or additions to documentation rust Rust code changes size/XL Extra large PR (500+ lines)

Projects

None yet

1 participant