Releases: CodeTruss/codetruss-cli
Release list
CodeTruss CLI v0.2.46
-
A repository's own scope globs could crash the review that reads them.
--allowand--denyare matched withminimatch, which expands brace
groups throughbrace-expansion, and the bundled copy was 5.0.7. That version
bounds the number of expansions at 100,000 but not their length, so a
pattern like{a,b}repeated a few hundred times keeps the count under the
cap while making every result as long as the pattern has groups. The arrays
built while combining them exhaust the heap and abort the process
(CVE-2026-14257, and the incomplete fix for it). This is reachable here
becauseallowanddenyare read from the scanned repository's
.codetruss.ymland validated only as non-empty strings: on 0.2.45's shipped
bundle, a 7.5 KB glob in that file endscodetruss reviewwith an uncatchable
out-of-memory abort rather than a verdict.brace-expansionmoves to 5.0.9,
which bounds the intermediate arrays as well, and the same input now returns a
verdict. Scope matching is otherwise unchanged.What this is not: nothing is disclosed, nothing is altered, and no verdict
changes. A pattern cannot reach the matcher from a diff, a filename, or the
network — only from flags you typed or a config file in the tree you pointed
the CLI at. The worst outcome was a local tool dying instead of reporting. -
Dependency floors that were pinned to vulnerable versions have been
raised.minimatchmoves to 10.2.6, whose own^5.0.8requirement means
brace-expansioncan no longer resolve below the patched line.postcsswas
held at 8.5.16 by a workspace override added as a security pin months ago and
never refreshed; it moves to 8.5.26, which carriesnanoid3.3.18 with it.
Neitherpostcssnornanoidis in the shipped bundle — they reach the
repository only through the test runner — so this changes nothing you install.
Install
curl -fsSL https://codetruss.com/install.sh | shWindows (PowerShell):
irm https://codetruss.com/install.ps1 | iexVerify what you installed
sha256 047dc9915d8c0594c913d1c29813edeecb7c71b0fd4e594ab0221139955ad643
gh attestation verify codetruss-cli-0.2.46.tgz --repo CodeTruss/codetruss-cliThe archive ships with a CycloneDX SBOM and SLSA build provenance. You
should not have to take our word for what is in it.
CodeTruss CLI v0.2.45
-
A PASS was reachable by typing.
codetruss-ignore: <reason>exists so a
developer can dismiss a finding beside the code it is about, and the one
promise it makes is that "nothing was found" can never be reached by editing
text. It could be. A dismissed finding stops gating the verdict — that is what
dismissing is for — and the marker was honored wherever those characters
appeared on the finding's own line, including inside a string literal. A
minified bundle is one physical line, so a single planted string dismissed
every credential finding in the file, and the verdict followed. The marker is
now read only where a person could have written it. It must sit in a COMMENT,
decided by the same classifier the comment analyzers ship, which separates
comments from code and from strings; in a language that classifier does not
cover, the marker is honored only in the placement that needs no classifier —
a line whose every preceding character is whitespace or comment punctuation.
Markers are no longer read out of generated, vendored or minified content at
all: that text had no author who could have meant it. And the reason itself is
now redacted against the credential patterns before it is quoted. A reason runs
to the end of its line, so a marker written just before a connection string
harvested the password verbatim onto a signed receipt and synced it to the
hosted database — the secret scanner's promise that values never leave it now
holds for the text other passes copy out of the repository too. -
Oversized-file findings counted comments as code. The size analyzer
measured non-blank lines and then printed the number as a fact: "parser.ts has
2202 lines of code", in a document a customer can disprove withwc. Two of
this repository's own HIGH findings existed only because of it — the same two
files measure 1995 and 1996 lines of code, both below the threshold that made
them HIGH — and the overcount inflated every oversized finding, because the
800-line gate read the same number. Both the gate and the printed number now
come from the classifier. Nothing stops being reported that a refactor would
have helped: a file with 800 lines of code has 800 lines of code however they
are counted. What stops is documentation manufacturing severity. -
Redirects that are not redirect calls are now findings. The open-redirect
rule matched two method names,redirectandsendRedirect. Most navigation
in a React or Next.js codebase is neither: it is<Link href={returnTo}>,
<form action={next}>,location.href = next,location.assign(...)or
router.push(...), and none of those is a call to anything the rule was
looking for. It missed a live open redirect in our own repository on that
basis. Those shapes are sinks now. The call forms are gated on their receiver,
becausepushandreplaceunqualified areArray.prototype.pushand
String.prototype.replace; the binding forms fire only where the untrusted
value IS the navigation target — a value, or a field of the request itself —
because reading taint off a record that a route segment merely looked up turns
every call-to-action on a[slug]page into an open redirect. Measured against
this repository, the narrow rule adds the real defect plus two links a reviewer
should confirm; the wide one added five more that no reviewer should have to.
Install
curl -fsSL https://codetruss.com/install.sh | shWindows (PowerShell):
irm https://codetruss.com/install.ps1 | iexVerify what you installed
sha256 0ced9ca92b28a96faf997a1c45911fd2dd77bb4107fdd3153d88692139c714ce
gh attestation verify codetruss-cli-0.2.45.tgz --repo CodeTruss/codetruss-cliThe archive ships with a CycloneDX SBOM and SLSA build provenance. You
should not have to take our word for what is in it.
CodeTruss CLI v0.2.44
- The person you hand a receipt to can now check it. Until this release a
receipt could only be verified by the repository that produced it:codetruss verifymeasures a receipt against the signing keys the local.codetruss.yml
pins, so the client, auditor, or acquirer the evidence was written for got
receipt signer <fp> does not match trusted key <fp>and stopped there. That
is most of the point of handing someone a receipt, and it did not work. The
gap was concrete rather than theoretical: publishing one of our own receipts
publicly required shipping a bespoke standalone verifier alongside it, because
the CLI would not check another install's receipt.codetruss verify-receipt <receipt.json|dir>is the supported path. It needs nothing but the files —
no checkout, no account, no configuration — and it reports two claims
separately, because they are two different facts and merging them would be a
lie. Integrity is that these bytes have not changed since they were
signed; it is established from the receipt alone, by checking the signature
under the key the receipt carries, reproducing the Markdown byte-for-byte from
the signed JSON, and matching the recorded digests. Provenance is that a
party you trust signed them, and it is established only against a
--public-keyyou obtained from that party some other way. A receipt vouching
for its own key proves nothing about who wrote it — forging one takes a
keypair and a minute — so a run without a supplied key can never print a
verified result or exit 0. That ceiling is the feature, not a missing half of
one. The exit codes carry the distinction into scripts: 0 for both claims, 1
for bytes that are intact but unattributed, 2 for bytes that are not what was
signed. When integrity fails, provenance is not evaluated at all and says so,
rather than printing a key match over altered bytes. Evidence a publisher
withheld — usually the patch, the only part of a receipt that quotes source —
is reported as unchecked next to the digest the signature does cover, and the
integrity line names the hole instead of reading clean.codetruss verifyis
unchanged and still requires a trusted key; its refusal now names the command
that can check a foreign receipt instead of dead-ending. Both paths run one
shared check list against one shared set of accepted Markdown renderings, so
neither can drift into checking less than it claims, and every superseded
profile wording stays reproducible, so receipts signed by older releases keep
verifying byte-for-byte.
Install
curl -fsSL https://codetruss.com/install.sh | shWindows (PowerShell):
irm https://codetruss.com/install.ps1 | iexVerify what you installed
sha256 8a405b77b2042c8daca6f2def782fa8e38a2ffcec4ebe9643631d3491af50884
gh attestation verify codetruss-cli-0.2.44.tgz --repo CodeTruss/codetruss-cliThe archive ships with a CycloneDX SBOM and SLSA build provenance. You
should not have to take our word for what is in it.
CodeTruss CLI v0.2.43
- A release can no longer reach you carrying code that does not compile.
Three type errors shipped in 0.2.42 and stopped the following release at its
first build step. The errors were trivial in themselves: a finding category
that does not exist, and a test helper that was not updated when one of the
fields it passes became required. Why they shipped is the part worth fixing.
The CLI's test sources were compiled by no gate upstream of the release job —
linting does not typecheck, the website build typechecks the website, and the
test run cannot stand in for either, because esbuild strips types without ever
checking them. A suite reports 12 of 12 passing with a type error sitting in
the file it just ran. The release job was the first step in the chain to
compile those files, which meant a failed build was the earliest available
symptom, and it arrived only once a version was already a release candidate. A
typecheckgate now runs that same compiler over those same files ahead of the
tests, in both the local gate and continuous integration, so this class of
defect surfaces at the edit that causes it instead of at the release it blocks.
Install
curl -fsSL https://codetruss.com/install.sh | shWindows (PowerShell):
irm https://codetruss.com/install.ps1 | iexVerify what you installed
sha256 e759a7ffce29ae0a0ba61aeb9bd8819cd75adb114abd1410a7e309d6f8a923c3
gh attestation verify codetruss-cli-0.2.43.tgz --repo CodeTruss/codetruss-cliThe archive ships with a CycloneDX SBOM and SLSA build provenance. You
should not have to take our word for what is in it.
CodeTruss CLI v0.2.41
- You can dismiss a finding you have judged wrong, in the place the judgement
belongs. Acodetruss-ignore: <reason>comment on a finding's own line, or
on a comment-only line directly above it, marks that finding as dismissed. A
marker trailing a line of code governs only that line, so it can never reach a
neighbouring finding its author never looked at. A dismissal never deletes
anything. The finding, its location, and the exact reason its author gave
all survive into the signed receipt under a "Suppressed findings" heading, and
the reader decides whether the reason is good — a receipt whose evidence could
be erased by editing a comment would not be evidence, and "nothing was found"
must never be reachable that way. The reason is mandatory for the same reason:
the reason is the output, and "someone decided this was fine" is not
evidence. A barecodetruss-ignoretherefore dismisses nothing and is
reported by location, so a developer who wrote one finds out why it did
nothing. Receipts that dismissed nothing are unchanged, byte for byte. - Python SQL injection is now caught through a cursor held in a local.
cur = conn.cursor()thencur.execute(f"... {user_input}")— the canonical
psycopg/sqlite3/MySQLdb two-step — was invisible, because the sink test was
lexical andcurdoes not read as a database receiver. The receiver is now
resolved to its binding, so a name bound to a.cursor()call counts however
it is spelled, includingwith conn.cursor() as cur:.executemanyjoins
execute;execstays name-gated, since a bare.exec()is far more often
RegExp.prototype.execthan SQL. Generic receiver names were not loosened, so
nothing else lost precision. - A stalled grammar-pack download now fails with a sentence instead of
hanging.codetruss grammars installis a foreground command someone is
watching, andfetchwill wait out a server that writes one byte and holds
the socket open forever. Two clocks bound it — a whole-transfer budget and an
idle budget — and the reason it was abandoned survives into the error, rather
than the bare "This operation was aborted" an abort produces on its own. Both
are generous enough that a slow connection is never mistaken for a hostile
origin. - A grammar pack's artifacts are bound by role, not by name prefix. The
loader picked the first file whose name started withtree-sitter-, so a pack
carrying an extra artifact ordered ahead of the real grammar would have had
the extra one loaded — and the pin verifier, which only proved each digest
appeared somewhere, would not have caught it. Each role must now be filled by
exactly one pinned artifact; an ambiguous pack does not resolve at all. A pack
that fails this reports a runtime failure rather than a digest failure, so a
defect in the CLI's own pin never publishes a receipt accusing the user's
install of tampering. SECURITY.mdnow states what the grammar-pack digest pin does not cover.
The pin protects against a compromised download origin, which is what it was
built for. It cannot protect against a compromised build: the pin, the
published artifact, and the offline check that compares them all derive from
the samenode_moduleson the release machine.pnpm grammars:attestnarrows
that window — it checks the lockfile digest against the npm registry, verifies
the registry's signature, and compares the downloaded tarball against the
files the pack is cut from — and the document says plainly that it does not
close it.hooks doctornames which fields drifted and what to run. It reported
only that an installed handler "differs", which reads identically for a config
installed several versions ago and a deliberate hand-edit, and named no
remedy. It now lists the drifted field names — enough to diagnose, without
putting handler command text in the message — and names the reinstall command.
This repository's own committed.codex/hooks.jsonwas the config that
exposed it: several versions stale, missingcore.longpaths=trueand pinned
to the old Stop timeout. It has been refreshed, and a test now compares the
committed hook configuration against what the installer actually writes, so it
cannot drift again unnoticed.- Build attestation is verified against the CodeTruss organisation. The CLI
repository moved from theDeliriumPulseaccount, and every release still in
circulation has been re-attested under the organisation, so one command
verifies all of them:gh attestation verify <artifact> --repo CodeTruss/codetruss-cli. The transferred--repo DeliriumPulse/…slug
returns HTTP 404 and is no longer advertised anywhere. The published manifest,
the verifier, and the verifier's own tests now derive that command from a
single module rather than each restating it; the Homebrew tap, plugin
marketplace and support links follow the organisation too. - Internal:
hooks.tsis now seven modules behind an unchanged public
surface. Installation, uninstallation, the doctor, the pre-commit block, the
agent handler shapes, the agent runner and executable resolution each have
their own file. No behaviour changed, and the hook tests are unmodified by
design — an unchanged test suite passing over a moved implementation is the
evidence that the move was only a move.
Install
curl -fsSL https://codetruss.com/install.sh | shWindows (PowerShell):
irm https://codetruss.com/install.ps1 | iexVerify what you installed
sha256 cb0b9d69f15e7113a6a523a6b139ec3150b7d07459e5242b2c047fc9aac57d04
gh attestation verify codetruss-cli-0.2.41.tgz --repo CodeTruss/codetruss-cliThe archive ships with a CycloneDX SBOM and SLSA build provenance. You
should not have to take our word for what is in it.
CodeTruss CLI v0.2.40
- Python can now be analyzed locally, if you ask for it.
codetruss grammars install pythondownloads theweb-tree-sitterruntime and the
compiled Python grammar (722 KB) into your data directory — XDG on macOS and
Linux,LOCALAPPDATAon Windows. Nothing is bundled in the tarball, nothing
is fetched during an analysis, and no other command installs it for you. The
CLI ships a hand-written JavaScript parser precisely because these grammars
are several times its entire release budget, and that trade is unchanged for
anyone who does not run this command.codetruss grammars list|status| uninstallround out the group;statusexits non-zero when a pack is
missing or fails verification, so it can gate a setup script. - The pack is pinned, verified as it arrives, and verified again every time
it is loaded. Each artifact's SHA-256 is compiled into the CLI at build
time. The download is hashed as it streams, with the pinned length enforced
mid-stream so a wrong or hostile origin cannot write an unbounded file to
disk; artifacts land in a scratch directory and are moved into place only
after every one of them verifies, so a pack directory is never half-installed.
The only download origin iscodetruss.com— no third-party CDN, and
redirects are refused. Hashing is streamed in-process, never shelled out to
shasumorGet-FileHash. Every failure — absent, truncated, over-long,
wrong digest, unreadable, or an unexpected extra file in the pack directory —
resolves to "pack unavailable", and the run reports Python as skipped. There
is no path on which unverified bytes are executed. - Python runs the complete rule pack, not the reduced JavaScript subset.
That subset exists because a hand-written parser might disagree with
tree-sitter, and only rules proven to agree were admitted. A grammar pack is
the hosted parser and the hosted grammar, so there is no divergence to guard
against — and narrowing it would report less than the same code receives in a
hosted scan, for no gain in precision. Command injection, path traversal,
SSRF and insecure deserialization are checked in Python locally; they remain
unchecked in JavaScript, TypeScript and TSX, and the receipt keeps saying so. - Verified against the hosted path over 233 real Python files — the
full-stack FastAPI template, three further repositories, and a synthetic
fixture covering each rule class. Both parsers produced the same 11 findings,
with zero divergence in either direction. - Receipts move to the
local-registry-v4profile, which states what the run
actually did about Python. The pass set is unchanged from v3; the wording
had to change, because v3 says flatly that the local pass covers "JavaScript,
TypeScript and TSX only" and that Python received no security analysis, and
that is false whenever a pack is installed. There are now three
distinguishable statements instead of one frozen sentence: absent names
the Python file count and the command that would cover them, verified
names the rule pack and the file count while keeping the JavaScript subset's
limits scoped to JavaScript, and a failed pack now says which kind of
failure it was — a digest mismatch (the pack does not match what this CLI
published, so reinstall), a runtime that would not start on this machine even
though the digests matched, or a scan that threw partway and had its partial
results discarded. Only a real digest mismatch renders the tampering sentence;
an out-of-memory error no longer accuses your install of not matching the
published digests. Every failure branch closes with the provable "No findings
from this pack were reported" in place of the wider absolute claim.
local-registry-v3keeps a frozen renderer, so receipts signed by 0.2.39
still verify byte-for-byte. - The bytes that are verified are now the exact bytes that execute. The
loader used to hash each artifact by path and then re-open the same path to
require()it, so the file that was hashed and the file that ran were two
separate reads with a window between them — three digests and a directory
listing wide enough for another process with write access to the pack
directory to swap a hostiletree-sitter.jsin after the check and have it
executed.inspectGrammarPacknow reads each artifact once and returns the
buffer it hashed; the runtime is compiled from that buffer and the two WASM
artifacts are handed toweb-tree-sitteras in-memoryUint8Arrays
(wasmBinaryandLanguage.load), so nothing is ever resolved from a path a
second time. Artifacts are openedO_NOFOLLOWand rejected unless they are
regular files; a symlinked pack root, a pack root not owned by the current
user, or one writable by group or other is refused, and a loose root created
by an earlier CLI is tightened to0700on install. A local same-user race
that reliably executed attacker code against the previous loader now fails
every attempt. - Fixed: Python was silently dropped from the second half of every review.
The tree-sitter runtime reassigns its own entry in Node's module cache while
initializing, so loading it a second time in one process returned the wrong
object. A review analyzes twice — once for the baseline tree, once for the
final tree — which meant the final analysis quietly failed to load the grammar
and reported Python as unanalyzable even with a healthy pack installed. The
runtime is now loaded once per process. Digests are still re-checked on every
load; only the runtime construction is reused. - Fixed: the Windows data directory was resolved with POSIX path rules.
LOCALAPPDATAwas checked with a path test that treatsC:\Users\…as
relative anywhere other than Windows, which made the branch correct on Windows
and unverifiable everywhere else. It now names the Windows path flavour
explicitly, and is covered by a test that runs on every platform.
Install
curl -fsSL https://codetruss.com/install.sh | shWindows (PowerShell):
irm https://codetruss.com/install.ps1 | iexVerify what you installed
sha256 5d64313b8b60acbd1f93e2246557967885a98fdc8c486ea7b2a6417fd8acdac2
gh attestation verify codetruss-cli-0.2.40.tgz --repo CodeTruss/codetruss-cliThe archive ships with a CycloneDX SBOM and SLSA build provenance. You
should not have to take our word for what is in it.
CodeTruss CLI v0.2.39
Two analyzers join the registry, which now holds 15. Both target things AI-written code does that compile fine and pass tests.
Comment Signal reports comments that repeat the line beneath them, and comments that narrate an edit (// Updated to use the new auth middleware), address the reader, or describe the work as provisional (// In a real app you would verify this) — the last of which no TODO scan can see, because it carries no marker.
Comment density is reported as a metric and is never a finding. Across the design study the most densely commented codebase produced zero restating comments and the sparsest produced sixty-one, so a density rule would penalise exactly the code worth rewarding.
Speculative Structure reports exported values that appear in no other file, and catch blocks whose entire body logs an error and rethrows it unchanged. Export findings are worded as candidates, because a symbol reached through a dynamic import looks identical to this pass.
Both analyzers emit only INFO and LOW, and the verdict escalates at MEDIUM — so neither can turn a PASS into a REVIEW_REQUIRED or a FAILED. A comment that repeats the line below it is not a reason to stop an agent mid-turn.
Both came from a study that ran candidate rules against eight real repositories and kept only what survived. Rules that measured badly were dropped, including several that seemed obviously right beforehand.
Install
curl -fsSL https://codetruss.com/install.sh | shVerify what you installed
sha256 feb9a7454abaf2c25bdeade2a6e638137290df8c0725a447e3aca04aef2bd8f0
gh attestation verify codetruss-cli-0.2.39.tgz --repo CodeTruss/codetruss-cliCodeTruss CLI v0.2.38
Scope drift detection was going quiet on exactly the run where it matters most.
When a repository has no configured scope, a single changed file was allowed to establish its own directory as approved scope — which is circular, since the scope was inferred from the very change being judged. On a first run with no configuration, an agent that wandered into an unrelated directory produced no signal at all.
The single-file allowance is now the whole turn's fallback rather than something each directory can claim for itself: it applies only when admitting it would be the turn's only inferred root. A one-file turn still infers, and a cohesive single-directory turn still infers at any file count. A turn that touches two unrelated directories now reports drift, as it did before inference existed.
Measured on the seeded fixture: with 0.2.36 the drift was reported only when scope was passed explicitly; with 0.2.38 it is reported on the bare default run.
Install
curl -fsSL https://codetruss.com/install.sh | shVerify what you installed
sha256 8b6093e502e10402fa6f84cd2cac86c7db9c3e54502c7566f1635ce993b32ff7
gh attestation verify codetruss-cli-0.2.38.tgz --repo CodeTruss/codetruss-cliCodeTruss CLI v0.2.37
Two fixes that came out of chasing a Windows CI failure to its root, plus a guard so one class of release mistake cannot happen again.
Never signal a recorded process id. On Windows, a freed pid is recycled within milliseconds. Both process-tree killers ran taskkill on a leader pid recorded earlier — including from the child's own exit handler, where the leader is dead by definition — which could force-kill whatever unrelated process had inherited that pid. In CI it killed a freshly forked test worker. Termination is now gated on a liveness check through our own process handle, which cannot be misdirected.
The 0.2.35 changelog entry is restored. The 0.2.36 release overwrote its heading, so the release that moved security analysis onto your machine shipped with no entry of its own. It is back, byte-identical to what it said.
The release build now refuses to cut a version whose changelog is wrong — a missing heading, a broken version chain, a duplicate. The bug above is why.
Install
curl -fsSL https://codetruss.com/install.sh | shVerify what you installed
sha256 082a03fe5dd2d9c516acc76504e0bb45bc7d3b5a1fa7308fb2d627ce07a3c627
gh attestation verify codetruss-cli-0.2.37.tgz --repo CodeTruss/codetruss-cliCodeTruss CLI v0.2.36
A Windows correctness release. The repository walk emitted whatever path separator the host used, so a Windows run produced src\users.ts while every other surface in the CLI — receipts, git snapshots, policy globs, scope inference — used src/users.ts.
That had three consequences, all fixed by normalizing at the source:
- A signed receipt's findings table and its changed-files table named the same file two different ways. A receipt disagreed with itself.
- Receipt bytes differed by platform for an identical tree, so a cross-platform reproduction could not match.
- Vendored-directory exclusion (
.claude/,vendor/, …) silently stopped matching on Windows, pulling tooling payloads back into analysis.
changedFindings() now compares paths separator-agnostically as well, as defense in depth.
Install
curl -fsSL https://codetruss.com/install.sh | shVerify what you installed
sha256 953e3f48725a7471043b48a55aedd33ef78854615a341dd95881d90eeae3e814
gh attestation verify codetruss-cli-0.2.36.tgz --repo CodeTruss/codetruss-cliThe archive ships with a CycloneDX SBOM and SLSA build provenance. You should not have to take our word for what is in it.