Skip to content

feat: Security-First Linting — opt-in, isolated, verified tool provisioning - #11

Merged
decksoftware merged 6 commits into
mainfrom
feat/security-first-provisioning
Jun 3, 2026
Merged

feat: Security-First Linting — opt-in, isolated, verified tool provisioning#11
decksoftware merged 6 commits into
mainfrom
feat/security-first-provisioning

Conversation

@decksoftware

Copy link
Copy Markdown
Owner

Security-First Linting: opt-in, isolated, integrity-verified tool provisioning

Synthesizes the three parallel analyses (ChatGPT honest positioning + Qwen Security-First Linting + Qwen "Trio de Ouro" auto-install). CSReview's fidelity — results faithful to the objective, far fewer false positives — comes from running real best-in-class security tools and corroborating them with the heuristic detector (tool + detector ⇒ CONFIRMED). So CSReview can now provision the tools it needs, made safe.

Design doc: csreview/docs/security-first-provisioning-design.md.

Safety model (auto-download is the highest-risk capability — designed so it can't be one)

  • Opt-in + informed consent: nothing downloads unless you pass --provision-tools; the CLI first prints what it will install, from where, and that it's isolated.
  • Pinned official hosts only: assertOfficialUrl (HTTPS + allow-listed host); URLs derived only from each tool's pinned official repo.
  • Integrity: SHA-256 verified against the vendor checksums file before the artifact is made executable/run; mismatch or non-official host ⇒ rejected, never executed.
  • Isolated: installs only into a gitignored .csreview/bin/; never global, never sudo, never a project dependency, never modifies the audited source.
  • Fail-open: PATH → cache → opt-in download → isolated install → lower-confidence Agent-Only. Never crashes, never blocks an offline scan.

What's included

  • src/provision.js — registry (gitleaks/gosec/trivy pinned to official repos) + ensureTool cascade + checksum/host guards (all I/O injected).
  • src/securityTools.js — normalizers (Gitleaks/Bandit/gosec/Trivy → canonical Finding, secrets always redacted) + injectable runners + gatherSecurityToolFindings.
  • src/provisionRuntime.js — real network/fs/exec I/O + makeSecurityToolGatherer.
  • runAnalysis opt-in gatherSecurityTools hook → findings merged into dedup ⇒ corroboration CONFIRMED.
  • CLI --provision-tools flag + consent banner + per-tool ran/skipped reporting.
  • Docs: README landing declaration (tools + opt-in install, official sources) + SKILL.md policy refinement; scanner ignores .csreview/.

Verified official sources (researched 2026-06-03)

semgrep/semgrep · google/osv-scanner · gitleaks/gitleaks · aquasecurity/trivy · PyCQA/bandit · securego/gosec.
Caveats acted on: fabpot/local-php-security-checker is archived → prefer composer audit; eslint-plugin-security is FP-prone → weighted lower / supplementary (not in the first wave).

Tests

+30 tests (provision foundation, normalizers + redaction, runtime I/O host-pin, gather fail-open, runAnalysis corroboration ⇒ CONFIRMED). 156/156 pass · lint clean · typecheck exit 0. Real downloads are never performed in tests (all I/O injected); the real path runs only on user opt-in.

Not in this PR (honest scope)

First wave wires Gitleaks/Bandit/gosec/Trivy; eslint-plugin-security / composer audit / brakeman / ruff remain in the recommendation matrix on the same foundation. Do not merge before review (security-sensitive capability).

🤖 Generated with Claude Code

dev-ecd-dm and others added 5 commits June 3, 2026 07:36
…ng foundation

Security-First Linting groundwork (design + the security-critical core), per the
synthesis of the ChatGPT (honest positioning) + Qwen (security-first linting /
auto-install) analyses. This commit lands ONLY the foundation; tools are wired in
follow-up waves.

- docs/security-first-provisioning-design.md: the brainstorm/design (safety
  model, verified official sources, research caveats, implementation order).
- src/provision.js: ensureTool() fail-open cascade (PATH -> isolated cache ->
  opt-in download -> "unavailable"), with the security guarantees that make
  auto-download safe for a security tool:
  * OPT-IN: nothing downloads unless `provision: true` (CLI will gate this behind
    --provision-tools + informed consent).
  * PINNED OFFICIAL HOSTS: assertOfficialUrl() requires HTTPS + an allow-listed
    official host; URLs are derived only from a tool's pinned official repo.
  * INTEGRITY: SHA-256 verified against the vendor checksums file BEFORE the
    artifact is installed/executed; mismatch or non-official URL => rejected,
    never executed.
  * ISOLATED + FAIL-OPEN: installs only into a caller-provided cache
    (<project>/.csreview/bin, gitignored), never global/sudo; degrades to lower
    confidence rather than crashing.
  All network/fs/exec is injected. TOOL_REGISTRY pins gitleaks/gosec/trivy to
  their official repos (gitleaks/gitleaks, securego/gosec, aquasecurity/trivy).

tests: test/provision.test.js (13) — host pin, checksum verify, platform asset
matching, and every cascade branch incl. checksum-mismatch + non-official-URL
rejection (install never called).

Gate: 139/139 tests, lint clean, typecheck exit 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…/Trivy

Pure normalizers over each tool''s JSON output -> canonical Finding schema
(confidence TOOL-ONLY; engine dedup promotes to CONFIRMED when a tool corroborates
the heuristic detector -> cuts false positives). Secret values are ALWAYS redacted
(Gitleaks Secret/Match, Trivy Secrets.Match, and Bandit B105-B107 / gosec G101
code lines) so CSReview never copies a raw secret into its report.

runSecurityTool() takes an injected exec (no process spawned in tests) and runs
each tool read-only with a SECURITY-ONLY profile + JSON output; the audited
rootDir is passed only as an argv element (no shell interpolation). Fail-open.

tests: test/security-tools.test.js (8) — mapping + CWE + redaction (raw secret
never present) for all four tools, empty/garbage tolerance, runner fail-open, and
the no-shell-interpolation guard.

Gate: 146/146 tests, lint clean, typecheck exit 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…roboration -> CONFIRMED)

- selectSecurityTools(): picks candidates by stack (always gitleaks + trivy;
  bandit for Python; gosec for Go).
- gatherSecurityToolFindings(): orchestrates ensure(provision/resolve) + run per
  candidate, aggregating findings; fully fail-open per tool (injected ensure/run).
- runAnalysis(): new optional options.gatherSecurityTools hook. When provided
  (the CLI composes the real one behind --provision-tools), its findings are
  merged into the dedup so a tool secret/finding at the same file:line:cwe as the
  heuristic detector is promoted to CONFIRMED — the false-positive-reduction
  mechanism. Absent => no-op, default scan unchanged. result.securityTools lists
  per-tool availability/source.

tests: selectSecurityTools, gather aggregation + fail-open, and a runAnalysis
integration proving detector+gitleaks corroboration yields CONFIRMED.

Gate: 150/150 tests, lint clean, typecheck exit 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ime I/O

src/provisionRuntime.js: the real (network/fs/exec) I/O for ensureTool, all
composable + injectable — probeOnPath, cacheLookup, fetchLatestRelease (official
GitHub API), downloadBuffer/downloadText (assertOfficialUrl enforced before
fetch), installFromArchiveAsync (extract -> isolated .csreview/bin -> chmod, writes
only under cacheDir + a temp dir), and makeSecurityToolGatherer() composing
ensureTool + runSecurityTool for runAnalysis.

CLI: --provision-tools is the opt-in gate. When set, CSReview prints an INFORMED
CONSENT banner (which tools, the official sources, SHA-256 verification, isolated
.csreview/bin, never global) before anything downloads, then wires the gatherer
into runAnalysis and reports per-tool ran/skipped status. Without the flag the
suite is off and the default scan is unchanged. provision.js awaits onPath/cache
so real async probes work (no-op for sync test mocks).

tests: test/provision-runtime.test.js (6) — version probe, cache lookup,
release mapping/fail-open, non-official-host refusal before fetch, isolated
install, and a gatherer run-on-PATH with provision:false never hitting the API.

Gate: 156/156 tests, lint clean, typecheck exit 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nding (+ SKILL honesty)

Owner requirement: the GitHub landing must tell users these tools are needed and
that CSReview can install them (opt-in) for a faithful, low-false-positive result.

- README: new prominent "Security tooling CSReview relies on (and can install for
  you)" section ABOVE the SKILL mirror (renders first on GitHub), listing every
  tool with its OFFICIAL source, the opt-in (--provision-tools) install behavior
  (official source + SHA-256 verify + isolated gitignored .csreview/bin + fail
  open), how to decline, and the honest positioning (local security alignment,
  not a pentest; read-only on source).
- SKILL.md: new "Opt-in security-tool provisioning" paragraph; install policy
  refined so provisioning into an isolated, gitignored .csreview/ is explicitly
  allowed with consent while never installing as a project dependency / globally
  / modifying the audited source. README mirror re-synced (equality test green).
- scanner.js: ignore **/.csreview/** so a scanned project's provisioned tool
  cache is never itself scanned. .gitignore: add .csreview/.

Gate: 156/156 tests, lint clean, typecheck exit 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@gitguardian

gitguardian Bot commented Jun 3, 2026

Copy link
Copy Markdown

⚠️ GitGuardian has uncovered 2 secrets following the scan of your pull request.

Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.

🔎 Detected hardcoded secrets in your pull request
GitGuardian id GitGuardian status Secret Commit Filename
33603145 Triggered Generic Password e9975dc csreview/test/security-tools.test.js View secret
33594677 Triggered Generic Password 9b9deb7 csreview/test/security-tools.test.js View secret
🛠 Guidelines to remediate hardcoded secrets
  1. Understand the implications of revoking this secret by investigating where it is used in your code.
  2. Replace and store your secrets safely. Learn here the best practices.
  3. Revoke and rotate these secrets.
  4. If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.

To avoid such incidents in the future consider


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

…ing path

Adversarial review verdict was FIX-FIRST (no Critical; core verify-before-exec,
opt-in gating, and host-pin confirmed sound). Remediated:

H1 (zip-slip / arbitrary write via assetName, Windows): installFromArchive used
assetName.split('/').pop() which kept backslashes, letting a hostile official
asset name escape the temp dir on Windows. Now uses path.basename (strips both
separators) + asserts the archive path stays inside the temp dir. Temp dir is now
mkdtemp (unpredictable) instead of a guessable name (N2).

H2 (PowerShell command injection in defaultExtract): the Windows .zip branch
interpolated the archive path into a PowerShell -Command string. Replaced with a
pure-argv `tar -xf` (Windows 10+ bsdtar extracts zip) — no shell, no
interpolation anywhere on the extraction path.

M1 (host pin not re-asserted across redirects): downloads now follow redirects
MANUALLY and re-run assertOfficialUrl on EVERY hop (bounded), so an official host
cannot 3xx to an arbitrary host. + size cap (L2, MAX_DOWNLOAD_BYTES).

M2 (bandit advertised but unreachable): bandit added to the registry as a
PATH-only tool (PyPI, not a GitHub single binary) — used if already installed,
never auto-downloaded; ensureTool returns a `pip install bandit` hint otherwise.
CLI banner clarified (auto-installed: Gitleaks/Trivy/gosec; used-if-installed:
Bandit). README/design doc aligned.

L1 (secret in tool free-text): gitleaks/trivy-secret name/description now run
through scrubSecrets() so a raw secret can never reach a finding field even if a
tool echoes it into rule text. N3: findBinary uses lstat + skips symlinked
archive members. N4: provisioning writes .csreview/.gitignore so the audited
project's cache is gitignored. L3: design-doc consent claim softened to match the
implemented banner.

tests: +4 (H1 containment, M1 redirect re-assert, M2 bandit pathOnly, L1 scrub).
Gate: 160/160 tests, lint clean, typecheck exit 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@decksoftware
decksoftware merged commit a909db9 into main Jun 3, 2026
11 of 12 checks passed
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