supply-shield (Shieldy) — run bilans
Newest first. See README for the template.
2026-06-30 — first SANDBOXED run from the sec image (runs 019f1783 / 019f1792 / 019f17.. dedup)
- Status: validated (sandboxed, end-to-end)
- Versions: bot supply-shield@0.1.0 · iterion 356fde8b6 · image
ghcr.io/socialgouv/iterion-sandbox-sec:edge(locally rebuilt + CIbuild-sandbox-secgreen — self-test passed: js-x-ray detected the eval,osv-scanner 2.4.0present) - Method: local Docker driver,
iterion runfrom inside/tmp/ssfix/malware-target(fixtures viascripts/adhoc/supply-shield-fixtures.sh),--var base_ref=<.base>,--store-dir /tmp/ss-store. No--sandbox none, no--workdir. - Proven for the first time on real infra:
- Scanners come from the IMAGE (no host shim): js-x-ray, trivy, npm-audit all ran inside the container;
coverage_gate→degraded:false(present:[generic.json, jsxray.json, npm-audit.json],missing:[]). - Skills mirrored INSIDE the container (
skills mirrored=11into the worktree.claude/skills, bind-mounted into the sandbox). - Malware detected.
telemetry-helper@2.4.1'spostinstall.js—eval(atob(...))→https://exfil.exampleC2 +AWS_SECRET_ACCESS_KEY/NPM_TOKENtheft. Notably js-x-ray hit aparsing-erroron the install hook; the LLM reviewer's deep-read fallback caught it directly (anti-façade working). - Cross-run dedup hits sandboxed. 2nd run on the same target →
already_scanned:[telemetry-helper],pending:[].
- Scanners come from the IMAGE (no host shim): js-x-ray, trivy, npm-audit all ran inside the container;
- Engine/infra findings:
- Host-wide cache + sandbox = store-dir matters. With the default store under
~/.iterion, the worktree lives inside~/.iterion, so the~/.iterionhost_state mount is SKIPPED (overlap rule,sandbox.go:collectHostStateMounts) → acache_pathunder~/.iterionis unreachable in-container andupdate_cachedies withPermissionError. Fix: put the run store OUTSIDE~/.iterion(--store-dir /tmp/ss-store) so~/.iterionmounts RW and the cache persists + is writable. Worth documenting for any host-wide-cache sandboxed run. - enumerate_deps shape non-determinism (FIXED, 356fde8b6). The LLM enumerator emits the new dep as
{pkg:[ver]},{eco:[{name,…}]}OR{pkg:{version,path,change,…}}across runs. The last shape (dict keyed by package name, value = details object WITHOUT aname) madenormalize_depsdrop it → 0 packages → no scan/dedup. Fixed by injecting the key asname. Copy-shared to supply-shield-cve.
- Host-wide cache + sandbox = store-dir matters. With the default store under
- Reported (decisions for the operator):
- (b) board emit: under sandbox the
__mcp-boardMCP server is unreachable (C082) —mcp__iterion_board__create_issue→ "No such tool available". The bot handled it gracefully (recorded the error, still produced the verdict + local report). Needs the HTTP board MCP transport (BoardHTTPEndpoint/BoardRunToken) → run via studio/server, not a bareiterion run. - (c) forge_report: no forge token present → degraded to
mode=local-only(report stays atreport_path). Expected; posting for real needs a forge PR + token.
- (b) board emit: under sandbox the
2026-06-30 — dedup + load_cache fixes, re-validated end-to-end
- Status: validated (all three findings from the first run fixed and re-dogfooded)
- Versions: bot supply-shield@0.1.0 · iterion (worktree, dedup fixes)
- Method: same unsandboxed setup as below, but run from inside the target dir (so
workDir= target → skills mirror correctly) + hostiterion-jsxrayshim. Fresh target each time for a clean cache. - What was fixed (from the first run's findings):
- Cross-run dedup now reliable. Added a deterministic
normalize_depsnode that coerces the LLM enumerator's output into canonical[{ecosystem,name,version,checksum}]regardless of the shape it emits (object array,{name:[versions]}map,{ecosystem:[name@ver]}map, orname@versionstrings) and strips artifact-dir prefixes (node_modules/,site-packages/,vendor/) so the name is the bare package id. The cache key changed fromecosystem:name:version:checksumtoecosystem:name:version(the LLM-fragile checksum left the key; it is now metadata compared on a hit to catch republish attacks →_checksum_changed). Proven: run 1 inspectedtelemetry-helper(pending 1, hit 0); run 2 on the same target →cache_hits:1, pending:0(no re-inspection). Applied to all three family bots (supply-shield, supply-shield-cve, sec-audit-deps). - Skill-mirroring was NOT an engine bug —
mirrorBundleSkillsis unconditional and correct. The first run's empty<target>/.claude/skillswas a dogfood-invocation error: passing--var workspace_dir=<target>does NOT move the engine'sworkDir(there is no--workdirflag;workDirdefaults to cwd). Running from inside the target mirrors skills correctly — the reviewer read them immediately this time, no runawaygrep. Documented as a usage note, no code change. load_cacherewritten in python (reads the path from env, not an inlineprintf) — emits clean JSON on a cold cache (line_count: 0, unquotedcache_path) instead of the old"0 0"/ quoted-path glitch.
- Cross-run dedup now reliable. Added a deterministic
- Re-validation result: risk 100 HIGH on
telemetry-helper@2.4.1again (5 signals, js-x-ray-confirmedeval,is-minifiedFP discarded, board issue filed), name now clean (telemetry-helper, prefix stripped), cache holds exactly onekind:malwareline, dedup hit on re-run. The CVE sibling (Vulny) was validated in the same pass — see supply-shield-cve.md.
2026-06-30 — first dogfood: malware PR-gate end-to-end (run 019f1738)
- Status: validated (core) / partial (dedup + scanner floor)
- Versions: bot supply-shield@0.1.0 · iterion
2e357e40e(main) - Method: unsandboxed (
--sandbox none— no docker daemon / sec image available on this host), so the scanner floor was partial. claude_code opus 4.8 forllm_review+forge_report;claw(openai/gpt-5.5via codex ChatGPT forfait) forenumerate_deps. js-x-ray supplied by a hostiterion-jsxrayshim (real@nodesecure/js-x-ray15.1.0 + the Dockerfile walker). Target: a crafted npm repo where the PR HEAD adds a malicioustelemetry-helper@2.4.1whosepostinstall.jsreadsAWS_SECRET_ACCESS_KEY/NPM_TOKEN,eval(atob())-decodes an exfil URL, andcp.exec("curl …")-exfiltrates via child_process.base_ref= the clean base commit. No forge token (soforge_reportexercises its degrade path). - Result: converged in 1 pass, ~5m43s, ~$1.16 (forfait → effectively free).
diff_scopecorrectly scoped to only the newly-added dep (telemetry-helper; the unchangedleft-padwas excluded).coverage_gatepassed (floorgeneric.json+jsxray.jsonpresent) and bannered the one gap (npm-audit.json— needs an install; CVE axis only).llm_review(opus) read the js-x-ray output + the package source and returned risk 100/100 HIGH, validating 5 signals (install-hook, eval-on-startup, base64-blob, child-process-on-import, network-exfil-shape) and correctly discarding theis-minifiedfalse positive on the 1-line stubindex.js— exactly the FP disciplinejs-xray.mddocuments. Filed board issuenative:09b9af90(severity:high), wrotesupply-shield-findings.md(coverage table cleanly separates the covered malware axis from the degraded CVE axis),sarif_genemitted valid SARIF 2.1.0 (5 error-level results),forge_reportcorrectly degraded tomode=local-only/posted=false(no PR/token),update_cacheappended onekind:malwareline. Commits: none (read-only bot). - Value: the headline capability — js-x-ray AST malware detection driven by a real LLM reviewer — is validated end-to-end. The reviewer's verdict was precise and well-evidenced (cited file:line for every signal, recommended blocking + secret rotation), and the anti-façade coverage banner did its job.
- Findings / misses:
- Cross-run cache dedup is unreliable (the headline "no re-check" guarantee). A second identical run did not hit the cache (
pending:[telemetry-helper],cache_hits:0) even thoughload_cacheread the cached line (line_count:1) and the checksum was stable. Root cause:enumerate_deps(an LLM node) emitted a different output shape between runs — run 1 produced the schema-intendeddeps:[{ecosystem,name,version,checksum,…}]; run 2 collapsed it to a{name:[versions]}map with noecosystem/checksum, sofilter_cachedbuilt a malformed cache key and missed. The deterministic cache key depends on non-deterministic LLM structured output (theenumerate_output.depsschema is untypedjson, so both shapes validate). This affects sec-audit-deps (Depsy) too — the enumerate+cache logic was copied from it. Fix direction: normalizeenumerate_depsoutput in a deterministic post-node (coerce to the canonical per-dep array, compute the checksum by hashing the resolved artifact / lockfile integrity) so the key no longer rides on LLM consistency. - Skills were not mirrored to the target workspace on this unsandboxed loose-
.botrun (<target>/.claude/skills/was empty). The reviewer improvised agrep -rlacross$HOMEto find them and timed out (Bash exit 143, ~2 min wasted) before recovering. The verdict still succeeded (opus's native judgement + the js-x-ray signal were sufficient without the skills), but the wasted hunt is a real cost. Investigate whether skill-mirroring is gated on the sandbox/bundle path and should also fire for--sandbox none+ a directorymain.bot. Workaround until then: run sandboxed (the sec image mirrors skills), or pre-mirror. load_cachecosmetic JSON glitch on an empty cache:line_countcame out"0 0"andcache_pathwas single-quoted. Inherited verbatim from Depsy; harmless (the fields aren't load-bearing —filter_cachedreadsraw_jsonl, which was correct). Worth a tidy-up across the family.
- Cross-run cache dedup is unreliable (the headline "no re-check" guarantee). A second identical run did not hit the cache (
- Engine hardening: none committed from this run — findings #1/#2 want focused follow-ups (deterministic cache key; skill-mirror on unsandboxed loose-bot runs), tracked here rather than rushed.
- Lessons for next run:
- Build/publish
iterion-sandbox-sec(with the new Layer 5) and run sandboxed to exercise the realiterion-jsxray+ the trivy/osv CVE floor + npm-audit, and to get skills mirrored automatically. - Don't trust cross-run dedup until
enumerate_depsoutput is normalized deterministically — until then a re-run re-pays the LLM review. - The CVE sibling (supply-shield-cve / Vulny) was not run live yet (needs trivy/osv from the sec image); validate it once the image is built.
- Build/publish
