Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 85 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
CodeTruss CLI follows semantic versioning. Release artifacts and their SHA-256
checksums are published at <https://codetruss.com/downloads/codetruss-cli-latest.json>.

The current public release is [v0.2.39 on GitHub](https://github.com/DeliriumPulse/codetruss-cli/releases/tag/v0.2.39),
The current public release is [v0.2.40 on GitHub](https://github.com/DeliriumPulse/codetruss-cli/releases/tag/v0.2.40),
distributed from <https://codetruss.com/downloads/codetruss-cli-latest.json>.
The npm `latest` tag is still
[`@codetruss/cli@0.2.24`](https://www.npmjs.com/package/@codetruss/cli/v/0.2.24):
Expand All @@ -16,6 +16,90 @@ were superseded before distribution.

No unreleased changes.

## 0.2.40 — 2026-08-07

- **Python can now be analyzed locally, if you ask for it.** `codetruss
grammars install python` downloads the `web-tree-sitter` runtime and the
compiled Python grammar (722 KB) into your data directory — XDG on macOS and
Linux, `LOCALAPPDATA` on 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|
uninstall` round out the group; `status` exits 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 is `codetruss.com` — no third-party CDN, and
redirects are refused. Hashing is streamed in-process, never shelled out to
`shasum` or `Get-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-v4` profile, 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-v3` keeps 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 hostile `tree-sitter.js` in after the check and have it
executed. `inspectGrammarPack` now reads each artifact once and returns the
buffer it hashed; the runtime is compiled from that buffer and the two WASM
artifacts are handed to `web-tree-sitter` as in-memory `Uint8Array`s
(`wasmBinary` and `Language.load`), so nothing is ever resolved from a path a
second time. Artifacts are opened `O_NOFOLLOW` and 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 to `0700` on 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.**
`LOCALAPPDATA` was checked with a path test that treats `C:\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.

## 0.2.39 — 2026-08-07

- **Two analyzers join the registry, which now holds 15.** Both come from a
Expand Down
22 changes: 14 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ To pin an exact version, install the immutable archive directly:

```bash
npm install --global --ignore-scripts --no-audit --no-fund \
https://codetruss.com/downloads/codetruss-cli-0.2.39.tgz
https://codetruss.com/downloads/codetruss-cli-0.2.40.tgz
```

The `@codetruss/cli` package on the npm registry is published as a separate,
Expand Down Expand Up @@ -121,6 +121,7 @@ codetruss sync [id|latest] [--dry-run]
codetruss auth login|status|logout
codetruss verify-policy [status|trust|trust-key|revoke]
codetruss hooks install|status|doctor|uninstall [pre-commit|claude|codex|all]
codetruss grammars list|status|install|uninstall [python]
```

`verify-policy status`, `trust`, and `revoke` govern whether the repository's
Expand Down Expand Up @@ -177,7 +178,7 @@ be rechecked later with `codetruss verify latest`. Every receipt states the
detection gaps in its own body, so a `PASS` is never mistaken for a security
clearance. Abridged from a real 0.2.36 run, so it prints the `local-registry-v2`
profile and its thirteen-analyzer wording. A run on this release prints
`local-registry-v3` and fifteen; 0.2.39 keeps the v2 renderer frozen so the
`local-registry-v4` and fifteen; 0.2.40 keeps the v2 renderer frozen so the
receipt below still verifies byte-for-byte as signed:

```markdown
Expand Down Expand Up @@ -231,10 +232,15 @@ run found nothing new. It is not a statement that this change is secure.

Since 0.2.35 the security rule pack and its taint solver run locally and
offline over JavaScript, TypeScript and TSX — the same engine as the hosted
audit, not a reimplementation. The rest of the rule pack, every other language,
and the symbol graph remain hosted-only, and the receipt names them rather than
leaving their absence to be inferred. Local security findings are
`REVIEW_REQUIRED` at most; they never fail a verdict on their own.
audit, not a reimplementation. Since 0.2.40 Python joins them if you ask for it:
`codetruss grammars install python` fetches a pinned, digest-verified grammar
pack, and Python is then analyzed with the complete rule pack rather than the
reduced JavaScript subset. Nothing is bundled in the tarball and nothing is
fetched during an analysis. The rest of the rule pack for the JavaScript family,
every language without a pack, and the symbol graph remain hosted-only, and the
receipt names them rather than leaving their absence to be inferred. Local
security findings are `REVIEW_REQUIRED` at most; they never fail a verdict on
their own.

Where a finding's own evidence determines a single correct change, the receipt
also carries a **Suggested fixes** section with a diff and a required safety
Expand Down Expand Up @@ -316,8 +322,8 @@ clean global install.
Verify a downloaded release yourself:

```bash
gh attestation verify codetruss-cli-0.2.39.tgz --repo DeliriumPulse/codetruss-cli
shasum -a 256 -c codetruss-cli-0.2.39.tgz.sha256
gh attestation verify codetruss-cli-0.2.40.tgz --repo DeliriumPulse/codetruss-cli
shasum -a 256 -c codetruss-cli-0.2.40.tgz.sha256
```

Maintainers should follow [docs/RELEASE.md](docs/RELEASE.md). Tag-driven GitHub
Expand Down
84 changes: 84 additions & 0 deletions packages/cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,90 @@ checksums are published at <https://codetruss.com/downloads/codetruss-cli-latest

## Unreleased

## 0.2.40 — 2026-08-07

- **Python can now be analyzed locally, if you ask for it.** `codetruss
grammars install python` downloads the `web-tree-sitter` runtime and the
compiled Python grammar (722 KB) into your data directory — XDG on macOS and
Linux, `LOCALAPPDATA` on 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|
uninstall` round out the group; `status` exits 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 is `codetruss.com` — no third-party CDN, and
redirects are refused. Hashing is streamed in-process, never shelled out to
`shasum` or `Get-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-v4` profile, 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-v3` keeps 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 hostile `tree-sitter.js` in after the check and have it
executed. `inspectGrammarPack` now reads each artifact once and returns the
buffer it hashed; the runtime is compiled from that buffer and the two WASM
artifacts are handed to `web-tree-sitter` as in-memory `Uint8Array`s
(`wasmBinary` and `Language.load`), so nothing is ever resolved from a path a
second time. Artifacts are opened `O_NOFOLLOW` and 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 to `0700` on 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.**
`LOCALAPPDATA` was checked with a path test that treats `C:\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.

## 0.2.39 — 2026-08-07

- **Two analyzers join the registry, which now holds 15.** Both come from a
Expand Down
4 changes: 3 additions & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@codetruss/cli",
"version": "0.2.39",
"version": "0.2.40",
"description": "Local-first scope, quality, and verification receipts for coding agents",
"license": "SEE LICENSE IN LICENSE",
"type": "module",
Expand Down Expand Up @@ -42,6 +42,8 @@
"build": "node scripts/build.mjs",
"release:artifact": "node scripts/build-release.mjs",
"verify:artifact": "node scripts/verify-release.mjs",
"release:grammars": "node scripts/build-grammar-packs.mjs",
"verify:grammars": "node scripts/verify-grammar-packs.mjs",
Comment on lines +45 to +46

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check which grammar-related scripts exist across workspace manifests.
fd -H -t f 'package.json' -E node_modules --exec sh -c 'echo "== $1"; jq -r "(.scripts // {}) | to_entries[] | select(.key | test(\"grammar\")) | \"\(.key): \(.value)\"" "$1"' _ {}

# Description: Find every reference to the documented command strings.
rg -n 'grammars:release|release:grammars|verify:grammars' -g '!node_modules'

Repository: DeliriumPulse/codetruss-cli

Length of output: 2144


🏁 Script executed:

#!/bin/bash
set -u

echo "== root package.json scripts =="
jq -r '.scripts // {} | to_entries[] | select(.key | test("grammar|release|verify"; "i")) | "\(.key): \(.value)"' package.json

echo
echo "== package scripts matching grammar (read-only, deterministic jq script per file) =='
python3 - <<'PY'
import json, subprocess
files = subprocess.check_output(["git","ls-files","package.json"], text=True).splitlines()
for path in files:
    try:
        data=json.load(open(path))
    except Exception as e:
        print(f"{path}: parse error {e}")
        continue
    scripts=data.get("scripts", {}) or {}
    for k,v in scripts.items():
        if "grammar" in k.lower():
            print(f"{path}: {k}: {v}")
PY

echo
echo "== command references =="
rg -n 'grammars:release|release:grammars|verify:grammars' -g '!node_modules' || true

echo
echo "== candidate grammar script files =="
fd -H -t f 'grammar' -E node_modules . || true

echo
echo "== error guidance text =="
fd -H -t f '\.(mjs|js|ts)$' -E node_modules . | xargs rg -n 'run pnpm grammars:release|pnpm grammars:release|release:grammars|verify:grammars' || true

Repository: DeliriumPulse/codetruss-cli

Length of output: 384


🏁 Script executed:

#!/bin/bash
set -u

echo "== root package.json scripts =="
jq -r '.scripts // {} | to_entries[] | select(.key | test("grammar|release|verify"; "i")) | "\(.key): \(.value)"' package.json || true

echo
echo "== package scripts matching grammar (read-only, deterministic jq per file) =="
python3 - <<'PY'
import json, subprocess
files = subprocess.check_output(["git","ls-files","package.json"], text=True).splitlines()
for path in files:
    try:
        data=json.load(open(path, encoding="utf-8"))
    except Exception as e:
        print(f"{path}: parse error {e}")
        continue
    scripts=data.get("scripts") or {}
    for k,v in scripts.items():
        if "grammar" in k.lower():
            print(f"{path}: {k}: {v}")
PY

echo
echo "== command references =="
rg -n 'grammars:release|release:grammars|verify:grammars' -g '!node_modules' || true

echo
echo "== candidate grammar script files =="
fd -H -t f 'grammar' -E node_modules . || true

echo
echo "== error guidance text =="
fd -H -t f '\.(mjs|js|ts)$' -E node_modules . | xargs rg -n 'run pnpm grammars:release|pnpm grammars:release|release:grammars|verify:grammars' || true

Repository: DeliriumPulse/codetruss-cli

Length of output: 580


Add or update the grammar-pack failure guidance.

release:grammars and verify:grammars run scripts/build-grammar-packs.mjs and scripts/verify-grammar-packs.mjs, but the workspace has no root grammars:release script or other grammar script. The error messages should point to a command that actually exists in the workspace.

🤖 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/package.json` around lines 45 - 46, Update the failure guidance
associated with the release and verification grammar scripts in package.json to
reference an existing workspace command, rather than the nonexistent root
grammars:release script. Keep the guidance aligned with the commands exposed by
release:grammars and verify:grammars.

"test:install": "node scripts/test-install.mjs",
"prepack": "pnpm build",
"pretest": "pnpm build",
Expand Down
Loading
Loading