llm-security/docs/plans/skillspector-gap-analysis.md
Kjell Tore Guttormsen 8a772b0534 docs(llm-security): SkillSpector gap analysis + reviewed execution plan
Brief + 8-step trekplan for 3 new dep-free scanners: TRG (trigger-abuse),
SIG (signature engine over the decode pipeline), AST (Python AST taint).
Adversarial review fixed 3 blockers + 7 majors on draft 1 (59/D -> 86/B).

STATE.md prepped as a self-contained cold-start for /trekexecute next session.
Un-gitignore STATE.md (global ~/.claude rule overrides old polyrepo convention);
ignore generated plan annotation HTML. No scanner code yet.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V3s6WnubSSrFjAQTLQdVbG
2026-06-20 09:00:24 +02:00

16 KiB
Raw Blame History

SkillSpector Gap Analysis & Build Brief

Date: 2026-06-20 Plugin version at time of writing: 7.7.2 Status: Analysis complete, awaiting implementation go-ahead. No code written yet. Reference: NVIDIA/SkillSpector — https://github.com/nvidia/skillspector (Apache-2.0, Python, v2.2.3 as researched)


1. Framing

SkillSpector and llm-security solve partially different problems, and that determines what "missing" actually means:

  • SkillSpector is a static pre-install gate for skill bundles (SKILL.md + code + deps). Python, pip install, runs in CI, emits pass/fail. Its depth is in static code analysis of the bundle itself — real Python AST, variable-level taint, YARA signatures.
  • llm-security is a broad runtime + posture defense for an entire Claude Code setup: 9 hooks, multi-session behavioral analysis, drift, baseline/diff, watch, IDE scanning, 7 package ecosystems, git forensics, MCP live-inspect.

We are broader and own a runtime layer they lack entirely. They are deeper on a few static techniques we either lack or do more coarsely. "Fill what's missing" therefore means closing a small number of sharp holes in our static analysis — not becoming SkillSpector.

The goal for each item below is an llm-security-native implementation (zero external npm dependencies, integrated with our decode/normalize pipeline and policy system) — not a port.


2. Verification log

Per the verification duty, every claim this brief depends on was checked against actual source — SkillSpector via raw source on main, our own plugin via direct file reads. Verdicts below are load-bearing; the priorities in §6 follow from them.

SkillSpector (all VERIFIED against source)

Claim Verdict Source file
4 YARA rulesets (malware, webshells, cryptominers, hacktools) VERIFIED src/skillspector/yara_rules/*.yar
Python ast-based behavioral analysis (exec/eval/subprocess) VERIFIED nodes/analyzers/behavioral_ast.py
Variable-level taint tracking via AST (source→sink) VERIFIED nodes/analyzers/behavioral_taint_tracking.py
Trigger-abuse rules TR1 (broad), TR2 (shadow), TR3 (baiting) VERIFIED nodes/analyzers/static_patterns_supply_chain.py
0100 risk score + exit 0/1/2 at threshold 50 VERIFIED cli.py (risk_score > 50 → Exit(1))
Manifest-vs-code least privilege LP1LP4 VERIFIED nodes/analyzers/mcp_least_privilege.py
SARIF 2.1.0 output VERIFIED cli.py + sarif_models.py

llm-security (our actual status)

Capability Verdict Evidence
#1 Known-malware signature engine MISSING No YARA/webshell/cryptominer/signature engine in scanners/, scanners/lib/, knowledge/. Entropy/taint detect shape, not known-bad identity.
#2 Real AST analysis MISSING taint-tracer.mjs:112 self-documents regex matching, ~70% recall, no scope/cross-file. No AST library imported anywhere (package.json has zero deps; no acorn/babel/espree/tree-sitter).
#3 Trigger/activation-abuse detection MISSING permission-mapper.mjs has exactly 6 checks (purpose-vs-tools, dangerous combos, ghost hooks, haiku model, over-privilege, missing-doc). None inspect name/description for broad/baiting/shadowing triggers.
#4 Per-scan 0100 risk score HAVE severity.mjs riskScore(counts)output.aggregate.risk_score; logged in orchestrator.
#4 CI exit-code gating HAVE scan-orchestrator.mjs:324333: --fail-on <level> exits 1 over threshold; default exits 2/1/0 on BLOCK/WARNING/ALLOW. Policy key ci.failOn.

Correction to the initial analysis: #4 was provisionally listed as a gap. Verification REFUTES that — it is fully implemented. #4 is therefore reclassified from "build" to "already covered, optional polish" (§5). It is intentionally not a build item.


3. What we already beat them on (scope guard — do not rebuild)

Runtime hooks (9), multi-session lethal-trifecta + Jensen-Shannon behavioral drift, MCP description drift with immutable baseline, baseline/diff, watch mode, IDE-extension scanning with VSIX sandbox, 7 package ecosystems (vs their PyPI+npm), git forensics, pre-compact transcript scan, evidence-package sanitization for remote repos, skill-registry fingerprinting. None of these exist in SkillSpector. No inspiration to draw there.


4. Build items

Three genuine gaps. Each lists the our-own-way differentiator, concrete plug-in points (verified from the orchestrator), framework mapping, risks, and per-item verification criteria.

Shared plug-in facts (verified):

  • New deterministic scanner = export scan(targetPath, discovery) (add requiresPriorResults: true for cross-scanner correlation), import it in scan-orchestrator.mjs, append to the SCANNERS array (scan-orchestrator.mjs:101125).
  • Findings use finding() (scanners/lib/output.mjs:3044): scanner (prefix), severity, title, description, file, line, evidence, owasp, recommendation. IDs auto-format as DS-<PREFIX>-NNN.
  • Configurable thresholds: add a section to DEFAULT_POLICY and read via getPolicyValue('section','key',default). User policy at <project>/.llm-security/policy.json.

4.1 — Signature engine (#1) · proposed prefix SIG

Gap: We detect shape (entropy, obfuscation, dangerous flows) but never match against known-bad identities — reverse shells, PHP/JSP/ASPX webshells, cryptominer stratum strings, offensive-tool references. SkillSpector ships 4 YARA rulesets for exactly this.

Our-own-way (better, not a copy):

  • Pure-Node signature engine, zero deps. Rules live as data in knowledge/signatures.json (string/regex/codepoint matchers grouped by family: webshell, reverse-shell, cryptominer, hacktool, c2). No yara-python dependency.
  • Match against the decode/normalize pipeline output, not just raw bytes. This is the differentiator: YARA matches raw file content, so a base64- or hex-obfuscated webshell evades it. We already have normalizeForScan() (Unicode/hex/URL/base64/HTML-entity/zero-width/BIDI/homoglyph/rot13 decode). Running signatures against every decoded variant catches obfuscated known-malware SkillSpector misses.
  • Optional acceleration, graceful degradation. If a yara binary is present on PATH, optionally shell out to it for the heavy lifting (we already shell out to npm audit/git/pip); otherwise the pure-Node engine is authoritative. Never a hard dependency.

Plug-in: new scanners/signature-scanner.mjs, prefix SIG, registered in SCANNERS. Rule data in knowledge/signatures.json. Policy section signatures (enable/disable families, custom rule path).

Frameworks: LLM03 (supply chain) primary; webshell/C2 hits also LLM02 (exfiltration); ASI04.

Risks: signature corpus is maintenance surface and can go stale — keep it small, high-confidence, and family-scoped; document provenance of each rule. False positives on security tooling/education — mitigate with context down-weighting (§5, confidence) and path exclusions (knowledge/, tests).

Verification criteria:

  • Fixture containing a known PHP webshell pattern → SIG flags it (HIGH/CRITICAL).
  • Same payload base64-wrapped → still flagged (decode-pipeline integration; this is what beats raw YARA).
  • Benign file with the word "shell" in prose → not flagged.
  • yara binary absent → scanner still runs and flags via pure-Node path.

4.2 — AST analysis (#2) · augments TNT, proposed behavioral prefix AST

Gap: taint-tracer.mjs is regex/line-based: ~70% recall, no scope awareness, no cross-file, no flow through arrays/objects/closures (self-documented at taint-tracer.mjs:112). SkillSpector parses a real Python AST and tracks taint at variable level with high confidence. This is our deepest methodology gap.

Constraint: zero npm deps (package.json is empty of dependencies). A real JS AST would require a parser dependency. This shapes the recommendation.

Our-own-way — staged, dep-free:

  • Phase A (recommended first): Python AST via a shipped helper. Ship scanners/lib/py-ast-taint.py invoked as python3 <helper> <file> (same optional-python pattern as dep-auditor's pip audit). The helper does ast.parse + ast.walk, runs source→sink taint at variable level, and emits JSON findings back to Node. Graceful degradation: if python3 is absent, fall back to the existing regex tracer. Rationale for going Python-first: skill bundles' executable payloads are most often Python, it is exactly where SkillSpector's AST advantage is real and where our regex tracer is weakest, and it is achievable with no dependency.
  • Phase B (smaller, optional): bracket-aware scope pass for JS/TS. Improve the existing JS regex tracer with balanced-bracket/function-scope tokenization to cut the "variable named input taints the whole file" class of false positives — incremental, still dep-free, no full parser.
  • Phase C (deferred): full JS/TS AST. Requires either vendoring a micro-parser (maintenance cost) or accepting a dependency (breaks the zero-dep invariant). Defer until A+B are measured; decide then.

Plug-in: Python findings flow through the existing taint scanner result (or a new AST prefix if we want them separable in reports). Add policy section ast (enable, python path override). The helper emits the same finding() fields so the orchestrator/SARIF path is unchanged.

Frameworks: LLM01 (injection sinks), LLM02 (exfiltration sinks) — same mapping as TNT, higher precision and recall.

Risks: python3 availability varies (handled by fallback). Subprocess to an interpreter on untrusted code — the helper must only parse (ast.parse never executes), never exec/eval/import the target. State this as a hard invariant and test it.

Verification criteria:

  • Python fixture: k = os.environ["AWS_SECRET"]; requests.post(url, data=k) → flagged CRITICAL (creds→net), where the regex tracer's recall is shaky on the intermediate variable.
  • Python fixture with input reused in two functions, only one tainted → only the tainted use flagged (scope awareness the regex tracer lacks).
  • Helper invoked on a file that calls os.system("rm -rf /") → parses without executing it (parse-only invariant; assert no side effects).
  • python3 removed from PATH → orchestrator still completes via regex fallback (no crash).

4.3 — Trigger / activation-abuse scanner (#3) · proposed prefix TRG

Gap: Cleanest uncovered hole and the most skill-native. Claude skills/commands auto-activate on their description. A skill can be engineered to hijack activation — a single common-word trigger, a maximally-activating description ("use this for anything", "always", "all messages"), or a name that shadows a built-in command. SkillSpector covers this as TR1TR3; we have nothing. permission-mapper's 6 checks never read name/description for activation-surface abuse.

Our-own-way: dedicated scanners/trigger-abuse.mjs, reusing our existing frontmatter parsing across commands/, agents/, skills/:

  • TRG-broad: single common-word or ≤2-char trigger names; description that claims universal applicability.
  • TRG-baiting: maximally-activating phrasing ("anything", "everything", "always", "all files/messages", "whenever") — word lists in policy, extensible.
  • TRG-shadow: name colliding with built-in command/tool names (configurable built-in list).
  • Differentiator vs SkillSpector: run the description through normalizeForScan() first, so a baiting description hidden behind homoglyphs/zero-width still trips — consistent with the rest of our pipeline.

Plug-in: new scanner registered in SCANNERS, prefix TRG, policy section triggers (word lists, built-in names, severity per rule).

Frameworks: AST04 (Scope Creep) primary; LLM06 (Excessive Agency). Cross-reference knowledge/owasp-skills-top10.md.

Risks: legitimate broad utilities exist (e.g. a general formatter) — keep severity advisory (MEDIUM) by default, allowlist via policy, and require the combination of broad-name + universal-claim for the higher tier to cut false positives.

Verification criteria:

  • Skill with name: run and description: "use for anything" → TRG-shadow + TRG-baiting fire.
  • Skill with a specific, scoped description → no finding.
  • Baiting phrase obfuscated with zero-width chars → still flagged (normalize-first differentiator).
  • New scanner appears in scan-orchestrator JSON and SARIF output with correct owasp tag.

5. #4 — already covered (no build)

Per §2, the 0100 risk score and CI exit-code gate exist. Optional, low-cost polish only:

  • Per-finding confidence. The finding() schema has no confidence field; SkillSpector uses confidence pervasively and adjusts it by context (e.g. P5 down-weights safety/education contexts). Adding an optional confidence field and feeding it into riskScore() would (a) let the new signature/AST/trigger scanners express uncertainty and (b) reduce false-positive weight on docs/test/fixture contexts. This is the one cross-cutting enhancement that connects #1/#2/#3 to the existing score. Small, optional, do-after.
  • Document the CI story. docs/ci-cd-guide.md exists; confirm --fail-on + risk_score + SARIF upload are documented end-to-end. Doc-only.

6. Priority & sequencing

Recommended order (value × independence × risk):

  1. #3 Trigger-abuse (TRG) — smallest, lowest-risk, fully dep-free, most skill-native, reuses existing frontmatter + normalize machinery. Best first win; establishes the new-scanner pattern.
  2. #1 Signature engine (SIG) — high value, dep-free, the decode-pipeline integration is a clear "better than YARA" story. Main cost is curating a small high-confidence rule corpus.
  3. #2 AST, Phase A (Python helper) — deepest methodology lift but most design surface (subprocess, parse-only invariant, fallback). Do after the new-scanner pattern is proven by #3/#1. Phases B/C gated on measuring A.

Each item is independent and shippable on its own; no item blocks another. TDD per the Iron Law — failing fixture test first for every rule.


7. Out of scope / deliberate non-goals

  • Harmful-content moderation (SkillSpector P5: toxic substances / dangerous actions). Content safety is not our mission — we are a code/config security scanner, not a content moderator. Excluded.
  • LLM semantic analyzers (SkillSpector SSD/SDI/SQP). We already cover semantic analysis through our agents (skill-scanner-agent, etc.); not reimplementing as inline LLM nodes.
  • Standalone pip install distribution. Our identity is the runtime Claude Code plugin; scan-orchestrator.mjs already works as a CLI. Repackaging for non-Claude CI is deprioritized.
  • Manifest-vs-code least privilege (SkillSpector LP1LP4). Partially covered by permission-mapper (intent-vs-tools, over-privilege). The under-declared dimension (code reaches network but tool not declared) is a possible later extension of permission-mapper, not part of this brief's three items.

8. Consolidated verification criteria

Acceptance for the build phase (each maps to a fixture test):

  • SIG flags a known webshell fixture, including a base64-obfuscated variant; ignores benign prose; runs without a yara binary.
  • AST (Python helper) flags creds→network with an intermediate variable; respects function scope; never executes target code; orchestrator survives missing python3.
  • TRG flags shadow+baiting skills, including obfuscated baiting; passes scoped skills; appears in JSON + SARIF with correct OWASP tags.
  • All three appear in scan-orchestrator.mjs SCANNERS, emit standard finding() objects, and are policy-configurable via .llm-security/policy.json.
  • No new npm dependency added (package.json dependencies remain empty).
  • Existing test suite (1820+) stays green; new rules covered by failing-first tests.

Sources

  • NVIDIA/SkillSpector source (main), verified file-by-file 2026-06-20 — paths in §2.
  • NVIDIA Technical Blog: developer.nvidia.com/blog (verified-agent-skills capability governance).
  • OWASP Agentic Skills Top 10 (AST) — knowledge/owasp-skills-top10.md.
  • llm-security internals verified by direct read: scan-orchestrator.mjs, scanners/taint-tracer.mjs, scanners/permission-mapper.mjs, scanners/lib/{output,severity,policy-loader}.mjs.