Skip to content

Release CodeTruss CLI v0.2.36 - #19

Merged
DeliriumPulse merged 3 commits into
mainfrom
release/v0.2.35
Aug 7, 2026
Merged

Release CodeTruss CLI v0.2.36#19
DeliriumPulse merged 3 commits into
mainfrom
release/v0.2.35

Conversation

@DeliriumPulse

@DeliriumPulse DeliriumPulse commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

Scope

  • Approved issue or context: the public mirror sat at 0.2.30 while codetruss.com served newer bytes. The newest attested tag must not trail what the site distributes.
  • Intended change: mirror the 0.2.36 release commit, publish the immutable 0.2.36 artifacts, correct the README against the shipped binary, and unblock the npm publish asset check.
  • Explicitly out of scope: tagging (follows this merge), npm publication (separate manual dispatch), any org migration.

History note. This PR originally targeted 0.2.35. The Windows CI legs here failed on a real defect in that release rather than on a sync mistake, so it was held rather than merged, and the two Windows commits below are that story. Upstream fixed the indexer and shipped 0.2.36; commit c0e40b4 retargets to it. The 0.2.35 archive is dropped rather than kept, because every archive in public/downloads corresponds to a tagged, attested release and v0.2.35 will never be tagged.

Validation

On the retargeted branch:

  • pnpm install --frozen-lockfile — lockfile up to date
  • pnpm typecheck — clean
  • pnpm release:artifact — rebuilt to 953e3f48725a7471043b48a55aedd33ef78854615a341dd95881d90eeae3e814, byte-identical to the published archive. git status shows no drift in public/downloads after the rebuild.
  • pnpm test295 tests across 24 files, all passing, including the new indexer-path-separators suite. Deterministic package format and release integrity verifier tests passed.
  • pnpm release:verifyVerified immutable CodeTruss CLI 0.2.36 release (953e3f48…)
  • pnpm test:install — clean global install, guided setup, all hook installers exercised

Mirror 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.ts makes indexer-path-separators.test.ts fail 2 of 2; restoring it makes them pass.

Every README claim changed here was checked against the extracted 0.2.36 binary:

Claim Checked by Result
verify-policy trust-key in --help cli.cjs --help listed — stale "omitted" note removed
Analysis profile real review run local-registry-v2
"A local run never performs injection or taint analysis" real receipt false since 0.2.35 — rewritten
First run without allow policy exits 1 real review run exits 0, scope allowed (inferred)
Benchmark five of nine, four misses codetruss.com/benchmark six of nine, three misses
Suggested fixes section real run on a committed secret renders with masked value
Receipt security sections unchanged 0.2.35 → 0.2.36 both binaries, whitespace-normalized byte-identical

Privacy and network impact

  • No private code, credentials, keys, receipts, or customer data are included.
  • This change adds or modifies no network egress, telemetry, provider authentication, sync/upload behavior, or command execution.

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

  • User-visible behavior: none from this repository. It mirrors an already-shipped release.
  • Release impact: none / patch (mirror sync)
  • Changelog, version, package, SBOM, release-reference, website, or documentation updates: all included. packages/cli/CHANGELOG.md mirrored verbatim (it is a published, byte-compared file), root CHANGELOG.md header refreshed, release-reference.json repointed to the 0.2.36 archive, SBOM, and bundle digests.

Second commit

Align the npm publish asset check with what the release actually uploads is a prerequisite for publishing to npm. The publish workflow demanded four release assets including release-manifest.json, which 992d6d3 removed 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.35 heading, 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.

DeliriumPulse and others added 2 commits August 7, 2026 00:11
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>
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This 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.

Changes

Security analysis and remediation

Layer / File(s) Summary
SAST foundation and analysis
packages/analyzer-engine/src/security/*
Adds parser contracts, a JavaScript/TypeScript/TSX parser, normalized AST handling, taint analysis, security rules, read-modify-write detection, bounded scanning, coverage metadata, and finding mapping.
Finding fixes and excluded-file scanning
packages/analyzer-engine/src/{fixes.ts,indexer.ts,secrets.ts,structure.ts,dependencies.ts,detect.ts,types.ts}
Adds deterministic secret, lockfile, README, and CI fixes. Generated-file content remains available for secret scanning.
Local SAST CLI integration
packages/cli/src/{local-sast.ts,analysis.ts,receipt.ts,types.ts}, packages/cli/test/{local-sast.test.ts,analysis-profile.test.ts,receipt.test.ts}
Runs the local SAST pass on selected production files, reports reduced coverage, maps findings, and preserves receipt compatibility.

CLI workflow changes

Layer / File(s) Summary
Inferred scope
packages/cli/src/{scope-inference.ts,policy.ts,cli.ts,analysis.ts,receipt.ts,types.ts}, packages/cli/test/scope-inference.test.ts
Infers bounded roots from task and changed-file context, classifies fully covered files, and records inference evidence in receipts.
Suggested fixes
packages/cli/src/{fix-suggestions.ts,hook-result.ts,hook-runtime.ts,receipt.ts,cli.ts}, packages/cli/test/{fix-suggestions.test.ts,fix-suggestion-hook.test.ts}
Renders safe suggested fixes, propagates the highest-severity suggestion through hooks, validates bounded output, and redacts fix bodies from hosted copies.
Hook resilience and diagnostics
packages/cli/src/{git-snapshot.ts,hook-baseline.ts,hook-runtime.ts,hooks.ts}, packages/cli/test/hooks.test.ts
Adds working-tree error detection, snapshot retries, non-blocking capture notices, promptless-turn handling, and CLI version-shadow diagnostics.
Verification setup and trust stores
packages/cli/src/{config.ts,setup.ts,verify-trust.ts}, packages/cli/test/{config.test.ts,verify-trust.test.ts,command-e2e.test.ts}
Adds structured verification detection, setup-footprint handling, blocker reporting, and XDG-aware trust-store resolution with legacy fallback.

Release and documentation

Layer / File(s) Summary
0.2.35 release metadata and documentation
README.md, CHANGELOG.md, packages/cli/CHANGELOG.md, packages/cli/package.json, packages/cli/scripts/build.mjs, public/downloads/*, release-reference.json, .github/workflows/publish-npm.yml
Updates version references, release notes, build settings, download metadata, SBOMs, checksums, attestation commands, and accepted release assets.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 59.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ⚠️ Warning The title describes a CLI release, but it specifies v0.2.36 while the PR objectives and changeset identify v0.2.35. Use the release version that matches the PR objectives and changeset, or update the changeset to consistently target v0.2.36.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The description includes all required sections and provides detailed scope, validation, privacy, release impact, and known-issue information.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch release/v0.2.35

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (4)
packages/analyzer-engine/src/security/analysis-scope.ts (1)

16-21: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Add a test that locks the rule cweKey invariant.

The current rule pack only uses CWE table keys, but RULE_META types cweKey as keyof typeof CWE instead of a narrower invariant. Add a test that checks every TAINT_SINKS and PATTERN_RULES cweKey exists in CWE so report-class sorting cannot regress to undefined.

🤖 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 win

The same-version case is not actually covered here.

The comment says "Same version: nothing to say", but the assertion is toBeUndefined(). installedCliVersion(bin) returns undefined because no @codetruss/cli manifest exists above node_modules/.bin/codetruss in this fixture. The test therefore covers the "no owning manifest" path, not the "manifest version equals CLI_VERSION" path.

inspectExecutableShadow documents that a repository-local install of the same version must not be reported as a shadow. That branch stays untested. Consider adding a manifest with version: CLI_VERSION above the resolved binary and asserting installedCliVersion returns CLI_VERSION while inspectHookDoctor emits 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 win

Consider a short delay between capture retries.

captureExactSnapshot retries immediately after WorkingTreeChangedError. 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 win

Consider covering the task-token rejection guards.

taskPathTokens rejects absolute paths, Windows drive prefixes, and .. or . segments. No case exercises those rejections. A task string such as Fix ../../etc/passwd or Fix /etc/passwd should produce no inferred root. The MAX_INFERRED_ROOTS cap 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

📥 Commits

Reviewing files that changed from the base of the PR and between 992d6d3 and 23dca6e.

📒 Files selected for processing (64)
  • .github/workflows/publish-npm.yml
  • CHANGELOG.md
  • README.md
  • packages/analyzer-engine/src/coverage.ts
  • packages/analyzer-engine/src/dependencies.ts
  • packages/analyzer-engine/src/detect.ts
  • packages/analyzer-engine/src/fixes.ts
  • packages/analyzer-engine/src/indexer.ts
  • packages/analyzer-engine/src/secrets.ts
  • packages/analyzer-engine/src/security/analysis-scope.ts
  • packages/analyzer-engine/src/security/cwe.ts
  • packages/analyzer-engine/src/security/engine.ts
  • packages/analyzer-engine/src/security/finding-map.ts
  • packages/analyzer-engine/src/security/js-parse/index.ts
  • packages/analyzer-engine/src/security/js-parse/lexer.ts
  • packages/analyzer-engine/src/security/js-parse/node.ts
  • packages/analyzer-engine/src/security/js-parse/parser.ts
  • packages/analyzer-engine/src/security/lang.ts
  • packages/analyzer-engine/src/security/local-profile.ts
  • packages/analyzer-engine/src/security/normalize.ts
  • packages/analyzer-engine/src/security/rmw.ts
  • packages/analyzer-engine/src/security/rules.ts
  • packages/analyzer-engine/src/security/taint.ts
  • packages/analyzer-engine/src/security/types.ts
  • packages/analyzer-engine/src/structure.ts
  • packages/analyzer-engine/src/types.ts
  • packages/cli/CHANGELOG.md
  • packages/cli/package.json
  • packages/cli/scripts/build.mjs
  • packages/cli/src/analysis.ts
  • packages/cli/src/cli.ts
  • packages/cli/src/config.ts
  • packages/cli/src/fix-suggestions.ts
  • packages/cli/src/git-snapshot.ts
  • packages/cli/src/hook-baseline.ts
  • packages/cli/src/hook-result.ts
  • packages/cli/src/hook-runtime.ts
  • packages/cli/src/hooks.ts
  • packages/cli/src/local-sast.ts
  • packages/cli/src/policy.ts
  • packages/cli/src/receipt.ts
  • packages/cli/src/scope-inference.ts
  • packages/cli/src/setup.ts
  • packages/cli/src/types.ts
  • packages/cli/src/verify-trust.ts
  • packages/cli/test/analysis-profile.test.ts
  • packages/cli/test/command-e2e.test.ts
  • packages/cli/test/config.test.ts
  • packages/cli/test/fix-suggestion-fixture.ts
  • packages/cli/test/fix-suggestion-hook.test.ts
  • packages/cli/test/fix-suggestions.test.ts
  • packages/cli/test/hooks.test.ts
  • packages/cli/test/local-sast.test.ts
  • packages/cli/test/receipt.test.ts
  • packages/cli/test/scope-inference.test.ts
  • packages/cli/test/verify-trust.test.ts
  • public/downloads/codetruss-cli-0.2.35.sbom.cdx.json
  • public/downloads/codetruss-cli-0.2.35.tgz
  • public/downloads/codetruss-cli-0.2.35.tgz.sha256
  • public/downloads/codetruss-cli-latest.json
  • public/downloads/codetruss-cli-latest.sbom.cdx.json
  • public/downloads/codetruss-cli-latest.tgz
  • public/downloads/codetruss-cli-latest.tgz.sha256
  • release-reference.json
💤 Files with no reviewable changes (1)
  • .github/workflows/publish-npm.yml

Comment thread packages/analyzer-engine/src/fixes.ts
Comment thread packages/analyzer-engine/src/fixes.ts
Comment thread packages/analyzer-engine/src/security/engine.ts
Comment thread packages/analyzer-engine/src/security/local-profile.ts
Comment thread packages/cli/src/config.ts
Comment thread packages/cli/src/git-snapshot.ts
Comment thread packages/cli/src/local-sast.ts
Comment thread README.md
@DeliriumPulse

Copy link
Copy Markdown
Collaborator Author

Blocked: two required Windows checks fail on a real defect in the shipped 0.2.35 source

windows-latest · Node 22 and windows-latest · Node 24 are both required on main and both fail. This is not flake and not an artifact of the sync — it is a genuine bug in the source this tag would attest.

AssertionError: expected 'src\users.ts' to be 'src/users.ts'
 ❯ test/analysis-profile.test.ts:39:33

AssertionError: expected 'src\generated-config.ts' to be 'src/generated-config.ts'
 ❯ test/command-e2e.test.ts:728:30

Root cause

packages/analyzer-engine/src/indexer.ts:103

state.paths.push(relative(root, full))

node:path's relative() returns platform-native separators, so on Windows the engine's directory walk seeds the index with src\users.ts. Every finding's filePath derives from those entries, so a signed receipt produced on Windows carries backslash paths.

Every other path surface in the CLI normalizes explicitly — receipt.ts, git-snapshot.ts, policy.ts, scope-inference.ts, and hook-runtime.ts all call .replaceAll('\\', '/'). The engine's own walk is the single place that does not.

Not a 0.2.35 regression

That line is byte-identical at v0.2.30, which merged with all nine checks green. What is new is the coverage: test/analysis-profile.test.ts (0.2.35) and the generated-banner assertion in test/command-e2e.test.ts (0.2.33) are the first tests to call analyzeRepository() directly. Every older test reaches the index through Git, and git status / ls-files emit forward slashes, which masked it.

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 here

Two independent reasons, either sufficient:

  1. indexer.ts is bundled into dist/cli.cjs. Changing it makes pnpm release:artifact produce a different digest, so pnpm release:verify fails against the immutable a58a392e… archive and CI goes red anyway. A source fix is not even mechanically available to the mirror.
  2. It would break the property this repository exists to provide — that the source here builds to exactly the bytes codetruss.com serves.

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 it

A 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 indexRepository() on the direct-walk path, since Git masks it everywhere else.

Everything else in this PR is verified and green: eight of nine checks pass, the rebuild reproduces the published digest exactly, and release:verify confirms it byte-for-byte against the website archive. The branch is ready to merge the moment the Windows defect is resolved upstream.

@DeliriumPulse

Copy link
Copy Markdown
Collaborator Author

Impact assessment, so this gets the right priority rather than the loudest one

Traced 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. computeVerdict filters input.findings by severity and category. It never matches finding paths against changed-file paths, so a backslash path still blocks exactly as it should. That is why windows-latest still passes expect(result.status).toBe(2) and expect(receipt.verdict).toBe('FAILED') on the AUTO-GENERATED test and only fails four lines later on the path string. No Windows user is getting a green light they should not have.

What it does break is the receipt as stable evidence. The second failing test runs the full CLI (runCli(root, ['review', '--staged', …])) against a real Git repository, not a direct analyzeRepository() call, so this is the shipped path and not a test-only artifact. Within one signed receipt on Windows:

  • the Changed files table comes from Git via git-snapshot.ts, which normalizes with .replaceAll('\\', '/')src/generated-config.ts
  • the analyzer findings table comes from the engine walk, which does not — src\generated-config.ts

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. changedFindings() in analysis.ts:173 compares finding.filePath to file.path with === and startsWith. On Windows that comparison is false for every path below the repository root. It currently has no caller in packages/cli/src, so nothing depends on it today — but it is exported, and wiring it into the verdict later without normalizing first would turn this cosmetic bug into the bypass it currently is not.

None of this changes the recommendation: normalize at the source in indexer.ts:103, which fixes the receipt, the two tests, and changedFindings() at once.

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>
@DeliriumPulse DeliriumPulse changed the title Release CodeTruss CLI v0.2.35 Release CodeTruss CLI v0.2.36 Aug 7, 2026
@DeliriumPulse

Copy link
Copy Markdown
Collaborator Author

Retargeted to v0.2.36 — the Windows blocker is closed

Upstream 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 relative() also silently disabled vendored-root exclusion on Windows: vendoredRoot() matches ^(?:\.claude|vendor|…)(?:\/|$), which a backslash path never satisfies, so vendored payloads re-entered analysis. Three consequences from one line, not the two visible here.

Re-verified against the new release commit

  • 112 files mirrored, zero content mismatches, zero file-mode mismatches
  • Byte-reproducibility: rebuild yields 953e3f48725a7471043b48a55aedd33ef78854615a341dd95881d90eeae3e814, release:verify confirms it against the immutable website archive, and git status shows no drift in public/downloads after the rebuild
  • 295 tests / 24 files, up from 292 / 23
  • test:install clean

The regression test was falsification-checked, not trusted

Reverting only the normalization and re-running indexer-path-separators.test.ts fails 2 of 2; restoring it makes them pass. The suite swaps relative for its win32 counterpart while leaving join and the filesystem real, so it reproduces the Windows separator on a POSIX host, and indexer.test.ts carries an unmocked nested-path assertion that will run for real on the Windows leg here.

0.2.35 artifacts dropped rather than kept

Every other archive in public/downloads corresponds to a tagged, attested release. v0.2.35 will never be tagged, so leaving its archive here would advertise bytes with no release and no attestation behind them.

README rechecked against the 0.2.36 binary

--help still lists verify-policy trust-key; profile is still local-registry-v2; both receipt security sections are byte-identical to the 0.2.35 wording after whitespace normalization. The excerpt is a fresh 0.2.36 run with its own evidence trees and policy digest.

@DeliriumPulse

Copy link
Copy Markdown
Collaborator Author

Upstream triage: the 0.2.36 changelog overwrote the 0.2.35 header

Found 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:

6da4cbc (0.2.35) 18d9ecf (0.2.36)
## 0.2.35, ## 0.2.34, ## 0.2.33 ## 0.2.36, ## 0.2.34, ## 0.2.33

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 packages/cli/CHANGELOG.md is one of the eight files verify-release.mjs byte-compares against the tarball, so this ships inside the archive users download, and it erases a release from the published history.

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 release:verify fail against the immutable archive, and a wording fix costs a version bump. Since a 0.2.37 is already in flight for the Windows pid-recycling CI crash, that is the natural place to restore the ## 0.2.35 heading and move its bullets back under it.

@DeliriumPulse
DeliriumPulse merged commit ed7debb into main Aug 7, 2026
10 checks passed
@DeliriumPulse
DeliriumPulse deleted the release/v0.2.35 branch August 7, 2026 05:56
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