Skip to content

feat!: cache out of the scanned tree, third-party licenses, symlink reporting (1.4.0) - #40

Merged
aksOps merged 1 commit into
mainfrom
fix/v1.4.0-final
Aug 4, 2026
Merged

feat!: cache out of the scanned tree, third-party licenses, symlink reporting (1.4.0)#40
aksOps merged 1 commit into
mainfrom
fix/v1.4.0-final

Conversation

@aksOps

@aksOps aksOps commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Closes the two confirmed blockers from the v1.3.0 verification gate, plus the adoption and supply-chain findings behind them. Cut as 1.4.0: the cache location and the duplicate-block default both change behavior.

Blockers

Cache poisoning via archive-delivered trees. Against shipped 1.3.0: forge a cache entry with a valid tag, ship the tree as a tar.gz that extracts to a predictable path (Docker WORKDIR, CI checkout), and siloscan reports a tree containing a live-format AWS key as clean, exit 0. tar preserves the 0600 mode the salt provenance check depended on, leaving only absolute-path binding, which a published Dockerfile defeats. Rather than patch the check, the cache moves out of the scanned tree: a per-user cache directory keyed by canonical scan root, with an in-tree .siloscan/cache never read (and never deleted - that is the user's repository). Verified: the attack reproduces on 1.3.0 and fails here.

Release archives shipped 139 statically linked crates with no license texts. THIRD-PARTY-LICENSES is now generated from the actual linked dependency set (201 crates, hard-failing if any yields no text) and included in all three archives.

Also

  • Symlinks were silently dropped - not scanned, not in skipped, not in ignored. A '.env -> ../vault/prod.env' was invisible. They are now recorded, distinguishing inside-root (informational, target scanned on its own path) from outside-root and dangling (target never scanned). --follow-symlinks opts in and still refuses to leave the tree.
  • Duplicate-block findings swamped real results: 46,891 of 47,102 findings on a real Rust tree, and a 28.7 MB / 28,195-result SARIF that exceeds GitHub's ingestion limits outright. Off by default now; metrics unchanged. Same tree: 14.6 KB / 6 results.
  • --min-severity filters reported findings (distinct from --fail-on) and is recorded in JSON and SARIF so a filtered report cannot read as clean.
  • Generic secret rules for credentialed URLs, AWS secret access keys, and high-entropy assignments, allow-listed against roughly 70 vendor prefixes so one credential yields one finding. Measured: 1 clear false positive across 3.18 M lines of real code.
  • Coverage subdirectory refinement, narrowed after review so an empty, truncated or stale report still exits 2 rather than passing quietly.
  • Supply chain: actions pinned by commit SHA, --locked release builds, lockfile freshness checked in CI, deny.toml with a reviewed allow list, SBOM attached, least-privilege workflow permissions.
  • Cache entries older than 30 days are swept, so the new location cannot grow without bound.

Verification

  • 875 workspace tests; clippy -D warnings, fmt, MSRV 1.96 clean.
  • The original attack and every repro from four prior rounds re-run against a release build.
  • Determinism: byte-identical output across worker counts and cold/warm/no-cache, including with a poisoned cache in the tree and with the cache directory under the scan root.
  • Fingerprint stability: a baseline written by the installed 1.3.0 binary still suppresses under this build.
  • Adversarial review found three blockers in these fixes (untracked files breaking a clean checkout, the relocated cache being walked when it fell under the scan root, and the coverage refinement passing unevaluable reports); all fixed with tests.
  • Test fixtures for vendor credential formats are assembled at run time rather than spelled in source, so neither our scanner nor anyone else's flags this repository (GitHub push protection caught the first attempt).

…ses, report symlinks (1.4.0)

BREAKING: the incremental cache now lives in a per-user cache directory
(XDG_CACHE_HOME/HOME on unix, LOCALAPPDATA on Windows) instead of
.siloscan/cache inside the scanned tree. An in-tree cache is never read.
BREAKING: duplicate-block findings are off by default; duplication
metrics are unchanged. Enable with [duplication] report_blocks = true or
by loading a duplication gate rule.

- a cache committed into a repository can no longer suppress findings:
  the poisoning attack worked against 1.3.0 through an archive extracted
  to a predictable path, because tar preserves the 0600 mode the salt
  provenance check relied on
- THIRD-PARTY-LICENSES generated from the linked dependency set and
  shipped in every release archive, plus an SBOM and SHA256SUMS
- symlinks are recorded in skipped, distinguishing targets inside the
  scan root from targets outside it; --follow-symlinks opts in and still
  refuses to leave the tree
- --min-severity filters reported findings and is recorded in the report
- generic secret rules: credentialed URLs, AWS secret access keys, and
  high-entropy assignments
- scan warnings reach SARIF via toolExecutionNotifications
- actions pinned by commit SHA, --locked release builds, deny.toml,
  least-privilege workflow permissions
- cache entries older than 30 days are swept
@aksOps
aksOps merged commit 754e3f5 into main Aug 4, 2026
9 checks passed
@aksOps
aksOps deleted the fix/v1.4.0-final branch August 4, 2026 18:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant