feat(security): add Stage-2 LLM semantic scan for third-party skills#4696
Merged
Conversation
Implements defense against Semantic Compliance Hijacking (SCH) attacks
(arXiv:2605.14460) where malicious third-party skills encode harmful
instructions in SKILL.md without explicit code payloads.
- zeph-skills: new SkillSemanticScanner using chat_typed_erased with a
configurable fast provider; 8 KiB content cap with head+tail sampling;
XML delimiter-escape neutralization (replaces </skill_content> before
interpolation); ScanVerdict::{Allow, Warn, Block} with unknown-token
fallback to Block
- zeph-plugins: scan_targets() extracts SKILL.md candidates from archive
before installation, keeping the crate LLM-free; includes canonicalize
path-traversal guard matching add()
- zeph-core: semantic_scan_plugin_add wires the scanner into handle_plugins;
fail-closed on Block verdict; returns config error when semantic_scan is
enabled but semantic_scan_provider is empty
- zeph-config: corrected doc comments for semantic_scan and
semantic_scan_provider fields; SemanticViolation doc scoped to Stage-1 only
Opt-in via [skill] semantic_scan = true and semantic_scan_provider in config.
Closes #3947
…d read size skill_name and declared_purpose from untrusted SKILL.md frontmatter were interpolated raw outside <skill_content> delimiters, allowing delimiter-escape injection via newlines or </skill_content> tokens in remote skill metadata. - Strip \r\n and neutralize </skill_content> in skill_name and declared_purpose before prompt interpolation in build_user_prompt - Add 512 KiB pre-read size cap in scan_targets() to prevent memory DoS on oversized SKILL.md files from local plugin add
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements defense against Semantic Compliance Hijacking (SCH) attacks (arXiv:2605.14460), where malicious third-party skills encode harmful behavior through natural language instructions in SKILL.md without any explicit code payload, bypassing static security scans.
zeph-skills: newSkillSemanticScanner(semantic_scanner.rs) — LLM-based compliance check usingchat_typed_erasedwith a configurable fast provider; 8 KiB content cap with head+tail sampling; XML delimiter-escape neutralization before prompt interpolation;ScanVerdict::{Allow, Warn, Block}with unknown-token fallback toBlockzeph-plugins:scan_targets()method extracts SKILL.md candidates from archive before installation, keeping the crate LLM-free; includescanonicalize/starts_withpath-traversal guardzeph-core:semantic_scan_plugin_addwires the scanner intohandle_plugins; fail-closed onBlock; returns config error whensemantic_scan = truebutsemantic_scan_provideris emptyzeph-config: doc comments corrected forsemantic_scan,semantic_scan_provider, andSemanticViolationOpt-in:
[skill] semantic_scan = true+semantic_scan_provider = "<provider-name>"in config.Test plan
cargo nextest run --config-file .github/nextest.toml --workspace --lib --bins— 10341 tests pass (10339 existing + 2 new security tests)cargo clippy --workspace -- -D warnings— cleancargo +nightly fmt --check— cleanRUSTFLAGS="-D warnings" cargo check --workspace --all-targets --features desktop,ide,server,chat,pdf,scheduler— cleanchat_typed_erased); use local Ollama provider (OpenAI 429 gate currently blocked)semantic_scan = true+ valid provider → scan runs onzeph plugins add <url>semantic_scan = true+ empty provider → config error returnedCloses #3947