Release CodeTruss CLI v0.2.36 - #19
Conversation
Bring the public mirror up to the shipped release. The mirror had been sitting at 0.2.30 while the website served 0.2.35, which is the gap that matters: the newest attested tag should never trail the bytes the site hands out. Source is mirrored from the 0.2.35 release commit, not from in-progress work. `pnpm release:artifact` rebuilds the bundle to the exact published digest a58a392e4b8dead73a032b94500c8a73ff67e2b6a11431bc041ad9ba9f99826f, and `pnpm release:verify` confirms it byte-for-byte against the immutable website archive now recorded in release-reference.json. The archive downloaded from codetruss.com is byte-identical to the one built from this tree. - Mirror packages/cli and packages/analyzer-engine at 0.2.35, including the 0.2.31 through 0.2.35 changelog entries. The security rule pack, taint solver, and zero-dependency JavaScript parser are new public source because 0.2.35 executes them locally; they already ship inside the published bundle. - Add the immutable 0.2.35 archive, checksum, and SBOM to public/downloads and repoint the latest.* aliases and manifest. - Update release-reference.json to the published archive, SBOM, and bundle digests. README corrections, checked against the shipped 0.2.35 binary rather than carried forward: - The receipt sample was a 0.2.30 run asserting `local-registry-v1` and "No injection or taint analysis was performed". Both are now false. Replaced with an abridged real 0.2.35 receipt: profile `local-registry-v2`, the new "What the local security pass checked" section, and the narrowed "What did not run". - Dropped "SAST and the symbol graph are hosted-only. A local run never performs injection or taint analysis." The rule pack and taint solver run locally and offline over JavaScript, TypeScript and TSX as of 0.2.35. What stays hosted is now stated precisely instead of categorically. - The first-run paragraph claimed that without an allow policy every changed file is unexpected and the review exits 1. Since 0.2.32 scope is inferred from the turn and disclosed on the receipt; a clean first run now exits 0. Verified both ways against the binary. - Removed "omitted from the built-in `--help` banner in 0.2.30" from the `verify-policy trust-key` note. 0.2.35 lists it in `--help`. - Benchmark row moved from five of nine to six, with three misses and the 177,703-line corpus size, matching codetruss.com/benchmark. Noted that read-modify-write moved because the rule shipped, not because the bar moved. - Documented the Suggested fixes section added in 0.2.34, including that a credential diff is masked and cannot apply cleanly by design. - Version-pinned install and attestation examples now name 0.2.35. npm still trails at 0.2.24; publication remains a separate manual dispatch and the README and changelog say so. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The publish workflow requires the selected release to carry exactly four assets, including release-manifest.json. Commit 992d6d3 removed that file from the release upload list because build-release.mjs no longer emits it, so every release from v0.2.30 onward carries three assets and this check rejects them. v0.2.24, the last release predating the change, is the only tag that still satisfies it — which is also the last version on npm. Drop release-manifest.json from the expected set. The check keeps its purpose: the release must carry the archive, its checksum, and its SBOM, and nothing else. Every other gate is untouched — attestation binding to the release workflow, exact tag, tag commit, and GitHub-hosted runner; the byte comparison; the package identity check; and the digest-pinned handoff to the publish job. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
📝 WalkthroughWalkthroughThis release updates the CLI to 0.2.35. It adds local JavaScript-family SAST, deterministic fixes, inferred scope, receipt changes, hook resilience, verification setup diagnostics, trust-store resolution, tests, documentation, and release artifacts. ChangesSecurity analysis and remediation
CLI workflow changes
Release and documentation
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 8
🧹 Nitpick comments (4)
packages/analyzer-engine/src/security/analysis-scope.ts (1)
16-21: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winAdd a test that locks the rule
cweKeyinvariant.The current rule pack only uses CWE table keys, but
RULE_METAtypescweKeyaskeyof typeof CWEinstead of a narrower invariant. Add a test that checks everyTAINT_SINKSandPATTERN_RULEScweKeyexists inCWEso report-class sorting cannot regress toundefined.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/analyzer-engine/src/security/analysis-scope.ts` around lines 16 - 21, Add a test covering all entries in TAINT_SINKS and PATTERN_RULES that asserts each rule’s cweKey exists in the CWE table. Reuse the exported rule collections and CWE symbol, and ensure the test fails when any key would resolve to undefined.packages/cli/test/hooks.test.ts (1)
458-461: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winThe same-version case is not actually covered here.
The comment says "Same version: nothing to say", but the assertion is
toBeUndefined().installedCliVersion(bin)returnsundefinedbecause no@codetruss/climanifest exists abovenode_modules/.bin/codetrussin this fixture. The test therefore covers the "no owning manifest" path, not the "manifest version equalsCLI_VERSION" path.
inspectExecutableShadowdocuments that a repository-local install of the same version must not be reported as a shadow. That branch stays untested. Consider adding a manifest withversion: CLI_VERSIONabove the resolved binary and assertinginstalledCliVersionreturnsCLI_VERSIONwhileinspectHookDoctoremits no shadow warning.This is a released mirror, so ship any change through a new CLI version rather than editing this file in place. Based on learnings: repository contents must remain byte-identical to the committed versioned archive in
public/downloads.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/cli/test/hooks.test.ts` around lines 458 - 461, Update the same-version fixture around installedCliVersion and inspectHookDoctor to include a repository-local `@codetruss/cli` manifest with version CLI_VERSION above the resolved codetruss binary, then assert installedCliVersion resolves to CLI_VERSION and no shadow warning is emitted. Do not edit this released mirror directly; deliver the test correction through a new CLI version while preserving byte-identical versioned archive contents.Source: Learnings
packages/cli/src/hook-runtime.ts (1)
1274-1288: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winConsider a short delay between capture retries.
captureExactSnapshotretries immediately afterWorkingTreeChangedError. The error means another writer is modifying the working tree at that moment. Three back-to-back attempts are likely to observe the same in-flight write, and each attempt re-materializes the whole working tree on the prompt path. A short delay before each retry raises the chance that the tree settles and reduces wasted snapshot work.This is a released mirror, so ship the change through a new CLI version rather than editing this file in place. Based on learnings: repository contents must remain byte-identical to the committed versioned archive in
public/downloads.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/cli/src/hook-runtime.ts` around lines 1274 - 1288, Update the released CLI through a new versioned release artifact rather than editing packages/cli/src/hook-runtime.ts directly. In the released mirror’s captureExactSnapshot retry loop, add a short asynchronous delay before retrying after isWorkingTreeChangedError, while preserving immediate propagation for other errors and the existing retry bound. Ensure repository contents remain byte-identical to the committed archive under public/downloads.Source: Learnings
packages/cli/test/scope-inference.test.ts (1)
72-99: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winConsider covering the task-token rejection guards.
taskPathTokensrejects absolute paths, Windows drive prefixes, and..or.segments. No case exercises those rejections. A task string such asFix ../../etc/passwdorFix /etc/passwdshould produce no inferred root. TheMAX_INFERRED_ROOTScap is also unpinned.These guards decide whether inference can name a path outside the repository. Add the cases in the next released version rather than editing the mirror.
Based on learnings: in the published CodeTruss CLI mirror, do not make mirror-only edits to TypeScript source files; ship fixes through a new released version from the private monorepo.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/cli/test/scope-inference.test.ts` around lines 72 - 99, Extend the scope-inference tests around resolve and taskPathTokens to cover rejected absolute paths, Windows drive-prefixed paths, and paths containing “..” or “.” segments, asserting that each produces no inferred roots. Also add coverage that pins the MAX_INFERRED_ROOTS limit, and make these changes only in the next released version rather than the published mirror.Source: Learnings
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/analyzer-engine/src/fixes.ts`:
- Around line 152-158: Update untrackEnvFileFix to shell-quote filePath
everywhere it is interpolated into the git rm and .gitignore commands, using a
dedicated quoting helper, and add -- before the git rm path to prevent option
interpretation.
- Around line 287-292: Update CI command selection in CI_INSTALL and
ciWorkflowFix() to pass the full packageManager value through CiStarterEvidence,
then select Yarn’s frozen flag by major version: use --frozen-lockfile for Yarn
1.x and --immutable for Yarn 2+. Preserve existing behavior for npm, pnpm, bun,
and non-frozen installs.
In `@packages/analyzer-engine/src/security/engine.ts`:
- Around line 198-202: Update scanOne around parser.parse so every successfully
parsed tree is released in a finally block, including when analyzeFunction, sink
matching, rule testing, or taint queries throw; preserve the existing degraded
handling for a null parse result and normal scan behavior. Apply this fix in the
private monorepo and ship it through a new release rather than editing this
published mirror.
In `@packages/analyzer-engine/src/security/local-profile.ts`:
- Around line 13-27: Update the source comment describing the CLI profile in the
private monorepo so it accurately documents all seven IDs in CLI_SAST_RULE_IDS,
including open-record-write, and adjust the rule count accordingly. Do not
modify this published mirror; release the correction through a new version.
In `@packages/cli/src/config.ts`:
- Around line 273-281: Update the ecosystem detection flow around nodeScripts
and the pnpm/npm/yarn lockfile branches so an empty or blocked Node result does
not return before probing Go, Cargo, and pytest. When a manifest is detected but
its executable is unavailable, preserve its candidate and mark it
package-manager-unavailable, then continue checking later ecosystems; return the
accumulated detected candidate or blocker only after all ecosystems are
evaluated. Add regression coverage for mixed repositories and unavailable
package managers.
In `@packages/cli/src/git-snapshot.ts`:
- Around line 149-156: Prevent nested retries between
materializeWorkingTreeSnapshot and captureExactSnapshot from multiplying full
snapshots: make the exhaustion error terminal at the outer retry layer, or
adjust one attempt bound so the combined maximum is predictable. Update
isWorkingTreeChangedError and the related retry flow without changing successful
retry behavior. Ship the fix via a new CLI version, regenerating the committed
versioned archive while preserving byte identity under public/downloads.
In `@packages/cli/src/local-sast.ts`:
- Around line 88-92: Update the receipt-generation logic around complete,
truncated, and detail so every incomplete pass names the lost coverage:
distinguish degradedLanguages from truncation, and report the relevant
truncatedFiles or findingsTruncated cases instead of counting only filesSkipped.
Do not modify this mirrored TypeScript file; implement the fix in the private
monorepo and release a new version so public/downloads remains byte-identical.
In `@README.md`:
- Around line 225-226: Update the PASS verdict explanation near the existing
receipt documentation in README.md to accurately state that the listed passes
ran and found nothing new, rather than saying they never ran. Preserve the
clarification that PASS is not a statement that the change is secure.
---
Nitpick comments:
In `@packages/analyzer-engine/src/security/analysis-scope.ts`:
- Around line 16-21: Add a test covering all entries in TAINT_SINKS and
PATTERN_RULES that asserts each rule’s cweKey exists in the CWE table. Reuse the
exported rule collections and CWE symbol, and ensure the test fails when any key
would resolve to undefined.
In `@packages/cli/src/hook-runtime.ts`:
- Around line 1274-1288: Update the released CLI through a new versioned release
artifact rather than editing packages/cli/src/hook-runtime.ts directly. In the
released mirror’s captureExactSnapshot retry loop, add a short asynchronous
delay before retrying after isWorkingTreeChangedError, while preserving
immediate propagation for other errors and the existing retry bound. Ensure
repository contents remain byte-identical to the committed archive under
public/downloads.
In `@packages/cli/test/hooks.test.ts`:
- Around line 458-461: Update the same-version fixture around
installedCliVersion and inspectHookDoctor to include a repository-local
`@codetruss/cli` manifest with version CLI_VERSION above the resolved codetruss
binary, then assert installedCliVersion resolves to CLI_VERSION and no shadow
warning is emitted. Do not edit this released mirror directly; deliver the test
correction through a new CLI version while preserving byte-identical versioned
archive contents.
In `@packages/cli/test/scope-inference.test.ts`:
- Around line 72-99: Extend the scope-inference tests around resolve and
taskPathTokens to cover rejected absolute paths, Windows drive-prefixed paths,
and paths containing “..” or “.” segments, asserting that each produces no
inferred roots. Also add coverage that pins the MAX_INFERRED_ROOTS limit, and
make these changes only in the next released version rather than the published
mirror.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 5d36c841-6367-4375-95f1-ba7676f306d1
📒 Files selected for processing (64)
.github/workflows/publish-npm.ymlCHANGELOG.mdREADME.mdpackages/analyzer-engine/src/coverage.tspackages/analyzer-engine/src/dependencies.tspackages/analyzer-engine/src/detect.tspackages/analyzer-engine/src/fixes.tspackages/analyzer-engine/src/indexer.tspackages/analyzer-engine/src/secrets.tspackages/analyzer-engine/src/security/analysis-scope.tspackages/analyzer-engine/src/security/cwe.tspackages/analyzer-engine/src/security/engine.tspackages/analyzer-engine/src/security/finding-map.tspackages/analyzer-engine/src/security/js-parse/index.tspackages/analyzer-engine/src/security/js-parse/lexer.tspackages/analyzer-engine/src/security/js-parse/node.tspackages/analyzer-engine/src/security/js-parse/parser.tspackages/analyzer-engine/src/security/lang.tspackages/analyzer-engine/src/security/local-profile.tspackages/analyzer-engine/src/security/normalize.tspackages/analyzer-engine/src/security/rmw.tspackages/analyzer-engine/src/security/rules.tspackages/analyzer-engine/src/security/taint.tspackages/analyzer-engine/src/security/types.tspackages/analyzer-engine/src/structure.tspackages/analyzer-engine/src/types.tspackages/cli/CHANGELOG.mdpackages/cli/package.jsonpackages/cli/scripts/build.mjspackages/cli/src/analysis.tspackages/cli/src/cli.tspackages/cli/src/config.tspackages/cli/src/fix-suggestions.tspackages/cli/src/git-snapshot.tspackages/cli/src/hook-baseline.tspackages/cli/src/hook-result.tspackages/cli/src/hook-runtime.tspackages/cli/src/hooks.tspackages/cli/src/local-sast.tspackages/cli/src/policy.tspackages/cli/src/receipt.tspackages/cli/src/scope-inference.tspackages/cli/src/setup.tspackages/cli/src/types.tspackages/cli/src/verify-trust.tspackages/cli/test/analysis-profile.test.tspackages/cli/test/command-e2e.test.tspackages/cli/test/config.test.tspackages/cli/test/fix-suggestion-fixture.tspackages/cli/test/fix-suggestion-hook.test.tspackages/cli/test/fix-suggestions.test.tspackages/cli/test/hooks.test.tspackages/cli/test/local-sast.test.tspackages/cli/test/receipt.test.tspackages/cli/test/scope-inference.test.tspackages/cli/test/verify-trust.test.tspublic/downloads/codetruss-cli-0.2.35.sbom.cdx.jsonpublic/downloads/codetruss-cli-0.2.35.tgzpublic/downloads/codetruss-cli-0.2.35.tgz.sha256public/downloads/codetruss-cli-latest.jsonpublic/downloads/codetruss-cli-latest.sbom.cdx.jsonpublic/downloads/codetruss-cli-latest.tgzpublic/downloads/codetruss-cli-latest.tgz.sha256release-reference.json
💤 Files with no reviewable changes (1)
- .github/workflows/publish-npm.yml
Blocked: two required Windows checks fail on a real defect in the shipped 0.2.35 source
Root cause
state.paths.push(relative(root, full))
Every other path surface in the CLI normalizes explicitly — Not a 0.2.35 regressionThat line is byte-identical at So this is a long-standing latent bug that 0.2.35 is the first release to expose — and the public mirror's Windows matrix is the first hard gate it has ever met. The private monorepo runs the same OS matrix, but its CI has been red for unrelated reasons across recent commits, so nothing blocked on it there. Why this is not being patched hereTwo independent reasons, either sufficient:
Patching only the two tests would dodge (1) while quietly converting a real Windows defect into a green check. That is the opposite of what this sync is for. What unblocks itA one-line upstream fix, then a 0.2.36 release, then this sync retargets to 0.2.36: state.paths.push(relative(root, full).replaceAll('\\', '/'))Worth pairing with a regression test that asserts POSIX separators out of Everything else in this PR is verified and green: eight of nine checks pass, the rebuild reproduces the published digest exactly, and |
Impact assessment, so this gets the right priority rather than the loudest oneTraced the blast radius on this tree. Two things worth stating precisely, one of which rules out the scary reading. It is not a verdict bypass. What it does break is the receipt as stable evidence. The second failing test runs the full CLI (
The same file appears under two different names in the same signed document, and the receipt bytes for identical repository state differ by platform. For an artifact whose entire pitch is that you can re-verify it later, that is the part worth fixing rather than the failing assertion. One latent hazard to fix at the same time. None of this changes the recommendation: normalize at the source in |
Windows CI on this branch caught a real defect in 0.2.35 rather than a sync mistake, so the mirror waited for a fixed release instead of shipping around it. Upstream normalized the indexer at the source and released 0.2.36; this retargets to that release commit. The fix went further than the one line reported here. The same unnormalized `relative()` also broke `vendoredRoot()`, whose regex matches `^(?:\.claude|vendor|…)(?:\/|$)` and therefore never matched a backslash path, so vendored payloads silently re-entered analysis on Windows. That was a third consequence, not visible from the two failing assertions. Re-verified end to end against the new release commit: - 112 files mirrored, zero content and zero file-mode mismatches. - `pnpm release:artifact` rebuilds to the published digest 953e3f48725a7471043b48a55aedd33ef78854615a341dd95881d90eeae3e814, and `release:verify` confirms it byte-for-byte against the immutable website archive. `git status` shows no drift in public/downloads after the rebuild. - 295 tests across 24 files, up from 292 across 23: the new indexer-path-separators suite. - The regression test was falsification-checked rather than taken on trust. Reverting only the normalization makes it fail 2 of 2; restoring it makes them pass. It swaps `relative` for its win32 counterpart while leaving `join` and the filesystem real, so it reproduces the Windows separator on a POSIX host. The 0.2.35 archive, checksum, and SBOM are dropped rather than kept alongside. Every other archive in public/downloads corresponds to a tagged, attested release; 0.2.35 will never be tagged, and shipping an artifact here with no release behind it is the exact ambiguity this mirror exists to remove. README rechecked against the 0.2.36 binary, not carried forward: `--help` still lists `verify-policy trust-key`, the analysis profile is still `local-registry-v2`, and both receipt security sections are byte-identical to the 0.2.35 wording after normalizing whitespace. The receipt excerpt is a fresh 0.2.36 run with its own evidence trees and policy digest. The line crediting the local SAST pass to 0.2.35 stays, because that is the release that introduced it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Retargeted to v0.2.36 — the Windows blocker is closedUpstream normalized the indexer at the source and shipped 0.2.36, so this branch now mirrors that release commit instead of 0.2.35. PR title updated to match. The fix was larger than what these two failing assertions showed. The same unnormalized Re-verified against the new release commit
The regression test was falsification-checked, not trustedReverting only the normalization and re-running 0.2.35 artifacts dropped rather than keptEvery other archive in README rechecked against the 0.2.36 binary
|
Upstream triage: the 0.2.36 changelog overwrote the 0.2.35 headerFound while mirroring, not a sync artifact. Not blocking this release, and deliberately not patched here. The 0.2.36 entry replaced the 0.2.35 heading instead of being inserted above it: -## 0.2.35 — 2026-08-07
+## 0.2.36 — 2026-08-07
+
+- **Indexed file paths are now the same bytes on every platform.** …Headings before and after:
So the whole 0.2.35 entry — the local SAST release, which is the headline feature of that version — now reads as part of 0.2.36, and 0.2.35 has no changelog entry at all. The two path-normalization bullets are followed by a blank line and then "Security analysis now runs locally", under one heading. Impact is documentation only; no behavior changes and nothing about the attestation chain is affected. But Worth noting the precedent: 0.2.30 was cut specifically to "publish the 0.2.25 through 0.2.29 release history, which shipped without changelog entries." This is that same failure, one release later. Not fixable on the mirror for the usual reason — editing it makes |
Scope
Validation
On the retargeted branch:
pnpm install --frozen-lockfile— lockfile up to datepnpm typecheck— cleanpnpm release:artifact— rebuilt to953e3f48725a7471043b48a55aedd33ef78854615a341dd95881d90eeae3e814, byte-identical to the published archive.git statusshows no drift inpublic/downloadsafter the rebuild.pnpm test— 295 tests across 24 files, all passing, including the newindexer-path-separatorssuite. Deterministic package format and release integrity verifier tests passed.pnpm release:verify—Verified immutable CodeTruss CLI 0.2.36 release (953e3f48…)pnpm test:install— clean global install, guided setup, all hook installers exercisedMirror fidelity: 112 files from release commit
18d9ecf, zero content mismatches and zero file-mode mismatches.The regression test was falsification-checked, not trusted. Reverting only the normalization in
indexer.tsmakesindexer-path-separators.test.tsfail 2 of 2; restoring it makes them pass.Every README claim changed here was checked against the extracted 0.2.36 binary:
verify-policy trust-keyin--helpcli.cjs --helpreviewrunlocal-registry-v2reviewrunallowed (inferred)Privacy and network impact
packages/analyzer-engine/src/security/**is not new disclosure: the CLI executes it locally and it already ships compiled inside the published bundle.Release impact
packages/cli/CHANGELOG.mdmirrored verbatim (it is a published, byte-compared file), rootCHANGELOG.mdheader refreshed,release-reference.jsonrepointed to the 0.2.36 archive, SBOM, and bundle digests.Second commit
Align the npm publish asset check with what the release actually uploadsis a prerequisite for publishing to npm. The publish workflow demanded four release assets includingrelease-manifest.json, which992d6d3removed from the upload list. Every release from v0.2.30 on carries three, so the check rejected them all — v0.2.24 is the last tag that satisfies it, which is also the last version on npm. No other gate is relaxed.Known, tracked separately
The 0.2.36 changelog overwrote the
## 0.2.35heading, so 0.2.35's entry now reads as part of 0.2.36. Documentation only, mirrored source, not patched here — see the triage comment below.