From 2000f5682764406ec4e62b35fc1367d7f1299202 Mon Sep 17 00:00:00 2001 From: Remi Antczak <140408771+remigiusz-antczak@users.noreply.github.com> Date: Fri, 30 Oct 2026 13:32:13 +0100 Subject: [PATCH] release: 1.625.0 (stamped at land time) Co-Authored-By: Claude Opus 5.5 --- .claude-plugin/plugin.json | 2 +- .claude/skills/agentic-delivery/SKILL.md | 2 +- .claude/skills/agentic-delivery/VERSION | 2 +- .claude/skills/deep-code-review/SKILL.md | 2 +- .claude/skills/deep-code-review/VERSION | 2 +- .claude/skills/idea-critic/SKILL.md | 2 +- .claude/skills/idea-critic/VERSION | 2 +- .codex-plugin/plugin.json | 2 +- CHANGELOG.md | 62 +++++++++++++++++++ SHA256SUMS | 12 ++-- .../affected-runs-cheap-whole-tree-gates.md | 2 - changelog.d/app-pentest-openapi-deadline.md | 2 - changelog.d/committed-data-fixture-label.md | 5 -- changelog.d/guard-rail-register.md | 4 -- .../parity-differ-focus-gate-loader.md | 2 - changelog.d/parity-differ-focus-gate-route.md | 2 - changelog.d/perpr-shards.md | 2 - changelog.d/review-checks-offline-optouts.md | 8 --- changelog.d/semgrep-js-date-gcm.md | 6 -- changelog.d/trufflehog-retry-and-noise.md | 8 --- changelog.d/trufflehog-secrets-step.md | 8 --- gemini-extension.json | 2 +- 22 files changed, 77 insertions(+), 64 deletions(-) delete mode 100644 changelog.d/affected-runs-cheap-whole-tree-gates.md delete mode 100644 changelog.d/app-pentest-openapi-deadline.md delete mode 100644 changelog.d/committed-data-fixture-label.md delete mode 100644 changelog.d/guard-rail-register.md delete mode 100644 changelog.d/parity-differ-focus-gate-loader.md delete mode 100644 changelog.d/parity-differ-focus-gate-route.md delete mode 100644 changelog.d/perpr-shards.md delete mode 100644 changelog.d/review-checks-offline-optouts.md delete mode 100644 changelog.d/semgrep-js-date-gcm.md delete mode 100644 changelog.d/trufflehog-retry-and-noise.md delete mode 100644 changelog.d/trufflehog-secrets-step.md diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index cbd7f1cbb..79a790ff4 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -2,7 +2,7 @@ "name": "perun", "description": "Perun: a suite of skills for building with AI agents. Deep code review, token and cost tools (spend reports, auto-compact with handoff, model-tier guidance), gated agentic delivery (merge train, lanes, destructive-command guard, auto-update), an idea critic, and product, growth, positioning, business-ops and output-safety specialists.", "skills": "./.claude/skills", - "version": "1.624.0", + "version": "1.625.0", "author": { "name": "deep-code-review contributors" }, diff --git a/.claude/skills/agentic-delivery/SKILL.md b/.claude/skills/agentic-delivery/SKILL.md index 1ad6e9410..8b321836f 100644 --- a/.claude/skills/agentic-delivery/SKILL.md +++ b/.claude/skills/agentic-delivery/SKILL.md @@ -8,7 +8,7 @@ description: >- license: MIT metadata: author: deep-code-review contributors - version: "1.624.0" + version: "1.625.0" --- # Agentic delivery diff --git a/.claude/skills/agentic-delivery/VERSION b/.claude/skills/agentic-delivery/VERSION index 544107d45..1bb512367 100644 --- a/.claude/skills/agentic-delivery/VERSION +++ b/.claude/skills/agentic-delivery/VERSION @@ -1 +1 @@ -1.624.0 +1.625.0 diff --git a/.claude/skills/deep-code-review/SKILL.md b/.claude/skills/deep-code-review/SKILL.md index 0809f8002..02c145f9f 100644 --- a/.claude/skills/deep-code-review/SKILL.md +++ b/.claude/skills/deep-code-review/SKILL.md @@ -8,7 +8,7 @@ description: >- license: MIT metadata: author: deep-code-review contributors - version: "1.624.0" + version: "1.625.0" --- # Deep Code Review diff --git a/.claude/skills/deep-code-review/VERSION b/.claude/skills/deep-code-review/VERSION index 544107d45..1bb512367 100644 --- a/.claude/skills/deep-code-review/VERSION +++ b/.claude/skills/deep-code-review/VERSION @@ -1 +1 @@ -1.624.0 +1.625.0 diff --git a/.claude/skills/idea-critic/SKILL.md b/.claude/skills/idea-critic/SKILL.md index 7f1c7c3ab..bebe09e62 100644 --- a/.claude/skills/idea-critic/SKILL.md +++ b/.claude/skills/idea-critic/SKILL.md @@ -8,7 +8,7 @@ description: >- license: MIT metadata: author: deep-code-review contributors - version: "1.624.0" + version: "1.625.0" --- # Veles: the evil twin (idea-critic) diff --git a/.claude/skills/idea-critic/VERSION b/.claude/skills/idea-critic/VERSION index 544107d45..1bb512367 100644 --- a/.claude/skills/idea-critic/VERSION +++ b/.claude/skills/idea-critic/VERSION @@ -1 +1 @@ -1.624.0 +1.625.0 diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index daec1b442..0b42584d5 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "perun", - "version": "1.624.0", + "version": "1.625.0", "description": "Perun: a skill suite for AI-agent teams: deep code review, token and cost tools, gated delivery, an idea critic and product specialists.", "skills": "./.claude/skills", "interface": { diff --git a/CHANGELOG.md b/CHANGELOG.md index b370bd0f0..8fed968e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,68 @@ All notable changes to this repository are documented here. Format loosely follows Keep a Changelog; versioning follows Semantic Versioning. +## [1.625.0] — 2026-10-30 + +### Fixed +- `scripts/run-ci-tests.sh --affected` (and `--list-affected`) now also runs the whole-tree gates of CI's cheap job that no test names, listed in its `always=` line: `scripts/test-mktemp-tmpdir.sh`, `scripts/check_section_pointers.py` and `.claude/skills/deep-code-review/scripts/derived_numbers.py`. Selection by name never reached them, so an affected run could pass 59/59 while the cheap job failed on a line ending in a bare `mktemp`. They run first on every non-empty affected run (a few seconds in total, so a violation fails before the suites start), appear in the printed selection and in the receipt claim, and a changed one is not reported UNCOVERED. Test: `scripts/test-run-ci-tests-always.sh` (hermetic fixtures; it fails against the runner without the list). + +### Changed +- `scripts/app_pentest.sh --openapi`: a spec URL now has a 10 s total deadline for the whole read (connect, headers and body), not only a 10 s limit on each socket operation, so a host that answers one byte at a time can no longer hold the run open; the refusal reads `cannot read spec: took over 10 s`. When schemathesis reports operations that errored, the output gains one low lead, `operations_errored` ("Operations errored and were not tested: N"); `tools.schemathesis` and `openapi.errored` keep their values. `scripts/test_app_pentest.sh` now also pins the `ST102` medium severity and the deadline against a silent host and a byte-at-a-time host. + +### Added +- Fixture signals on `committed-data` rows. Each file row of `committed_personal_data.py` (the `committed-data` step of `review_checks.sh`) now ends with counts only, never an address or a domain: how many of the file's addresses have a placeholder-style local part (john, jane, test, user, foo and similar words, alone or back to back, digits and `._%+-` ignored), and how many are on a reserved domain (example.com, .net and .org, plus .test, .example, .invalid and .localhost), out of every address in the file with the reserved names counted back in. The row starts with `likely fixture:` only when the path is a test, spec, fixture or mock path (a whole directory name such as `tests/`, `__mocks__/` or `testdata/`, or a word of the file name such as `people.test.ts` or `mock-users.json`) and at least half of the addresses are placeholder-style or on a reserved domain. docs, examples and samples are not test paths, because real data lives there too. Why: in an audit of 43 repositories the check gave 38 single-file rows; 30 were test or mock data with invented addresses and 8 were real data, and every row needed manual triage. +- The label is a hint, not a verdict, and recall is unchanged. No row is dropped, no threshold or exit code changed, and the first scan, which decides the count and the threshold, is untouched; the second scan (reserved names included, so the signals do not depend on `--include-reserved`) runs only for a file that is already flagged. The repository-total row and the `not-checked` rows carry neither the counts nor the label. Measured on the 38 single-file rows of that audit, the label marks 4 of the 30 fixture rows and none of the 8 real-data rows, and none of the 33 rows of a known real export elsewhere in the audit; most of those fixtures use invented names at realistic domains, outside the word list, so they keep a plain row. `review_checks.sh` still adds no `likely-fixture` hint or tag to these rows, because a real address in a fixture is still a leak. Known ceiling, stated in the script: a test file that mixes real-looking addresses among many reserved ones can carry the label. +- New test `scripts/test_committed_personal_data_fixture_label.py` (13 tests, synthetic addresses only: `.zz` and example.com names, with obviously invented names for the real-looking ones; registered in `scripts/ci-tests.d/committed-personal-data-fixture-label.sh`). A test-path file of placeholder addresses gets the label, a test-path file of real-looking addresses does not (at 5 of 12 placeholder-style, 6 of 12 labelled), a non-test path never does, the row set is unchanged, and no address, domain or local-part word reaches the output. 11 of the 13 fail on the previous release; the other 2 pin the unchanged strict exit code and repository-total row. The existing suites keep every old expectation: their helper strips the new clause from rows, which is the evidence that the rows themselves are the same. +- size-budget-raise: .claude/skills/deep-code-review/references/method-situational.md 44122→44231 the analyzer list says a committed-data row ends with counts and leads likely fixture: for a test path + +### Added +- Every guard and hook script under `.claude/skills/*/scripts/` now records who owns it, why it exists and when it is next reviewed, as one header line: `# rail: owner=, why=, review=YYYY-MM-DD`. 42 scripts carry it: those named `*_guard`, `*_gate`, `*_cap` or `*_hook`, those a shipped `templates/*.settings.json` runs as a hook, and `stop_reminder.py`. The first review date is 2027-01-30. Scripts that only the git pre-push template runs (for example `flake_holder.py`) are outside the required set; adding the line opts one in. +- `scripts/ci-gates.sh rails ` fails on a rail with no header, a missing or empty field, or a review date that is not a real calendar day, so a new guard cannot land unowned. `scripts/test-rails.sh` pins it with 46 cases (all red against a `ci-gates.sh` without the subcommand) and `scripts/ci-tests.d/rails.sh` runs both in CI (refs #1592). +- `scripts/ci-gates.sh rails --audit ` lists the rails past their review date for the owner to confirm (bump `review=`) or retire; it never removes one, and a past date does not fail the plain gate. It prints no firing counts: no log of rail firings is shared (`reply_cap.py`, `handback_cap.py` and `file_finding.py` each keep a private one), so no rail is called never-firing until a shared log exists. + +### Fixed +- `parity_differ.py` now loads agentic-delivery's `focus_gate.py` on every Python it supports. `_focus_gate()` ran the module without registering it in `sys.modules`, so its dataclasses failed to build on Python 3.12 and 3.14 (the two versions checked) and the loader returned `None`, while 3.15 loaded it. The accept-file owner check therefore took the inline `git log` fallback on 3.12 and 3.14 but `focus_gate`'s commit-authorship trust on 3.15: trust semantics, including whether `--accept-rev` is required, changed with the interpreter. The loader now registers the module before executing it, reuses an already-loaded `focus_gate` only when it is the same file, and drops the entry when the import fails; a missing or broken `focus_gate.py` still falls back as before. Test: `scripts/test_parity_differ_focus_gate.py` (`RealFocusGateLoad`) calls the real `_focus_gate()` from a fresh-interpreter state and runs the owner-trusted, agent-authored and no-owner cases through it; it fails on the previous loader under 3.12 and 3.14 and passes on 3.12, 3.14 and 3.15. + +### Fixed +- `parity_differ.py` accept-file trust through `focus_gate.load_record` (the route used wherever `focus_gate.py` imports): the call passed a keyword `load_record` does not have (`commit_only`; commit-only trust is already its default), so it raised `TypeError`, and it passed `resolve_owners()`'s whole `(owners, warning)` tuple as the owner list, so once the keyword was gone every commit, the owner's included, still read as "not the owner" and a missing owner reported that instead of "no owner email configured". It now passes the owner list. CI's `--selftest` never reached the route: `_focus_gate()` loads `focus_gate.py` without registering it in `sys.modules`, so on Python 3.11 to 3.14 (3.15 imports it; no other version was checked) its dataclasses fail to build and the loader falls back; that import is unchanged here. Test: `scripts/test_parity_differ_focus_gate.py`, which imports `focus_gate` normally and hands it to `parity_differ`, so the route runs on every interpreter (it fails on the previous code with the `TypeError`). + +### Changed +- CI job `perpr` is now a matrix of 3 legs (`shard: [1, 2, 3]`, `fail-fast: false`) instead of one serial job, because it was the critical path of every PR: it took 19.0 minutes in CI run 41028470595 (2026-10-30). The slowest leg is predicted at about 7.1 minutes, computed from per-test seconds and not measured on CI. Leg `i` runs only the `scripts/ci-tests.d` registrations that `scripts/ci-tests-shard.sh` lists for `SHARD=i/3`, the spelling `selftests` already uses: sorted in the C locale, `*.sh` and `*.bg.sh` numbered separately so the slow background tests spread over the legs, shard `i` takes the numbers equal to `i-1` modulo 3. The 3 slices are disjoint and cover every registration. The helper fails closed (a malformed `SHARD` exits 2, an empty slice exits 1), the tests themselves never see `SHARD`, and `gates` is unchanged: it still needs `perpr`, and a matrix job fails unless every leg succeeds. Contract for test authors (`CONTRIBUTING.md`): a registration must not rely on another registration having run first. Inserting a registration moves every later one along by one position, so a leg can drift by tens of seconds; a weights file for the split is the upgrade if a leg nears 8 minutes. `scripts/test-ci-shards.sh` now reads only the `selftests` shard count, and `scripts/test-conflict-free-prs.sh` copies the helper into its fixture and runs the steps with `SHARD` unset. Test: `scripts/test-perpr-shard.sh` (the real tree's slices are disjoint and complete, a dotfile in `scripts/ci-tests.d` is not counted (the step globs skip it), a bad `SHARD` fails closed, and `ci.yml`'s own start and per-PR steps run as each leg over a fixture: every registration runs once, `SHARD` does not leak, a failing serial or background test fails only its own leg, and `gates` still needs `perpr`). + +### Fixed +- `review_checks.sh` says its scanners run offline, but three of them phoned home. Run under a network-denying sandbox with their exact argv on a one-file fixture, trivy 0.76.1 `fs` contacted `check.trivy.dev` (telemetry, even with `--offline-scan`), grype 0.121.0 contacted `toolbox-data.anchore.io` (its own-version check, even with `GRYPE_DB_AUTO_UPDATE=false`) and checkov 3.3.30 contacted `pypi.org` (its own-version check, even with `--skip-download`). Each is now switched off by an environment variable and not a flag, so an older tool ignores it instead of crashing on an unknown flag: `TRIVY_DISABLE_TELEMETRY=true` (set once for the trivy block, so the secret-only rerun carries it too), `GRYPE_CHECK_FOR_APP_UPDATE=false` and `CKV_SKIP_PACKAGE_UPDATE_CHECK=true`. With the variables set, the three runs contact no host. +- The other scanners were probed the same way and contacted no host: zizmor, actionlint, bandit, hadolint, gosec, gitleaks, trufflehog, osv-scanner on a warm offline DB, and semgrep with the bundled rules plus a warm `p/default` snapshot. semgrep with the registry pack contacts `semgrep.dev` by design (a warm snapshot or `REVIEW_SEMGREP_REGISTRY=0` avoids it); that was already documented and is unchanged. + +### Changed +- `tool-hub.md`: the Network line names the three opt-outs, the versions they were found on and the denied hosts. +- `scripts/test-review-checks-tool-hub.sh` grows from 101 to 108 checks and `scripts/test-scanner-offline-db.sh` from 19 to 20. The stubs now log the opt-out environment they start with (`GRYPE_*`, `CKV_*`, `TRIVY_*`), and new checks assert it for grype (both variables, the DB one was unasserted until now), checkov and trivy, the trivy secret-only rerun included, and that `tool-hub.md` names each. Both suites unset the three variables first, so a developer's own export cannot satisfy them. Seven of the eight new checks fail against the previous `review_checks.sh`; the eighth pins the existing `GRYPE_DB_AUTO_UPDATE=false`. +- size-budget-raise: .claude/skills/deep-code-review/references/tool-hub.md 9788→10152 the three environment opt-outs, why they are variables and the hosts they stop + +### Added +- Two bundled JavaScript/TypeScript semgrep rules in `templates/semgrep-taint/js.yml`, for bug classes that hand audits found and the bundled rules missed. `review_checks.sh` already loads the whole template directory, so both run in the default review with no wiring change. +- `perun.js.date-parse-rollover` (INFO, low): a date accepted only because `Number.isFinite` or `isNaN` of an inline `Date.parse(x)`, `new Date(x)` or `new Date(x).getTime()` passed. V8 reads an impossible day such as 2026-02-30 as 2 March instead of rejecting it. The message names the fix: format the parsed date back with `toISOString()` and reject the input unless it matches. A `.toISOString()` call anywhere in the enclosing function counts as that round trip and silences the rule, and test paths are skipped like the credential rules. A check on a variable bound first (`const d = new Date(x); isNaN(d)`) is deliberately not flagged: most of those hits were display, age and TTL helpers, so they cost more precision than they bought recall. Other known ceilings: a function that also stamps an unrelated `toISOString()` is missed, component compares such as `getUTCDate()` are not recognised, and an INFO hit can land on code that parses a date without validating it. +- `perun.js.gcm-no-auth-tag-length` (WARNING, medium): `createDecipheriv` with an `*-gcm` cipher and no `authTagLength` option, so Node accepts an authentication tag shorter than 16 bytes (a 4-byte tag passes; checked on Node 24). That weakens integrity only when the tag length is attacker-controlled, for example when the tag is whatever follows the ciphertext in the message, and the message says so. It names the fix: pass `{ authTagLength: 16 }` as the fourth argument. The cipher may be a string literal or a `const` bound to one; a non-literal cipher name and an options argument that is not an object literal are skipped, not guessed. +- New test `scripts/test_semgrep_js_date_gcm.py` (registered in `scripts/ci-tests.d/semgrep-js-date-gcm.sh`): fictional fixtures annotated `// ruleid:` and `// ok:` in `.js` and `.ts` copies (28 expected hits), with the round-trip form, the `authTagLength: 16` form and a variable-bound check as the clean cases, plus a copy under a test path that must stay clean. The live part needs semgrep and prints SKIP without it, and CI installs none, so offline structural asserts on the YAML back it up: the severities, the named fix, the cipher regex (semgrep anchors `metavariable-regex` at the start, so `gcm$` alone matches nothing), both round-trip suppressions, the test-path exclusion shared with the credential rules, the `authTagLength` exemptions, no variable-bound alternative, and the attacker-controlled-tag wording. Against the previous release's `js.yml` the live check misses all 28 expected hits; seven single-edit mutants of the new rules (no round-trip suppression, no `authTagLength` exemption, an unanchored cipher regex, the wrong severity, no test-path exclusion, a variable-bound form added back, the old overstated message) each fail the test both with and without semgrep installed. +- Precision, measured read-only on audited code, counts only. A true positive (TP) is one reviewer's reading of three lines of context: the check is the only gate deciding whether an externally supplied date string is accepted, in non-test code. Date rule: 40 files pre-selected for an inline `isNaN`/`isFinite` of a date parse (26 distinct file contents, identical copies across clones counted once). A first version that also flagged variable-bound checks gave 42 hits in 34 of the 40 files (34 distinct hits, 23 TP, 68%); this version gives 38 hits in 34 files (30 distinct, 23 TP, 77%), the 4 dropped hits being variable-bound and none a TP. 7 of the kept hits pair a format regex with the parse check on the same value, the clearest TPs; the 7 kept false positives are a bar-highlight computation, two test or check scripts, a coercion helper, an age calculation, a token-payload shape check and a Retry-After header. On 60 other files that contain a date parse and an `isNaN`/`isFinite` anywhere, the first version gave 32 hits in 26 files and this one 0; about 5 of those 30 distinct dropped hits were real input validators (throwing or returning an error on NaN), so the narrowing costs some recall. One audited module that validated a calendar date with an inline `Number.isFinite(Date.parse(...))` plus a format regex: 1 hit on the revision before its fix, 0 on the revision after it (which round-trips with `toISOString()`). Gcm rule: of 10 JavaScript files that call `createDecipheriv` across the audit clones, 1 hit, a TP on the variant without `authTagLength`; the six copies of the fixed variant and the other files stay clean. + +### Fixed +- trufflehog step: one bad file no longer voids a whole 200-file call. `--fail-on-scan-errors` makes a single unreadable file fail the entire call with no output, so the step reported `not run: trufflehog` and every finding in the other 199 files was lost. A failed call (a crash, a timeout or an unreadable file) is now retried file by file: files that pass alone keep their rows, each file that still fails is one `not-checked` row naming it, and the run is `partial` (`trufflehog(errors)` in the closing line and the record) instead of `not run`. When no file passes alone the tool itself failed, so it is `not run` as before, with the first call's reason. A call that succeeds is never retried, and neither is a one-file call (the retry would be the same call). The retry gives up after three failed single-file runs while none has passed (that chunk is `not run`, with the first call's reason), so a tool that crashes or hangs on every call costs three extra runs per chunk and not up to 200 timeboxed ones; the price is that three or more unreadable files ahead of the first good one also read as `not run` (loud, never clean). + +### Changed +- trufflehog now follows gitleaks' noise rules. When the repo has no `.gitleaks.toml` (gitleaks then uses `templates/gitleaks.toml`), the template's skip paths (`.history/`, `fixtures/`, `testdata/`, generated registry JSON, lockfiles) are dropped from trufflehog's file list and counted on stderr: `review_checks: trufflehog template skipped N changed file(s) ...`, in the wording of gitleaks' line. The filter runs on the file list and not through `--exclude-paths`, because on trufflehog 3.99.7 that flag is ignored for files named on the command line (checked on the real binary), so passing it would change nothing. A diff made only of those paths leaves trufflehog no work: it is not called and is not listed as wired or as not run. A repo with its own `.gitleaks.toml` is scanned in full, as before. +- `tool-hub.md` records that `.gitleaksignore` fingerprints and `.gitleaks-allow` hashes do not apply to trufflehog and that an inline `trufflehog:ignore` comment does (checked on trufflehog 3.99.7), and lists trufflehog among the tools that can be `partial`. +- `scripts/test-review-checks-trufflehog.sh` grows from 40 to 67 checks: the stub `trufflehog` gains `TF_BAD` (a call naming that file fails with no output) and `TF_HANGN` (a call naming that many files hangs), and the suite covers the retry, its cap with a tool that crashes or hangs on every call, a 450-file run with one bad file, the template filter and, with the real binary, an unreadable file, `.gitleaksignore`, the inline ignore comment and the template skip. Its fixture path moved from `tests/fixtures/` to `tests/`, since `fixtures/` is now a skipped path. `scripts/test-review-checks-gitleaks-template.sh` matches its skip-count line on `gitleaks template skipped`, because the trufflehog line now has the same wording. +- size-budget-raise: .claude/skills/deep-code-review/references/tool-hub.md 8881→9788 the trufflehog noise-rule, suppression and per-file retry rule with its cap, and trufflehog under partial + +### Added +- trufflehog secrets step in the review hub. `review_checks.sh` now runs trufflehog right after gitleaks. The hub never ran it, even when it was installed, so a second secret detector never contributed. The step scans the changed files in diff mode and every tracked file under `--full` (vendored directories are skipped as for every other step), passed as explicit `./path` arguments, 200 per call like semgrep. It runs fully offline: `--no-verification` never contacts a provider (so every finding is unverified) and `--no-update` skips the version check. `--fail-on-scan-errors` turns an unreadable path into a not-run row instead of a silent clean pass. +- No secret value is read, printed or stored. The step asks for `--sarif`, not `--json`, because the default and JSON outputs print the secret (JSON as `Raw`, `RawV2`, `Redacted` and `SecretParts`); the SARIF document holds the detector name, the file, the line and a fingerprint hash. A row is the detector, file and line with a fixed message of ours, and the parser reads only those three fields, so even a hostile document cannot leak a value into a row. `--log-level=-1` keeps trufflehog's own log lines out of the merged output so they cannot corrupt the document. A test plants a canary secret in the fake binary's `--json` output and proves it never reaches stdout, stderr, the record or any file under `TMPDIR`, and that the same witness does catch the canary when the run is switched to `--json`; the real binary gets the same drill on a runtime-built token. +- Same patterns as the other tools. A missing binary gives the `not installed` row (a `not-checked` finding, listed as not run and fixable, with the `brew install trufflehog` hint). The heavy lease is taken like gitleaks' (a full lease degrades to `nice -n 19` and is named in the summary, `REVIEW_LEASE_FULL=skip` gives a not-run row), and `heavy_gate.py` already classed `trufflehog ... filesystem` as heavy; a test feeds it the exact argv the step ran. A crash, a non-SARIF document or a result without a location fails closed as not run, never clean. trufflehog counts in the closing `ran X / wired Y` line and the record, and rows under fixture paths get the same `likely-fixture` hint as gitleaks' rows. +- `provision_tools.sh` now lists trufflehog (`brew install trufflehog`). It was missing from the installer list, so an absent trufflehog could not be `fixable` and `--print` would not have installed it. `method-situational.md` names the step in the analyzer list, and `tool-hub.md` lists trufflehog among the tools routed before the hub and records why the step uses `--sarif`. +- New test `scripts/test-review-checks-trufflehog.sh` (40 checks, 37 on a host without trufflehog; registered in `scripts/ci-tests.d/review-checks-trufflehog.sh`): a fake `trufflehog` on a hermetic PATH covers the finding row, the offline flags, diff mode against `--full`, the 200-file chunks, the fail-closed cases, the missing binary, the three lease states, the doc rows and the no-secret guarantee. A real-binary smoke runs on a two-file fixture only when trufflehog is installed. 35 of the 40 checks fail on the previous release; the other 5 are negative guards (nothing leaked, nothing scanned) or a self-check of the fake, which hold when the step is absent. `test-provision-tools.sh`, `test-semgrep-pack-fallback.sh` and `test-mode-coverage.sh` gain a clean trufflehog stub (in the first two the scanner counts rise by one; in the third a host without trufflehog would otherwise add a second not-checked row), and `test-review-checks-gitleaks-template.sh` now asserts on gitleaks rows only, because the trufflehog step has no counterpart to the gitleaks template's skip list and reports the `.history/`, `fixtures/` and `testdata/` files the template leaves out. +- size-budget-raise: .claude/skills/deep-code-review/references/method-situational.md 44018→44122 the analyzer list names the trufflehog step and what its rows carry +- size-budget-raise: .claude/skills/deep-code-review/references/tool-hub.md 8731→8881 trufflehog joins the routed-before-the-hub list and the no-secret-in-output rule states why the step uses --sarif + ## [1.624.0] — 2026-10-30 ### Added diff --git a/SHA256SUMS b/SHA256SUMS index 1f44af62b..885b0b089 100644 --- a/SHA256SUMS +++ b/SHA256SUMS @@ -20,8 +20,8 @@ ef9e423362b12d0dd73c8e165faf32e9f53c4d4ac215b57c2e34002a88713838 .claude/skills 9521b057ff3db92d1e60923ebd88170713914f7e235ceb45717eb39419daf811 .claude/skills/agentic-ceo/scripts/weekly_receipt.py bb2f134ab9b07e2cf15c4c8a0fad4708bc398144195178960d63b8ea6fd4a2d4 .claude/skills/agentic-delivery/INDEX-scripts.md 5d66d96d22947e0528fd3f337f789d3a8445dfeeb8240b41fe28303aa5a5c8ad .claude/skills/agentic-delivery/INDEX.md -c4fae7dfe8732e83dcdc296ce7a0fd80730ad9c3da97e1c73e7f1494906f15b2 .claude/skills/agentic-delivery/SKILL.md -a411de2d7f2f0fa07d90bb54cac85173edc1c6932ab84778e78c8b439a86a424 .claude/skills/agentic-delivery/VERSION +2298706eaed5b386a927204e2dcd841ba551a97a1d7e1206690d7aeb557e7c41 .claude/skills/agentic-delivery/SKILL.md +4731d34a6bcc1e86310d93ce3e94853be847645fbd12278cda9d76dcf5ab5cd0 .claude/skills/agentic-delivery/VERSION 94144c101f82399eb6c18f688321a00e1c195dd0ab334f3a5daa2543b40ff9f5 .claude/skills/agentic-delivery/evals/evals.json fba7699a2d0836b51b7c838d019b1f0cd20570522a362e8a32b8dacfb911298e .claude/skills/agentic-delivery/evals/triggers.json 39140ac0b40907dc2279bb3234ce1d8ffb4db56e8658c7f89cc370f777df0592 .claude/skills/agentic-delivery/references/cost-quality-guardrails.md @@ -204,8 +204,8 @@ d98f3e4380da3496061ba63be7beebe75017ab4ee1734eda9a3e798e19007b0e .claude/skills 18f20db537ab784460ad33cf218f7a01ac0a172fc38413f14c0e19e1ce6821cc .claude/skills/contribution/scripts/share_learning.py 17958281dce6f131591dcdba808153213372b8ecdc1005c6ff6fdf6cead34da0 .claude/skills/deep-code-review/INDEX-scripts.md 4418e1caba2a95681572ec8471537d8bb33e9ebf0ba84630ca7c04ee0b2adcc9 .claude/skills/deep-code-review/INDEX.md -d351b1527c4565ac9780d18ad2f5622b90b54a67a5e39f52a31be6b9073212ef .claude/skills/deep-code-review/SKILL.md -a411de2d7f2f0fa07d90bb54cac85173edc1c6932ab84778e78c8b439a86a424 .claude/skills/deep-code-review/VERSION +4c60387330f75f5f8ad3396b339c3093879d562ded072a1a79a9b790ccfe5a25 .claude/skills/deep-code-review/SKILL.md +4731d34a6bcc1e86310d93ce3e94853be847645fbd12278cda9d76dcf5ab5cd0 .claude/skills/deep-code-review/VERSION b97d95bc93cc23a8bc689e2e23db261a138367bcbc69d2f8a0452d5c2c6dfb68 .claude/skills/deep-code-review/evals/evals.json 13dbb52e4bf61b1093302e094151def167371d26e7cf67a026f3beeb09d34378 .claude/skills/deep-code-review/evals/triggers.json b84e3a2b88dc189f041784e76b97e68ad879915814e00f4b37cd27513746d6f1 .claude/skills/deep-code-review/references/a11y-aria.md @@ -420,8 +420,8 @@ e951eaa63fd0d1870701d69c89c29ef4fe74dbad9b680ab6ef0a4e993fb8bc20 .claude/skills fd316c63bbf1cc0ea64322d7d923d66692eaf7b545c056367bfecca35146c862 .claude/skills/growth-analytics/references/full-map.md 2f1d7499e9ed7ae59f550b5c00043e465b2a4e885821ad153438e2e33fcccb45 .claude/skills/growth-analytics/scripts/readout_check.py 67e71ec40535ad0b3931c51f3decf323bce809ed4c9586c6d422b7ad5698129f .claude/skills/idea-critic/INDEX.md -aaf3489bff46ea8575763405ef94f7cfbfc80831800b51aa11b5040a3f6bffc1 .claude/skills/idea-critic/SKILL.md -a411de2d7f2f0fa07d90bb54cac85173edc1c6932ab84778e78c8b439a86a424 .claude/skills/idea-critic/VERSION +bf9d2625aa35c3942352f2c1c9d8a6e1a8217e4a074795ecc195765816fe0152 .claude/skills/idea-critic/SKILL.md +4731d34a6bcc1e86310d93ce3e94853be847645fbd12278cda9d76dcf5ab5cd0 .claude/skills/idea-critic/VERSION 9f77d5d6d13c99fd16cd8c398795d8be226d74bae5a5bf65369ed94a59fa56b7 .claude/skills/idea-critic/evals/evals.json 0e1c0db300661a1ab1a427a7157c75e2ed67130ab24d5c6cb02ec3ff9e793f58 .claude/skills/idea-critic/evals/heldout.json d8098c5ced63e8b8608867dd144091049563a0553cf62d27ce05b9d52ac54266 .claude/skills/idea-critic/evals/triggers.json diff --git a/changelog.d/affected-runs-cheap-whole-tree-gates.md b/changelog.d/affected-runs-cheap-whole-tree-gates.md deleted file mode 100644 index 1c806904b..000000000 --- a/changelog.d/affected-runs-cheap-whole-tree-gates.md +++ /dev/null @@ -1,2 +0,0 @@ -### Fixed -- `scripts/run-ci-tests.sh --affected` (and `--list-affected`) now also runs the whole-tree gates of CI's cheap job that no test names, listed in its `always=` line: `scripts/test-mktemp-tmpdir.sh`, `scripts/check_section_pointers.py` and `.claude/skills/deep-code-review/scripts/derived_numbers.py`. Selection by name never reached them, so an affected run could pass 59/59 while the cheap job failed on a line ending in a bare `mktemp`. They run first on every non-empty affected run (a few seconds in total, so a violation fails before the suites start), appear in the printed selection and in the receipt claim, and a changed one is not reported UNCOVERED. Test: `scripts/test-run-ci-tests-always.sh` (hermetic fixtures; it fails against the runner without the list). diff --git a/changelog.d/app-pentest-openapi-deadline.md b/changelog.d/app-pentest-openapi-deadline.md deleted file mode 100644 index 726e63151..000000000 --- a/changelog.d/app-pentest-openapi-deadline.md +++ /dev/null @@ -1,2 +0,0 @@ -### Changed -- `scripts/app_pentest.sh --openapi`: a spec URL now has a 10 s total deadline for the whole read (connect, headers and body), not only a 10 s limit on each socket operation, so a host that answers one byte at a time can no longer hold the run open; the refusal reads `cannot read spec: took over 10 s`. When schemathesis reports operations that errored, the output gains one low lead, `operations_errored` ("Operations errored and were not tested: N"); `tools.schemathesis` and `openapi.errored` keep their values. `scripts/test_app_pentest.sh` now also pins the `ST102` medium severity and the deadline against a silent host and a byte-at-a-time host. diff --git a/changelog.d/committed-data-fixture-label.md b/changelog.d/committed-data-fixture-label.md deleted file mode 100644 index 1f4d8101d..000000000 --- a/changelog.d/committed-data-fixture-label.md +++ /dev/null @@ -1,5 +0,0 @@ -### Added -- Fixture signals on `committed-data` rows. Each file row of `committed_personal_data.py` (the `committed-data` step of `review_checks.sh`) now ends with counts only, never an address or a domain: how many of the file's addresses have a placeholder-style local part (john, jane, test, user, foo and similar words, alone or back to back, digits and `._%+-` ignored), and how many are on a reserved domain (example.com, .net and .org, plus .test, .example, .invalid and .localhost), out of every address in the file with the reserved names counted back in. The row starts with `likely fixture:` only when the path is a test, spec, fixture or mock path (a whole directory name such as `tests/`, `__mocks__/` or `testdata/`, or a word of the file name such as `people.test.ts` or `mock-users.json`) and at least half of the addresses are placeholder-style or on a reserved domain. docs, examples and samples are not test paths, because real data lives there too. Why: in an audit of 43 repositories the check gave 38 single-file rows; 30 were test or mock data with invented addresses and 8 were real data, and every row needed manual triage. -- The label is a hint, not a verdict, and recall is unchanged. No row is dropped, no threshold or exit code changed, and the first scan, which decides the count and the threshold, is untouched; the second scan (reserved names included, so the signals do not depend on `--include-reserved`) runs only for a file that is already flagged. The repository-total row and the `not-checked` rows carry neither the counts nor the label. Measured on the 38 single-file rows of that audit, the label marks 4 of the 30 fixture rows and none of the 8 real-data rows, and none of the 33 rows of a known real export elsewhere in the audit; most of those fixtures use invented names at realistic domains, outside the word list, so they keep a plain row. `review_checks.sh` still adds no `likely-fixture` hint or tag to these rows, because a real address in a fixture is still a leak. Known ceiling, stated in the script: a test file that mixes real-looking addresses among many reserved ones can carry the label. -- New test `scripts/test_committed_personal_data_fixture_label.py` (13 tests, synthetic addresses only: `.zz` and example.com names, with obviously invented names for the real-looking ones; registered in `scripts/ci-tests.d/committed-personal-data-fixture-label.sh`). A test-path file of placeholder addresses gets the label, a test-path file of real-looking addresses does not (at 5 of 12 placeholder-style, 6 of 12 labelled), a non-test path never does, the row set is unchanged, and no address, domain or local-part word reaches the output. 11 of the 13 fail on the previous release; the other 2 pin the unchanged strict exit code and repository-total row. The existing suites keep every old expectation: their helper strips the new clause from rows, which is the evidence that the rows themselves are the same. -- size-budget-raise: .claude/skills/deep-code-review/references/method-situational.md 44122→44231 the analyzer list says a committed-data row ends with counts and leads likely fixture: for a test path diff --git a/changelog.d/guard-rail-register.md b/changelog.d/guard-rail-register.md deleted file mode 100644 index 5c191aeb3..000000000 --- a/changelog.d/guard-rail-register.md +++ /dev/null @@ -1,4 +0,0 @@ -### Added -- Every guard and hook script under `.claude/skills/*/scripts/` now records who owns it, why it exists and when it is next reviewed, as one header line: `# rail: owner=, why=, review=YYYY-MM-DD`. 42 scripts carry it: those named `*_guard`, `*_gate`, `*_cap` or `*_hook`, those a shipped `templates/*.settings.json` runs as a hook, and `stop_reminder.py`. The first review date is 2027-01-30. Scripts that only the git pre-push template runs (for example `flake_holder.py`) are outside the required set; adding the line opts one in. -- `scripts/ci-gates.sh rails ` fails on a rail with no header, a missing or empty field, or a review date that is not a real calendar day, so a new guard cannot land unowned. `scripts/test-rails.sh` pins it with 46 cases (all red against a `ci-gates.sh` without the subcommand) and `scripts/ci-tests.d/rails.sh` runs both in CI (refs #1592). -- `scripts/ci-gates.sh rails --audit ` lists the rails past their review date for the owner to confirm (bump `review=`) or retire; it never removes one, and a past date does not fail the plain gate. It prints no firing counts: no log of rail firings is shared (`reply_cap.py`, `handback_cap.py` and `file_finding.py` each keep a private one), so no rail is called never-firing until a shared log exists. diff --git a/changelog.d/parity-differ-focus-gate-loader.md b/changelog.d/parity-differ-focus-gate-loader.md deleted file mode 100644 index 71bac24b7..000000000 --- a/changelog.d/parity-differ-focus-gate-loader.md +++ /dev/null @@ -1,2 +0,0 @@ -### Fixed -- `parity_differ.py` now loads agentic-delivery's `focus_gate.py` on every Python it supports. `_focus_gate()` ran the module without registering it in `sys.modules`, so its dataclasses failed to build on Python 3.12 and 3.14 (the two versions checked) and the loader returned `None`, while 3.15 loaded it. The accept-file owner check therefore took the inline `git log` fallback on 3.12 and 3.14 but `focus_gate`'s commit-authorship trust on 3.15: trust semantics, including whether `--accept-rev` is required, changed with the interpreter. The loader now registers the module before executing it, reuses an already-loaded `focus_gate` only when it is the same file, and drops the entry when the import fails; a missing or broken `focus_gate.py` still falls back as before. Test: `scripts/test_parity_differ_focus_gate.py` (`RealFocusGateLoad`) calls the real `_focus_gate()` from a fresh-interpreter state and runs the owner-trusted, agent-authored and no-owner cases through it; it fails on the previous loader under 3.12 and 3.14 and passes on 3.12, 3.14 and 3.15. diff --git a/changelog.d/parity-differ-focus-gate-route.md b/changelog.d/parity-differ-focus-gate-route.md deleted file mode 100644 index 9c9658441..000000000 --- a/changelog.d/parity-differ-focus-gate-route.md +++ /dev/null @@ -1,2 +0,0 @@ -### Fixed -- `parity_differ.py` accept-file trust through `focus_gate.load_record` (the route used wherever `focus_gate.py` imports): the call passed a keyword `load_record` does not have (`commit_only`; commit-only trust is already its default), so it raised `TypeError`, and it passed `resolve_owners()`'s whole `(owners, warning)` tuple as the owner list, so once the keyword was gone every commit, the owner's included, still read as "not the owner" and a missing owner reported that instead of "no owner email configured". It now passes the owner list. CI's `--selftest` never reached the route: `_focus_gate()` loads `focus_gate.py` without registering it in `sys.modules`, so on Python 3.11 to 3.14 (3.15 imports it; no other version was checked) its dataclasses fail to build and the loader falls back; that import is unchanged here. Test: `scripts/test_parity_differ_focus_gate.py`, which imports `focus_gate` normally and hands it to `parity_differ`, so the route runs on every interpreter (it fails on the previous code with the `TypeError`). diff --git a/changelog.d/perpr-shards.md b/changelog.d/perpr-shards.md deleted file mode 100644 index eb2867199..000000000 --- a/changelog.d/perpr-shards.md +++ /dev/null @@ -1,2 +0,0 @@ -### Changed -- CI job `perpr` is now a matrix of 3 legs (`shard: [1, 2, 3]`, `fail-fast: false`) instead of one serial job, because it was the critical path of every PR: it took 19.0 minutes in CI run 41028470595 (2026-10-30). The slowest leg is predicted at about 7.1 minutes, computed from per-test seconds and not measured on CI. Leg `i` runs only the `scripts/ci-tests.d` registrations that `scripts/ci-tests-shard.sh` lists for `SHARD=i/3`, the spelling `selftests` already uses: sorted in the C locale, `*.sh` and `*.bg.sh` numbered separately so the slow background tests spread over the legs, shard `i` takes the numbers equal to `i-1` modulo 3. The 3 slices are disjoint and cover every registration. The helper fails closed (a malformed `SHARD` exits 2, an empty slice exits 1), the tests themselves never see `SHARD`, and `gates` is unchanged: it still needs `perpr`, and a matrix job fails unless every leg succeeds. Contract for test authors (`CONTRIBUTING.md`): a registration must not rely on another registration having run first. Inserting a registration moves every later one along by one position, so a leg can drift by tens of seconds; a weights file for the split is the upgrade if a leg nears 8 minutes. `scripts/test-ci-shards.sh` now reads only the `selftests` shard count, and `scripts/test-conflict-free-prs.sh` copies the helper into its fixture and runs the steps with `SHARD` unset. Test: `scripts/test-perpr-shard.sh` (the real tree's slices are disjoint and complete, a dotfile in `scripts/ci-tests.d` is not counted (the step globs skip it), a bad `SHARD` fails closed, and `ci.yml`'s own start and per-PR steps run as each leg over a fixture: every registration runs once, `SHARD` does not leak, a failing serial or background test fails only its own leg, and `gates` still needs `perpr`). diff --git a/changelog.d/review-checks-offline-optouts.md b/changelog.d/review-checks-offline-optouts.md deleted file mode 100644 index d94dcadb6..000000000 --- a/changelog.d/review-checks-offline-optouts.md +++ /dev/null @@ -1,8 +0,0 @@ -### Fixed -- `review_checks.sh` says its scanners run offline, but three of them phoned home. Run under a network-denying sandbox with their exact argv on a one-file fixture, trivy 0.76.1 `fs` contacted `check.trivy.dev` (telemetry, even with `--offline-scan`), grype 0.121.0 contacted `toolbox-data.anchore.io` (its own-version check, even with `GRYPE_DB_AUTO_UPDATE=false`) and checkov 3.3.30 contacted `pypi.org` (its own-version check, even with `--skip-download`). Each is now switched off by an environment variable and not a flag, so an older tool ignores it instead of crashing on an unknown flag: `TRIVY_DISABLE_TELEMETRY=true` (set once for the trivy block, so the secret-only rerun carries it too), `GRYPE_CHECK_FOR_APP_UPDATE=false` and `CKV_SKIP_PACKAGE_UPDATE_CHECK=true`. With the variables set, the three runs contact no host. -- The other scanners were probed the same way and contacted no host: zizmor, actionlint, bandit, hadolint, gosec, gitleaks, trufflehog, osv-scanner on a warm offline DB, and semgrep with the bundled rules plus a warm `p/default` snapshot. semgrep with the registry pack contacts `semgrep.dev` by design (a warm snapshot or `REVIEW_SEMGREP_REGISTRY=0` avoids it); that was already documented and is unchanged. - -### Changed -- `tool-hub.md`: the Network line names the three opt-outs, the versions they were found on and the denied hosts. -- `scripts/test-review-checks-tool-hub.sh` grows from 101 to 108 checks and `scripts/test-scanner-offline-db.sh` from 19 to 20. The stubs now log the opt-out environment they start with (`GRYPE_*`, `CKV_*`, `TRIVY_*`), and new checks assert it for grype (both variables, the DB one was unasserted until now), checkov and trivy, the trivy secret-only rerun included, and that `tool-hub.md` names each. Both suites unset the three variables first, so a developer's own export cannot satisfy them. Seven of the eight new checks fail against the previous `review_checks.sh`; the eighth pins the existing `GRYPE_DB_AUTO_UPDATE=false`. -- size-budget-raise: .claude/skills/deep-code-review/references/tool-hub.md 9788→10152 the three environment opt-outs, why they are variables and the hosts they stop diff --git a/changelog.d/semgrep-js-date-gcm.md b/changelog.d/semgrep-js-date-gcm.md deleted file mode 100644 index c8302e7b8..000000000 --- a/changelog.d/semgrep-js-date-gcm.md +++ /dev/null @@ -1,6 +0,0 @@ -### Added -- Two bundled JavaScript/TypeScript semgrep rules in `templates/semgrep-taint/js.yml`, for bug classes that hand audits found and the bundled rules missed. `review_checks.sh` already loads the whole template directory, so both run in the default review with no wiring change. -- `perun.js.date-parse-rollover` (INFO, low): a date accepted only because `Number.isFinite` or `isNaN` of an inline `Date.parse(x)`, `new Date(x)` or `new Date(x).getTime()` passed. V8 reads an impossible day such as 2026-02-30 as 2 March instead of rejecting it. The message names the fix: format the parsed date back with `toISOString()` and reject the input unless it matches. A `.toISOString()` call anywhere in the enclosing function counts as that round trip and silences the rule, and test paths are skipped like the credential rules. A check on a variable bound first (`const d = new Date(x); isNaN(d)`) is deliberately not flagged: most of those hits were display, age and TTL helpers, so they cost more precision than they bought recall. Other known ceilings: a function that also stamps an unrelated `toISOString()` is missed, component compares such as `getUTCDate()` are not recognised, and an INFO hit can land on code that parses a date without validating it. -- `perun.js.gcm-no-auth-tag-length` (WARNING, medium): `createDecipheriv` with an `*-gcm` cipher and no `authTagLength` option, so Node accepts an authentication tag shorter than 16 bytes (a 4-byte tag passes; checked on Node 24). That weakens integrity only when the tag length is attacker-controlled, for example when the tag is whatever follows the ciphertext in the message, and the message says so. It names the fix: pass `{ authTagLength: 16 }` as the fourth argument. The cipher may be a string literal or a `const` bound to one; a non-literal cipher name and an options argument that is not an object literal are skipped, not guessed. -- New test `scripts/test_semgrep_js_date_gcm.py` (registered in `scripts/ci-tests.d/semgrep-js-date-gcm.sh`): fictional fixtures annotated `// ruleid:` and `// ok:` in `.js` and `.ts` copies (28 expected hits), with the round-trip form, the `authTagLength: 16` form and a variable-bound check as the clean cases, plus a copy under a test path that must stay clean. The live part needs semgrep and prints SKIP without it, and CI installs none, so offline structural asserts on the YAML back it up: the severities, the named fix, the cipher regex (semgrep anchors `metavariable-regex` at the start, so `gcm$` alone matches nothing), both round-trip suppressions, the test-path exclusion shared with the credential rules, the `authTagLength` exemptions, no variable-bound alternative, and the attacker-controlled-tag wording. Against the previous release's `js.yml` the live check misses all 28 expected hits; seven single-edit mutants of the new rules (no round-trip suppression, no `authTagLength` exemption, an unanchored cipher regex, the wrong severity, no test-path exclusion, a variable-bound form added back, the old overstated message) each fail the test both with and without semgrep installed. -- Precision, measured read-only on audited code, counts only. A true positive (TP) is one reviewer's reading of three lines of context: the check is the only gate deciding whether an externally supplied date string is accepted, in non-test code. Date rule: 40 files pre-selected for an inline `isNaN`/`isFinite` of a date parse (26 distinct file contents, identical copies across clones counted once). A first version that also flagged variable-bound checks gave 42 hits in 34 of the 40 files (34 distinct hits, 23 TP, 68%); this version gives 38 hits in 34 files (30 distinct, 23 TP, 77%), the 4 dropped hits being variable-bound and none a TP. 7 of the kept hits pair a format regex with the parse check on the same value, the clearest TPs; the 7 kept false positives are a bar-highlight computation, two test or check scripts, a coercion helper, an age calculation, a token-payload shape check and a Retry-After header. On 60 other files that contain a date parse and an `isNaN`/`isFinite` anywhere, the first version gave 32 hits in 26 files and this one 0; about 5 of those 30 distinct dropped hits were real input validators (throwing or returning an error on NaN), so the narrowing costs some recall. One audited module that validated a calendar date with an inline `Number.isFinite(Date.parse(...))` plus a format regex: 1 hit on the revision before its fix, 0 on the revision after it (which round-trips with `toISOString()`). Gcm rule: of 10 JavaScript files that call `createDecipheriv` across the audit clones, 1 hit, a TP on the variant without `authTagLength`; the six copies of the fixed variant and the other files stay clean. diff --git a/changelog.d/trufflehog-retry-and-noise.md b/changelog.d/trufflehog-retry-and-noise.md deleted file mode 100644 index 4944534c6..000000000 --- a/changelog.d/trufflehog-retry-and-noise.md +++ /dev/null @@ -1,8 +0,0 @@ -### Fixed -- trufflehog step: one bad file no longer voids a whole 200-file call. `--fail-on-scan-errors` makes a single unreadable file fail the entire call with no output, so the step reported `not run: trufflehog` and every finding in the other 199 files was lost. A failed call (a crash, a timeout or an unreadable file) is now retried file by file: files that pass alone keep their rows, each file that still fails is one `not-checked` row naming it, and the run is `partial` (`trufflehog(errors)` in the closing line and the record) instead of `not run`. When no file passes alone the tool itself failed, so it is `not run` as before, with the first call's reason. A call that succeeds is never retried, and neither is a one-file call (the retry would be the same call). The retry gives up after three failed single-file runs while none has passed (that chunk is `not run`, with the first call's reason), so a tool that crashes or hangs on every call costs three extra runs per chunk and not up to 200 timeboxed ones; the price is that three or more unreadable files ahead of the first good one also read as `not run` (loud, never clean). - -### Changed -- trufflehog now follows gitleaks' noise rules. When the repo has no `.gitleaks.toml` (gitleaks then uses `templates/gitleaks.toml`), the template's skip paths (`.history/`, `fixtures/`, `testdata/`, generated registry JSON, lockfiles) are dropped from trufflehog's file list and counted on stderr: `review_checks: trufflehog template skipped N changed file(s) ...`, in the wording of gitleaks' line. The filter runs on the file list and not through `--exclude-paths`, because on trufflehog 3.99.7 that flag is ignored for files named on the command line (checked on the real binary), so passing it would change nothing. A diff made only of those paths leaves trufflehog no work: it is not called and is not listed as wired or as not run. A repo with its own `.gitleaks.toml` is scanned in full, as before. -- `tool-hub.md` records that `.gitleaksignore` fingerprints and `.gitleaks-allow` hashes do not apply to trufflehog and that an inline `trufflehog:ignore` comment does (checked on trufflehog 3.99.7), and lists trufflehog among the tools that can be `partial`. -- `scripts/test-review-checks-trufflehog.sh` grows from 40 to 67 checks: the stub `trufflehog` gains `TF_BAD` (a call naming that file fails with no output) and `TF_HANGN` (a call naming that many files hangs), and the suite covers the retry, its cap with a tool that crashes or hangs on every call, a 450-file run with one bad file, the template filter and, with the real binary, an unreadable file, `.gitleaksignore`, the inline ignore comment and the template skip. Its fixture path moved from `tests/fixtures/` to `tests/`, since `fixtures/` is now a skipped path. `scripts/test-review-checks-gitleaks-template.sh` matches its skip-count line on `gitleaks template skipped`, because the trufflehog line now has the same wording. -- size-budget-raise: .claude/skills/deep-code-review/references/tool-hub.md 8881→9788 the trufflehog noise-rule, suppression and per-file retry rule with its cap, and trufflehog under partial diff --git a/changelog.d/trufflehog-secrets-step.md b/changelog.d/trufflehog-secrets-step.md deleted file mode 100644 index 3b2e7a2d5..000000000 --- a/changelog.d/trufflehog-secrets-step.md +++ /dev/null @@ -1,8 +0,0 @@ -### Added -- trufflehog secrets step in the review hub. `review_checks.sh` now runs trufflehog right after gitleaks. The hub never ran it, even when it was installed, so a second secret detector never contributed. The step scans the changed files in diff mode and every tracked file under `--full` (vendored directories are skipped as for every other step), passed as explicit `./path` arguments, 200 per call like semgrep. It runs fully offline: `--no-verification` never contacts a provider (so every finding is unverified) and `--no-update` skips the version check. `--fail-on-scan-errors` turns an unreadable path into a not-run row instead of a silent clean pass. -- No secret value is read, printed or stored. The step asks for `--sarif`, not `--json`, because the default and JSON outputs print the secret (JSON as `Raw`, `RawV2`, `Redacted` and `SecretParts`); the SARIF document holds the detector name, the file, the line and a fingerprint hash. A row is the detector, file and line with a fixed message of ours, and the parser reads only those three fields, so even a hostile document cannot leak a value into a row. `--log-level=-1` keeps trufflehog's own log lines out of the merged output so they cannot corrupt the document. A test plants a canary secret in the fake binary's `--json` output and proves it never reaches stdout, stderr, the record or any file under `TMPDIR`, and that the same witness does catch the canary when the run is switched to `--json`; the real binary gets the same drill on a runtime-built token. -- Same patterns as the other tools. A missing binary gives the `not installed` row (a `not-checked` finding, listed as not run and fixable, with the `brew install trufflehog` hint). The heavy lease is taken like gitleaks' (a full lease degrades to `nice -n 19` and is named in the summary, `REVIEW_LEASE_FULL=skip` gives a not-run row), and `heavy_gate.py` already classed `trufflehog ... filesystem` as heavy; a test feeds it the exact argv the step ran. A crash, a non-SARIF document or a result without a location fails closed as not run, never clean. trufflehog counts in the closing `ran X / wired Y` line and the record, and rows under fixture paths get the same `likely-fixture` hint as gitleaks' rows. -- `provision_tools.sh` now lists trufflehog (`brew install trufflehog`). It was missing from the installer list, so an absent trufflehog could not be `fixable` and `--print` would not have installed it. `method-situational.md` names the step in the analyzer list, and `tool-hub.md` lists trufflehog among the tools routed before the hub and records why the step uses `--sarif`. -- New test `scripts/test-review-checks-trufflehog.sh` (40 checks, 37 on a host without trufflehog; registered in `scripts/ci-tests.d/review-checks-trufflehog.sh`): a fake `trufflehog` on a hermetic PATH covers the finding row, the offline flags, diff mode against `--full`, the 200-file chunks, the fail-closed cases, the missing binary, the three lease states, the doc rows and the no-secret guarantee. A real-binary smoke runs on a two-file fixture only when trufflehog is installed. 35 of the 40 checks fail on the previous release; the other 5 are negative guards (nothing leaked, nothing scanned) or a self-check of the fake, which hold when the step is absent. `test-provision-tools.sh`, `test-semgrep-pack-fallback.sh` and `test-mode-coverage.sh` gain a clean trufflehog stub (in the first two the scanner counts rise by one; in the third a host without trufflehog would otherwise add a second not-checked row), and `test-review-checks-gitleaks-template.sh` now asserts on gitleaks rows only, because the trufflehog step has no counterpart to the gitleaks template's skip list and reports the `.history/`, `fixtures/` and `testdata/` files the template leaves out. -- size-budget-raise: .claude/skills/deep-code-review/references/method-situational.md 44018→44122 the analyzer list names the trufflehog step and what its rows carry -- size-budget-raise: .claude/skills/deep-code-review/references/tool-hub.md 8731→8881 trufflehog joins the routed-before-the-hub list and the no-secret-in-output rule states why the step uses --sarif diff --git a/gemini-extension.json b/gemini-extension.json index 60789b5f7..1598d9aa5 100644 --- a/gemini-extension.json +++ b/gemini-extension.json @@ -1,6 +1,6 @@ { "name": "perun", - "version": "1.624.0", + "version": "1.625.0", "description": "Perun: a skill suite for AI-agent teams: deep code review, token and cost tools, gated delivery, an idea critic and product specialists.", "contextFileName": "AGENTS.md" }