From cabc30957daa532c56833fab91de50666dcf9e5b Mon Sep 17 00:00:00 2001 From: Zack Whitson Date: Fri, 7 Aug 2026 10:56:12 -0500 Subject: [PATCH 1/2] Release CodeTruss CLI v0.2.41 Syncs the 0.2.41 source from the private monorepo. codetruss-cli-0.2.41.tgz sha256 cb0b9d69f15e7113a6a523a6b139ec3150b7d07459e5242b2c047fc9aac57d04 That is the digest the website serves and the digest in release-reference.json, rebuilt from this tree and compared rather than copied across. Co-Authored-By: Claude Opus 5 --- CHANGELOG.md | 79 +- SECURITY.md | 43 + packages/analyzer-engine/src/index.ts | 1 + .../analyzer-engine/src/security/rules.ts | 75 +- packages/analyzer-engine/src/suppression.ts | 105 +++ packages/analyzer-engine/src/types.ts | 24 + packages/cli/CHANGELOG.md | 77 ++ packages/cli/SECURITY.md | 43 + packages/cli/package.json | 9 +- .../cli/scripts/attest-grammar-sources.mjs | 128 +++ packages/cli/scripts/build-grammar-packs.mjs | 63 +- packages/cli/scripts/build-release.mjs | 15 +- packages/cli/scripts/grammar-pack-render.mjs | 110 +++ packages/cli/scripts/grammar-pack-sources.mjs | 10 +- packages/cli/scripts/lockfile-integrity.mjs | 81 ++ packages/cli/scripts/npm-tarball.mjs | 109 +++ packages/cli/scripts/release-metadata.mjs | 47 ++ .../cli/scripts/release-package-policy.mjs | 6 +- .../cli/scripts/test-release-verifier.mjs | 57 +- packages/cli/scripts/verify-grammar-packs.mjs | 88 ++- packages/cli/scripts/verify-release.mjs | 17 +- packages/cli/src/analysis.ts | 60 +- packages/cli/src/grammar-pack-manifest.ts | 12 + packages/cli/src/grammar-pack.ts | 124 ++- packages/cli/src/grammar-parser.ts | 58 +- packages/cli/src/hook-agent-config.ts | 105 +++ packages/cli/src/hook-agent-runner.ts | 81 ++ packages/cli/src/hook-doctor.ts | 297 +++++++ packages/cli/src/hook-executable.ts | 49 ++ packages/cli/src/hook-pre-commit.ts | 56 ++ packages/cli/src/hook-targets.ts | 11 + packages/cli/src/hook-writes.ts | 143 ++++ packages/cli/src/hooks.ts | 739 +----------------- packages/cli/src/receipt.ts | 88 ++- packages/cli/src/types.ts | 15 +- packages/cli/test/grammar-loader.test.ts | 78 +- packages/cli/test/grammar-pack.test.ts | 79 +- packages/cli/test/grammar-pin-verify.test.ts | 156 ++++ packages/cli/test/grammar-sast.test.ts | 9 + packages/cli/test/hooks.test.ts | 55 ++ packages/cli/test/npm-tarball.test.ts | 79 ++ packages/cli/test/release-scripts.d.ts | 35 + packages/cli/test/suppression.test.ts | 224 ++++++ .../codetruss-cli-0.2.41.sbom.cdx.json | 170 ++++ public/downloads/codetruss-cli-0.2.41.tgz | Bin 0 -> 873560 bytes .../downloads/codetruss-cli-0.2.41.tgz.sha256 | 1 + public/downloads/codetruss-cli-latest.json | 16 +- .../codetruss-cli-latest.sbom.cdx.json | 10 +- public/downloads/codetruss-cli-latest.tgz | Bin 857688 -> 873560 bytes .../downloads/codetruss-cli-latest.tgz.sha256 | 2 +- release-reference.json | 10 +- 51 files changed, 3065 insertions(+), 884 deletions(-) create mode 100644 packages/analyzer-engine/src/suppression.ts create mode 100644 packages/cli/scripts/attest-grammar-sources.mjs create mode 100644 packages/cli/scripts/grammar-pack-render.mjs create mode 100644 packages/cli/scripts/lockfile-integrity.mjs create mode 100644 packages/cli/scripts/npm-tarball.mjs create mode 100644 packages/cli/scripts/release-metadata.mjs create mode 100644 packages/cli/src/hook-agent-config.ts create mode 100644 packages/cli/src/hook-agent-runner.ts create mode 100644 packages/cli/src/hook-doctor.ts create mode 100644 packages/cli/src/hook-executable.ts create mode 100644 packages/cli/src/hook-pre-commit.ts create mode 100644 packages/cli/src/hook-targets.ts create mode 100644 packages/cli/src/hook-writes.ts create mode 100644 packages/cli/test/grammar-pin-verify.test.ts create mode 100644 packages/cli/test/npm-tarball.test.ts create mode 100644 packages/cli/test/release-scripts.d.ts create mode 100644 packages/cli/test/suppression.test.ts create mode 100644 public/downloads/codetruss-cli-0.2.41.sbom.cdx.json create mode 100644 public/downloads/codetruss-cli-0.2.41.tgz create mode 100644 public/downloads/codetruss-cli-0.2.41.tgz.sha256 diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a2c4a7..1e3bf11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ CodeTruss CLI follows semantic versioning. Release artifacts and their SHA-256 checksums are published at . -The current public release is [v0.2.40 on GitHub](https://github.com/CodeTruss/codetruss-cli/releases/tag/v0.2.40), +The current public release is [v0.2.41 on GitHub](https://github.com/CodeTruss/codetruss-cli/releases/tag/v0.2.41), distributed from . The npm `latest` tag is still [`@codetruss/cli@0.2.24`](https://www.npmjs.com/package/@codetruss/cli/v/0.2.24): @@ -16,6 +16,83 @@ were superseded before distribution. No unreleased changes. +## 0.2.41 — 2026-08-07 + +- **You can dismiss a finding you have judged wrong, in the place the judgement + belongs.** A `codetruss-ignore: ` 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 bare `codetruss-ignore` therefore 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()` then `cur.execute(f"... {user_input}")` — the canonical + psycopg/sqlite3/MySQLdb two-step — was invisible, because the sink test was + lexical and `cur` does 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, including `with conn.cursor() as cur:`. `executemany` joins + `execute`; `exec` stays name-gated, since a bare `.exec()` is far more often + `RegExp.prototype.exec` than 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 install` is a foreground command someone is + watching, and `fetch` will 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 with `tree-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.md` now 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 same `node_modules` on the release machine. `pnpm grammars:attest` narrows + 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 doctor` names 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.json` was the config that + exposed it: several versions stale, missing `core.longpaths=true` and 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 the `DeliriumPulse` account, and every release still in + circulation has been re-attested under the organisation, so one command + verifies all of them: `gh attestation verify --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.ts` is 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. + ## 0.2.40 — 2026-08-07 - **Python can now be analyzed locally, if you ask for it.** `codetruss diff --git a/SECURITY.md b/SECURITY.md index 562f92c..b631391 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -30,6 +30,49 @@ fix and disclosure timeline with the reporter. Good-faith research that avoids privacy violations, service disruption, data destruction, and access beyond what is needed to demonstrate the issue is welcome. +## Grammar packs: what the digest pin covers, and what it does not + +`codetruss grammars install python` downloads code that this CLI later executes +in your process. Every artifact is pinned to an exact SHA-256 compiled into the +binary, checked as the bytes arrive and re-checked on every load, and the buffer +that was hashed is the buffer that is executed — nothing is re-opened by path. + +That pin protects you against a **compromised download origin**. If codetruss.com +or anything between it and your machine serves different bytes, the install +fails and no Python is analyzed. It is a strong guarantee and it is the one the +pin was built for. + +It does **not** protect you against a compromised **build**. The pin, the +published artifact, and the offline check that compares them are all derived +from the same `node_modules` on the machine that cut the release. Anything able +to write there between `pnpm install` and the release command would poison all +three in one move, and every check would still pass. A digest pin can only ever +say "these are the bytes we published"; it cannot say "these are the bytes we +meant to publish". + +Two things narrow that window, and neither closes it: + +- **`pnpm grammars:attest` (run by the release).** The tarball digest recorded + in `pnpm-lock.yaml` is checked against what the npm registry serves for that + exact version, the registry's ECDSA signature over the version/digest pair is + verified against npm's published keys, the tarball is downloaded and hashed, + and its files are compared against the `node_modules` copies the pack is cut + from. A file dropped into `node_modules` fails the release. This runs on the + release machine, so a compromise deep enough to patch the script is not caught + by the script. +- **`pnpm grammars:verify` (runs in every build).** Re-derives the whole + generated pin from the published artifacts and compares it byte for byte, and + checks that the versions a pack claims are the versions the lockfile pins. It + is an internal-consistency proof, not an independent one. + +There is no reproducible build and no third-party rebuild of these artifacts. +Anyone can check the published bytes for themselves: each artifact is served +with a `.sha256` sidecar under `/downloads/grammars/`, the same digests appear +in the CLI's `src/grammar-pack-manifest.ts`, and the upstream packages +(`web-tree-sitter`, `tree-sitter-wasms`) are copied byte for byte with nothing +recompiled, so a published artifact can be diffed directly against the version +you install yourself. + ## Scope Security-sensitive surfaces include artifact/install integrity, receipt diff --git a/packages/analyzer-engine/src/index.ts b/packages/analyzer-engine/src/index.ts index df4f111..2403c65 100644 --- a/packages/analyzer-engine/src/index.ts +++ b/packages/analyzer-engine/src/index.ts @@ -1,6 +1,7 @@ export * from './types' export * from './registry' export * from './runner' +export * from './suppression' export * from './scoring' export * from './coverage' export * from './support' diff --git a/packages/analyzer-engine/src/security/rules.ts b/packages/analyzer-engine/src/security/rules.ts index e7cee39..e0447c6 100644 --- a/packages/analyzer-engine/src/security/rules.ts +++ b/packages/analyzer-engine/src/security/rules.ts @@ -90,7 +90,73 @@ const CHILD_PROCESS_RECEIVER = /(^|[._])(child_?process|cp|proc|shell)$/i const SQL_ALWAYS = new Set(['query', 'raw', 'unsafe', 'executequery', 'executeupdate', 'executesql', 'rawquery']) /** Method names that execute SQL only on a DB-ish receiver. */ -const SQL_GATED = new Set(['execute', 'exec']) +const SQL_GATED = new Set(['execute', 'exec', 'executemany']) +/** The DB-API cursor methods among {@link SQL_GATED}: for these the receiver may + * also be proven a cursor by its binding, not just by its name. `exec` stays + * name-gated — a bare `.exec()` is RegExp.prototype.exec far more often than + * it is SQL, and resolving its binding would buy nothing but scan time. */ +const CURSOR_METHODS = new Set(['execute', 'executemany']) + +/** A `.cursor()` call — the DB-API idiom that hands back a cursor. */ +function isCursorFactoryCall(node: SyntaxNode, lang: SastLanguage): boolean { + const c = asCall(unwrap(node, lang), lang) + return !!c && !!c.receiver && lc(c.method) === 'cursor' +} + +/** + * True when this call's receiver is a local BOUND to a DB cursor — + * `cur = conn.cursor()` or `with conn.cursor() as cur:` — rather than a name + * that merely reads DB-ish. + * + * {@link DB_RECEIVER} is a purely lexical test. It catches `cursor.execute(...)` + * and the direct chain `conn.cursor().execute(...)` (whose dotted receiver name + * is `conn.cursor`), but goes blind the moment the cursor is parked in a short + * local — `cur`, `c`, `crsr` — which is how most psycopg/sqlite3/MySQLdb code is + * actually written. Resolving the binding restores that canonical two-step form + * without loosening the lexical gate to generic names, which would cost + * precision everywhere else. + */ +function receiverBindsToCursor(call: NCall, lang: SastLanguage): boolean { + if (!call.receiver) return false + const name = identifierName(call.receiver, lang) + if (!name) return false + // Innermost enclosing scope that binds the name wins (program root last). + let scope: SyntaxNode | null = call.node.parent + for (let i = 0; i < 60 && scope; i++) { + const isRoot = scope.parent === null + if (isFunctionNode(scope, lang) || isRoot) { + const fn = isFunctionNode(scope, lang) ? asFunction(scope, lang) : null + const body = isRoot && !fn ? scope : fn?.body + if (body) { + const defs = [ + ...collectAssignments(body, lang).filter((a) => a.target === name).map((a) => a.value), + ...withAliasDefs(body, name, lang), + ] + if (defs.length > 0) return defs.some((d) => isCursorFactoryCall(d, lang)) + } + } + scope = scope.parent + } + return false +} + +/** `with as name:` bindings in a scope — python models them as an + * `as_pattern`, which {@link collectAssignments} does not treat as a def. */ +function withAliasDefs(body: SyntaxNode, name: string, lang: SastLanguage): SyntaxNode[] { + const out: SyntaxNode[] = [] + const visit = (node: SyntaxNode) => { + if (node !== body && isFunctionNode(node, lang)) return // separate scope + if (node.type === 'as_pattern') { + const alias = node.childForFieldName('alias') + const value = node.namedChildren[0] + const bound = alias ? identifierName(alias.namedChildren[0] ?? alias, lang) : null + if (value && bound === name) out.push(value) + } + for (const c of node.namedChildren) visit(c) + } + visit(body) + return out +} // NOTE: generic receiver names like `client`/`session` are deliberately NOT here. // They match Redis/DB/gRPC/GraphQL clients (`client.get(id)`), which are not HTTP @@ -140,10 +206,13 @@ export const TAINT_SINKS: TaintSink[] = [ title: 'SQL injection', message: 'Untrusted input is concatenated into a SQL query and executed. An attacker can alter the query to read or modify arbitrary data.', remediation: 'Use parameterized queries / prepared statements and pass user input as bound parameters, never string concatenation.', - match(call) { + match(call, lang) { const m = lc(call.method) if (SQL_ALWAYS.has(m)) return [0] - if (SQL_GATED.has(m) && DB_RECEIVER.test(call.receiverName ?? '')) return [0] + if (SQL_GATED.has(m)) { + if (DB_RECEIVER.test(call.receiverName ?? '')) return [0] + if (CURSOR_METHODS.has(m) && receiverBindsToCursor(call, lang)) return [0] + } // Go database/sql, gated on a DB-ish receiver. Context variants take // (ctx, query, ...args) so the SQL string is argument 1, not 0. if (DB_RECEIVER.test(call.receiverName ?? '')) { diff --git a/packages/analyzer-engine/src/suppression.ts b/packages/analyzer-engine/src/suppression.ts new file mode 100644 index 0000000..ebf00f6 --- /dev/null +++ b/packages/analyzer-engine/src/suppression.ts @@ -0,0 +1,105 @@ +import type { AnalyzerFinding, FindingSuppression, RepoIndex } from './types' + +/** + * Inline finding suppression: `codetruss-ignore: `. + * + * A developer who has judged a finding wrong needs a way to say so in the one + * place the judgement belongs — beside the code it is about. The marker mirrors + * `gitleaks:allow` in placement and differs from it in two deliberate ways. + * + * FIRST, a reason is mandatory. The marker's entire output is a line of evidence + * on a signed receipt, and "someone decided this was fine" is not evidence. A + * bare `codetruss-ignore` therefore suppresses nothing and is recorded as + * rejected, so the developer learns why their comment did nothing: a marker that + * silently fails is worse than no marker at all. + * + * SECOND, nothing here deletes a finding. This pass ANNOTATES: the finding keeps + * flowing through the delta, the passes and the receipt, carrying the reason it + * was dismissed. A receipt that quietly dropped a finding because a comment in + * the repository told it to would be a hole in the evidence chain — the whole + * claim of the artifact is that it states what was and was not flagged, and + * "nothing was found" must never be reachable by editing a comment. + */ +const MARKER_RE = /\bcodetruss-ignore\b[ \t]*(:[ \t]*(.*))?/ + +/** + * Comment terminators that would otherwise be read as part of the reason. + * `/* codetruss-ignore: deliberate *\/` is how this gets written in JS, CSS, + * Java and C; in HTML and Markdown it is ``. + */ +const COMMENT_CLOSE_RE = /\s*(\*\/|-->)\s*$/ + +/** + * Reasons are quoted verbatim into a signed receipt that is also validated + * against a bounded schema on sync. A reason longer than this is not a reason, + * and the cap stops one pathological comment from making a receipt unsyncable. + */ +const MAX_REASON_LENGTH = 500 + +interface ParsedMarker { + /** Text after the colon. Empty when the marker gave no reason. */ + reason: string + /** + * Nothing but whitespace and comment punctuation precedes the marker. + * + * A marker trailing a line of CODE was written about that code, so it governs + * only its own line. Letting it reach the line below would silently dismiss a + * neighbouring finding its author never looked at. + */ + commentOnly: boolean +} + +function parseMarker(line: string | undefined): ParsedMarker | null { + if (line === undefined) return null + const match = MARKER_RE.exec(line) + if (!match) return null + const reason = match[2] === undefined ? '' : match[2].replace(COMMENT_CLOSE_RE, '').trim().slice(0, MAX_REASON_LENGTH) + return { reason, commentOnly: !/[A-Za-z0-9_$]/.test(line.slice(0, match.index)) } +} + +/** The marker governing `line`: its own line, else a comment-only line above it. */ +function markerFor(lines: string[], line: number): { reason: string; markerLine: number } | null { + const own = parseMarker(lines[line - 1]) + if (own) return { reason: own.reason, markerLine: line } + const above = parseMarker(lines[line - 2]) + if (above?.commentOnly) return { reason: above.reason, markerLine: line - 1 } + return null +} + +/** + * Record on every finding whether an inline marker dismissed it. + * + * Returns new objects; the input is not mutated. Findings without BOTH a file + * and a line are returned untouched: a repository-level or whole-file finding + * has no line for a comment to sit beside, and picking one — the top of the + * file, the first match — would suppress by guesswork. + */ +export function annotateSuppressions(findings: AnalyzerFinding[], index: RepoIndex): AnalyzerFinding[] { + if (findings.length === 0) return findings + const lineCache = new Map() + const readLines = (path: string): string[] | null => { + const cached = lineCache.get(path) + if (cached !== undefined) return cached + const file = index.files.find((candidate) => candidate.path === path) + // Generated files are read here for the reason the secret scanner reads + // them: what the line says governs, whichever tool wrote it. + const content = file ? file.content ?? file.excludedContent ?? null : null + const lines = content === null ? null : content.split('\n') + lineCache.set(path, lines) + return lines + } + + return findings.map((finding) => { + if (!finding.filePath || !finding.line) return finding + const lines = readLines(finding.filePath) + if (!lines) return finding + const marker = markerFor(lines, finding.line) + if (!marker) return finding + const suppression: FindingSuppression = { + reason: marker.reason, + markerLine: marker.markerLine, + applied: marker.reason.length > 0, + } + return { ...finding, suppression } + }) +} diff --git a/packages/analyzer-engine/src/types.ts b/packages/analyzer-engine/src/types.ts index b39fc75..064ef0b 100644 --- a/packages/analyzer-engine/src/types.ts +++ b/packages/analyzer-engine/src/types.ts @@ -90,6 +90,28 @@ export interface FindingFix { safetyNote: string } +/** + * A developer's inline judgement that one finding is wrong, read from a + * `codetruss-ignore: ` comment beside the code. See `suppression.ts`. + * + * Attached to the finding rather than replacing it. A suppressed finding is + * still produced, still counted in the delta, and still written to the receipt + * — as suppressed, with its reason. Deleting it instead would let a comment in + * the repository decide what the signed evidence is allowed to say. + */ +export interface FindingSuppression { + /** The text after `codetruss-ignore:`. Empty only when `applied` is false. */ + reason: string + /** 1-based line carrying the marker: the finding's own line, or the one above. */ + markerLine: number + /** + * Whether the marker actually dismissed this finding. False for a marker that + * gave no reason — the finding stays reported, and the rejected marker is + * disclosed so a comment is never seen to fail in silence. + */ + applied: boolean +} + export interface AnalyzerFinding { category: FindingCategory severity: FindingSeverity @@ -104,6 +126,8 @@ export interface AnalyzerFinding { effort?: 'low' | 'medium' | 'high' metadata?: Record analyzerId?: string + /** Present only when an inline marker was found beside this finding. */ + suppression?: FindingSuppression } export interface AnalyzerRunResult { diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index f5f7b1a..6df6954 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -5,6 +5,83 @@ checksums are published at ` 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 bare `codetruss-ignore` therefore 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()` then `cur.execute(f"... {user_input}")` — the canonical + psycopg/sqlite3/MySQLdb two-step — was invisible, because the sink test was + lexical and `cur` does 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, including `with conn.cursor() as cur:`. `executemany` joins + `execute`; `exec` stays name-gated, since a bare `.exec()` is far more often + `RegExp.prototype.exec` than 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 install` is a foreground command someone is + watching, and `fetch` will 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 with `tree-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.md` now 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 same `node_modules` on the release machine. `pnpm grammars:attest` narrows + 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 doctor` names 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.json` was the config that + exposed it: several versions stale, missing `core.longpaths=true` and 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 the `DeliriumPulse` account, and every release still in + circulation has been re-attested under the organisation, so one command + verifies all of them: `gh attestation verify --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.ts` is 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. + ## 0.2.40 — 2026-08-07 - **Python can now be analyzed locally, if you ask for it.** `codetruss diff --git a/packages/cli/SECURITY.md b/packages/cli/SECURITY.md index 562f92c..b631391 100644 --- a/packages/cli/SECURITY.md +++ b/packages/cli/SECURITY.md @@ -30,6 +30,49 @@ fix and disclosure timeline with the reporter. Good-faith research that avoids privacy violations, service disruption, data destruction, and access beyond what is needed to demonstrate the issue is welcome. +## Grammar packs: what the digest pin covers, and what it does not + +`codetruss grammars install python` downloads code that this CLI later executes +in your process. Every artifact is pinned to an exact SHA-256 compiled into the +binary, checked as the bytes arrive and re-checked on every load, and the buffer +that was hashed is the buffer that is executed — nothing is re-opened by path. + +That pin protects you against a **compromised download origin**. If codetruss.com +or anything between it and your machine serves different bytes, the install +fails and no Python is analyzed. It is a strong guarantee and it is the one the +pin was built for. + +It does **not** protect you against a compromised **build**. The pin, the +published artifact, and the offline check that compares them are all derived +from the same `node_modules` on the machine that cut the release. Anything able +to write there between `pnpm install` and the release command would poison all +three in one move, and every check would still pass. A digest pin can only ever +say "these are the bytes we published"; it cannot say "these are the bytes we +meant to publish". + +Two things narrow that window, and neither closes it: + +- **`pnpm grammars:attest` (run by the release).** The tarball digest recorded + in `pnpm-lock.yaml` is checked against what the npm registry serves for that + exact version, the registry's ECDSA signature over the version/digest pair is + verified against npm's published keys, the tarball is downloaded and hashed, + and its files are compared against the `node_modules` copies the pack is cut + from. A file dropped into `node_modules` fails the release. This runs on the + release machine, so a compromise deep enough to patch the script is not caught + by the script. +- **`pnpm grammars:verify` (runs in every build).** Re-derives the whole + generated pin from the published artifacts and compares it byte for byte, and + checks that the versions a pack claims are the versions the lockfile pins. It + is an internal-consistency proof, not an independent one. + +There is no reproducible build and no third-party rebuild of these artifacts. +Anyone can check the published bytes for themselves: each artifact is served +with a `.sha256` sidecar under `/downloads/grammars/`, the same digests appear +in the CLI's `src/grammar-pack-manifest.ts`, and the upstream packages +(`web-tree-sitter`, `tree-sitter-wasms`) are copied byte for byte with nothing +recompiled, so a published artifact can be diffed directly against the version +you install yourself. + ## Scope Security-sensitive surfaces include artifact/install integrity, receipt diff --git a/packages/cli/package.json b/packages/cli/package.json index 4f84173..f519951 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@codetruss/cli", - "version": "0.2.40", + "version": "0.2.41", "description": "Local-first scope, quality, and verification receipts for coding agents", "license": "SEE LICENSE IN LICENSE", "type": "module", @@ -21,11 +21,11 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/DeliriumPulse/codetruss-cli.git" + "url": "git+https://github.com/CodeTruss/codetruss-cli.git" }, "homepage": "https://codetruss.com/cli", "bugs": { - "url": "https://github.com/DeliriumPulse/codetruss-cli/issues" + "url": "https://github.com/CodeTruss/codetruss-cli/issues" }, "keywords": [ "ai-agent", @@ -42,7 +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", + "release:grammars": "node scripts/attest-grammar-sources.mjs && node scripts/build-grammar-packs.mjs", + "attest:grammars": "node scripts/attest-grammar-sources.mjs", "verify:grammars": "node scripts/verify-grammar-packs.mjs", "test:install": "node scripts/test-install.mjs", "prepack": "pnpm build", diff --git a/packages/cli/scripts/attest-grammar-sources.mjs b/packages/cli/scripts/attest-grammar-sources.mjs new file mode 100644 index 0000000..2f43ec8 --- /dev/null +++ b/packages/cli/scripts/attest-grammar-sources.mjs @@ -0,0 +1,128 @@ +/** + * Prove a grammar pack's upstream bytes against something other than this + * machine's `node_modules`. + * + * The offline gate (`verify-grammar-packs.mjs`) chains published artifact → + * `node_modules` → generated pin, which is an internal-consistency proof: one + * compromised `node_modules` on the release machine poisons the artifact, the + * pin AND the verifier in a single move, and every check still passes. This + * script is the missing link at the top of that chain. It takes the tarball + * digest the workspace lockfile recorded at install time, confirms the registry + * still serves that exact digest for that version, verifies the registry's + * signature over the pair, downloads the tarball, checks it hashes to the same + * digest, and compares the files inside it against what the build script would + * read out of `node_modules`. + * + * What that establishes, precisely: the bytes this release publishes are the + * bytes npm signed for the versions the lockfile pins. What it does not: it runs + * on the release machine, so a compromise deep enough to patch this script is + * not caught by this script. The point is to close the cheap window — a poisoned + * file dropped into `node_modules` — not to claim a reproducible build. + * + * Network, therefore release-time and not a build gate: `pnpm build` must not + * depend on npm being reachable. Run by `pnpm grammars:release` before anything + * is published, and standalone with `pnpm grammars:attest`. + */ +import { readFile } from 'node:fs/promises' +import { createHash } from 'node:crypto' +import { dirname, join, resolve } from 'node:path' +import { fileURLToPath } from 'node:url' +import { GRAMMAR_PACKS, GRAMMAR_PACK_PROVENANCE } from './grammar-pack-sources.mjs' +import { lockedSourcePackage } from './lockfile-integrity.mjs' +import { readTarballMembers, subresourceIntegrity, verifyRegistrySignature } from './npm-tarball.mjs' + +const scriptDir = dirname(fileURLToPath(import.meta.url)) +const packageDir = resolve(scriptDir, '..') +const repoRoot = resolve(packageDir, '../..') +const REGISTRY = 'https://registry.npmjs.org' +const NETWORK_TIMEOUT_MS = 60_000 + +async function fetchJson(url) { + const response = await fetch(url, { signal: AbortSignal.timeout(NETWORK_TIMEOUT_MS) }) + if (!response.ok) throw new Error(`${url} responded ${response.status}`) + return response.json() +} + +async function fetchBytes(url) { + const response = await fetch(url, { signal: AbortSignal.timeout(NETWORK_TIMEOUT_MS) }) + if (!response.ok) throw new Error(`${url} responded ${response.status}`) + return Buffer.from(await response.arrayBuffer()) +} + +export async function attestGrammarSources({ + moduleDir = join(repoRoot, 'node_modules'), + lockfilePath = join(repoRoot, 'pnpm-lock.yaml'), + registry = REGISTRY, +} = {}) { + const lockfile = await readFile(lockfilePath, 'utf8') + const keys = (await fetchJson(`${registry}/-/npm/v1/keys`)).keys ?? [] + const attested = [] + + for (const source of Object.values(GRAMMAR_PACK_PROVENANCE)) { + const { package: name, version } = source + const locked = lockedSourcePackage(lockfile, name, version) + + const metadata = await fetchJson(`${registry}/${encodeURIComponent(name)}/${encodeURIComponent(version)}`) + const dist = metadata.dist ?? {} + // The lockfile is the anchor, not the registry: if they disagree, this + // workspace installed something the registry no longer serves under that + // version, which is exactly the event worth stopping a release for. + if (dist.integrity !== locked.integrity) { + throw new Error( + `${name}@${version}: pnpm-lock.yaml records ${locked.integrity} but the registry serves ${dist.integrity}`, + ) + } + const [signature] = dist.signatures ?? [] + if (!signature) throw new Error(`${name}@${version} carries no registry signature`) + verifyRegistrySignature({ name, version, integrity: dist.integrity, signature, keys }) + + const tarball = await fetchBytes(dist.tarball) + const downloaded = subresourceIntegrity(tarball) + if (downloaded !== locked.integrity) { + throw new Error(`${dist.tarball} hashes to ${downloaded}, expected ${locked.integrity}`) + } + attested.push({ name, version, integrity: locked.integrity, members: readTarballMembers(tarball) }) + } + + const files = [] + for (const pack of GRAMMAR_PACKS) { + for (const file of pack.files) { + const [sourcePackage, ...rest] = file.source + const upstream = attested.find((entry) => entry.name === sourcePackage) + if (!upstream) { + throw new Error(`${pack.name}/${file.name} is cut from ${sourcePackage}, which has no provenance entry`) + } + const member = upstream.members.get(['package', ...rest].join('/')) + if (!member) { + throw new Error( + `${sourcePackage}@${upstream.version} does not contain ${rest.join('/')}; ` + + 'the pack source path is wrong or the tarball layout changed', + ) + } + const installed = await readFile(join(moduleDir, ...file.source)) + if (!member.equals(installed)) { + throw new Error( + `node_modules/${file.source.join('/')} does not match the signed ${sourcePackage}@${upstream.version} ` + + 'tarball; do not publish from this checkout until that is explained', + ) + } + files.push({ + name: file.name, + source: `${sourcePackage}@${upstream.version}`, + sha256: createHash('sha256').update(member).digest('hex'), + }) + } + } + + return { sources: attested.map(({ name, version, integrity }) => ({ name, version, integrity })), files } +} + +if (process.argv[1] && resolve(process.argv[1]) === resolve(fileURLToPath(import.meta.url))) { + const result = await attestGrammarSources() + for (const source of result.sources) { + process.stdout.write(`attested ${source.name}@${source.version} against its signed registry tarball\n`) + } + for (const file of result.files) { + process.stdout.write(` ${file.name}: ${file.sha256} (from ${file.source})\n`) + } +} diff --git a/packages/cli/scripts/build-grammar-packs.mjs b/packages/cli/scripts/build-grammar-packs.mjs index 9595270..62af8c2 100644 --- a/packages/cli/scripts/build-grammar-packs.mjs +++ b/packages/cli/scripts/build-grammar-packs.mjs @@ -21,6 +21,7 @@ import { packDirectoryName, packFileUrl, } from './grammar-pack-sources.mjs' +import { renderGrammarPin, renderGrammarSiteManifest } from './grammar-pack-render.mjs' const scriptDir = dirname(fileURLToPath(import.meta.url)) const packageDir = resolve(scriptDir, '..') @@ -70,7 +71,13 @@ for (const pack of GRAMMAR_PACKS) { const target = join(packDir, file.name) await publishImmutable(target, bytes, `${directoryName}/${file.name}`) await writeFile(`${target}.sha256`, `${digest} ${file.name}\n`, 'utf8') - files.push({ name: file.name, url: packFileUrl(pack, file.name), bytes: bytes.length, sha256: digest }) + files.push({ + name: file.name, + role: file.role, + url: packFileUrl(pack, file.name), + bytes: bytes.length, + sha256: digest, + }) } // A stray file in a published pack directory is a supply-chain question, not @@ -94,55 +101,11 @@ for (const pack of GRAMMAR_PACKS) { export const GRAMMAR_MANIFEST_NAME = 'codetruss-grammars-latest.json' -const manifest = `${JSON.stringify({ packs: manifestPacks }, null, 2)}\n` -await writeFile(join(grammarDir, GRAMMAR_MANIFEST_NAME), manifest, 'utf8') - -/** - * The compiled-in pin. - * - * Generated rather than hand-maintained because a hand-copied digest is a digest - * that eventually disagrees with the bytes, and this one is the only thing - * standing between a user and executing whatever a compromised origin served. - */ -const pin = `/** - * Pinned grammar-pack digests. GENERATED by scripts/build-grammar-packs.mjs. - * - * Do not edit by hand. These digests are what \`codetruss grammars install\` - * checks a download against, and what every subsequent load re-checks on disk. - * A pack whose bytes do not hash to exactly these values is never loaded, and - * the run discloses Python as skipped instead. - */ - -export interface PinnedGrammarFile { - name: string - /** Path under the downloads origin, e.g. \`/downloads/grammars/python-1.0.0/…\`. */ - url: string - bytes: number - sha256: string -} - -export interface PinnedGrammarPack { - name: string - version: string - /** The \`SastLanguage\` this pack enables. */ - language: string - runtime: { package: string; version: string } - grammar: { package: string; version: string } - files: PinnedGrammarFile[] -} - -export const PINNED_GRAMMAR_PACKS: readonly PinnedGrammarPack[] = ${JSON.stringify(manifestPacks, null, 2) - .split('\n') - .join('\n')} - -/** Pack names this CLI build knows how to install. */ -export const GRAMMAR_PACK_NAMES: readonly string[] = PINNED_GRAMMAR_PACKS.map((pack) => pack.name) - -export function pinnedGrammarPack(name: string): PinnedGrammarPack | undefined { - return PINNED_GRAMMAR_PACKS.find((pack) => pack.name === name) -} -` -await writeFile(join(packageDir, 'src', 'grammar-pack-manifest.ts'), pin, 'utf8') +// Both generated files come from one renderer, shared with the verifier, so the +// check that they are what this script would write today is a byte comparison +// rather than a re-implementation of the format. +await writeFile(join(grammarDir, GRAMMAR_MANIFEST_NAME), renderGrammarSiteManifest(manifestPacks), 'utf8') +await writeFile(join(packageDir, 'src', 'grammar-pack-manifest.ts'), renderGrammarPin(manifestPacks), 'utf8') for (const pack of manifestPacks) { const total = pack.files.reduce((sum, file) => sum + file.bytes, 0) diff --git a/packages/cli/scripts/build-release.mjs b/packages/cli/scripts/build-release.mjs index b274e21..2825fda 100644 --- a/packages/cli/scripts/build-release.mjs +++ b/packages/cli/scripts/build-release.mjs @@ -7,6 +7,7 @@ import { spawnSync } from 'node:child_process' import { assertChangelogPolicy } from './changelog-policy.mjs' import { buildDeterministicPackageArchive } from './deterministic-package.mjs' import { assertReleasePackagePolicy } from './release-package-policy.mjs' +import { buildReleaseManifest, serialiseReleaseManifest } from './release-metadata.mjs' import { verifyDeterministicPackageArchive } from './verify-deterministic-package.mjs' const scriptDir = dirname(fileURLToPath(import.meta.url)) @@ -73,19 +74,7 @@ try { await writeFile(`${latest}.sha256`, `${sha256} ${latestName}\n`, 'utf8') await writeFile( join(downloadDir, 'codetruss-cli-latest.json'), - `${JSON.stringify({ - name: pkg.name, - version: pkg.version, - url: `/downloads/${versionedName}`, - latestUrl: `/downloads/${latestName}`, - sha256, - sbomUrl: `/downloads/${versionedSbomName}`, - sbomSha256, - node: pkg.engines.node, - repository: 'https://github.com/DeliriumPulse/codetruss-cli', - releaseUrl: `https://github.com/DeliriumPulse/codetruss-cli/releases/tag/v${pkg.version}`, - attestationCommand: `gh attestation verify ${versionedName} --repo DeliriumPulse/codetruss-cli`, - }, null, 2)}\n`, + serialiseReleaseManifest(buildReleaseManifest({ pkg, sha256, sbomSha256 })), 'utf8', ) diff --git a/packages/cli/scripts/grammar-pack-render.mjs b/packages/cli/scripts/grammar-pack-render.mjs new file mode 100644 index 0000000..b8092d0 --- /dev/null +++ b/packages/cli/scripts/grammar-pack-render.mjs @@ -0,0 +1,110 @@ +/** + * The exact text of the two files the grammar release GENERATES. + * + * Shared by `build-grammar-packs.mjs`, which writes them, and + * `verify-grammar-packs.mjs`, which re-derives them from the published bytes and + * compares. One renderer is the point: a verifier that re-implements the format + * eventually disagrees with the generator over whitespace and gets loosened + * until it proves nothing. Comparing whole files instead of hunting for expected + * substrings is what makes the check total — it fails on a digest bound to the + * wrong name, on an entry nobody published, and on any hand edit to a file whose + * header says not to edit it by hand. + */ + +/** + * What the CLI's loader does with each artifact. + * + * Roles exist because the loader used to pick the grammar with + * `files.find(f => f.name.startsWith('tree-sitter-'))` — the FIRST match. A pack + * carrying an extra `tree-sitter-evil.wasm` ordered ahead of the real grammar + * would have had it loaded as the grammar. Every artifact now says what it is, + * and the loader requires exactly one artifact per role, so "whichever matched + * the name shape first" is not a question that can be asked. + */ +export const GRAMMAR_FILE_ROLES = ['runtime', 'runtime-wasm', 'grammar'] + +/** + * The published index at `/downloads/grammars/codetruss-grammars-latest.json`. + * + * Deliberately carries no `role`. This file is the site's record of what exists, + * where, how large and with which digest; role is an instruction to the CLI's + * loader, and the CLI never fetches this file — it ships the pin instead. Adding + * a field with no consumer would rewrite bytes already served for nothing. + */ +export function renderGrammarSiteManifest(packs) { + const published = packs.map((pack) => ({ + ...pack, + files: pack.files.map((file) => ({ + name: file.name, + url: file.url, + bytes: file.bytes, + sha256: file.sha256, + })), + })) + return `${JSON.stringify({ packs: published }, null, 2)}\n` +} + +/** + * The compiled-in pin, `packages/cli/src/grammar-pack-manifest.ts`. + * + * Generated rather than hand-maintained because a hand-copied digest is a digest + * that eventually disagrees with the bytes, and this one is the only thing + * standing between a user and executing whatever a compromised origin served. + */ +export function renderGrammarPin(packs) { + const pinned = packs.map((pack) => ({ + ...pack, + files: pack.files.map((file) => ({ + name: file.name, + role: file.role, + url: file.url, + bytes: file.bytes, + sha256: file.sha256, + })), + })) + return `/** + * Pinned grammar-pack digests. GENERATED by scripts/build-grammar-packs.mjs. + * + * Do not edit by hand. These digests are what \`codetruss grammars install\` + * checks a download against, and what every subsequent load re-checks on disk. + * A pack whose bytes do not hash to exactly these values is never loaded, and + * the run discloses Python as skipped instead. + * + * \`pnpm grammars:verify\` re-derives this entire file from the published + * artifacts and compares it byte for byte, so an edit here — a digest moved to + * another name, an artifact nobody published, a fourth entry — fails the build + * rather than shipping. + */ + +/** What the loader does with an artifact. Exactly one artifact per role, per pack. */ +export type GrammarFileRole = ${GRAMMAR_FILE_ROLES.map((role) => `'${role}'`).join(' | ')} + +export interface PinnedGrammarFile { + name: string + role: GrammarFileRole + /** Path under the downloads origin, e.g. \`/downloads/grammars/python-1.0.0/…\`. */ + url: string + bytes: number + sha256: string +} + +export interface PinnedGrammarPack { + name: string + version: string + /** The \`SastLanguage\` this pack enables. */ + language: string + runtime: { package: string; version: string } + grammar: { package: string; version: string } + files: PinnedGrammarFile[] +} + +export const PINNED_GRAMMAR_PACKS: readonly PinnedGrammarPack[] = ${JSON.stringify(pinned, null, 2)} + +/** Pack names this CLI build knows how to install. */ +export const GRAMMAR_PACK_NAMES: readonly string[] = PINNED_GRAMMAR_PACKS.map((pack) => pack.name) + +export function pinnedGrammarPack(name: string): PinnedGrammarPack | undefined { + return PINNED_GRAMMAR_PACKS.find((pack) => pack.name === name) +} +` +} diff --git a/packages/cli/scripts/grammar-pack-sources.mjs b/packages/cli/scripts/grammar-pack-sources.mjs index 1ebf4be..2a29920 100644 --- a/packages/cli/scripts/grammar-pack-sources.mjs +++ b/packages/cli/scripts/grammar-pack-sources.mjs @@ -34,15 +34,19 @@ export const GRAMMAR_PACK_PROVENANCE = { * `language` is the {@link SastLanguage} the pack enables. `files` are copied * byte-for-byte; nothing is recompiled, minified or repackaged, so a reviewer * can diff a published artifact against `node_modules` directly. + * + * `role` says what the CLI's loader does with an artifact, and is the only thing + * it selects on — never the shape of the file name. Exactly one artifact per + * role, checked by the loader and by `pnpm grammars:verify`. */ export const GRAMMAR_PACKS = [ { name: 'python', language: 'python', files: [ - { name: 'tree-sitter.js', source: ['web-tree-sitter', 'tree-sitter.js'] }, - { name: 'tree-sitter.wasm', source: ['web-tree-sitter', 'tree-sitter.wasm'] }, - { name: 'tree-sitter-python.wasm', source: ['tree-sitter-wasms', 'out', 'tree-sitter-python.wasm'] }, + { name: 'tree-sitter.js', role: 'runtime', source: ['web-tree-sitter', 'tree-sitter.js'] }, + { name: 'tree-sitter.wasm', role: 'runtime-wasm', source: ['web-tree-sitter', 'tree-sitter.wasm'] }, + { name: 'tree-sitter-python.wasm', role: 'grammar', source: ['tree-sitter-wasms', 'out', 'tree-sitter-python.wasm'] }, ], }, ] diff --git a/packages/cli/scripts/lockfile-integrity.mjs b/packages/cli/scripts/lockfile-integrity.mjs new file mode 100644 index 0000000..73f6efe --- /dev/null +++ b/packages/cli/scripts/lockfile-integrity.mjs @@ -0,0 +1,81 @@ +/** + * What the workspace lockfile says about a package a grammar pack is cut from. + * + * The pack's provenance (`web-tree-sitter@0.22.6`, `tree-sitter-wasms@0.1.11`) + * is hand-written in `grammar-pack-sources.mjs` and, until this existed, was + * checked against nothing: a dependency bump with a stale provenance string + * would have published a pack that misidentified its own source. Reading the + * lockfile makes that string an assertion the build can fail on. + * + * What a lockfile `integrity` covers is worth being precise about, because it + * bounds what any check built on it can claim: it is the digest of the packed + * npm TARBALL, not of the individual files inside it. It cannot be turned into + * a per-file digest without the tarball itself, which pnpm does not keep — its + * store is content-addressed per file. Re-deriving a pack's digests from an + * independently obtained, signed tarball is therefore a network operation and + * lives in `attest-grammar-sources.mjs`, not in this offline gate. + */ + +/** + * A YAML parser this is not. + * + * The lockfile's `packages:` block is machine-generated with a fixed two-space + * shape, and the alternative — a YAML dependency in a script whose entire job is + * checking supply-chain claims — adds a package to trust in order to verify + * packages. Anything this reader fails to understand is reported as "not found", + * which fails the caller closed. + */ +export function lockfileEntries(lockfile, packageName) { + const lines = lockfile.split('\n') + const start = lines.indexOf('packages:') + if (start === -1) throw new Error('pnpm-lock.yaml has no packages section') + + const entries = [] + for (let index = start + 1; index < lines.length; index += 1) { + const line = lines[index] + // A non-blank line at column zero is the next top-level section. + if (line.trim() !== '' && !line.startsWith(' ')) break + // Scoped names are emitted quoted, because `@types/node@20.1.0:` would + // otherwise start with YAML's reserved `@`. + const header = /^ {2}(?:'([^']+)'|(\S+)):\s*$/.exec(line) + const key = header?.[1] ?? header?.[2] + if (!key) continue + // Peer-suffixed keys (`ts-api-utils@2.5.0(typescript@5.9.3)`) carry a second + // `@` inside the parentheses; drop the suffix before splitting on version. + const bare = key.replace(/\([^)]*\)$/, '') + const at = bare.lastIndexOf('@') + if (at <= 0 || bare.slice(0, at) !== packageName) continue + const integrity = /integrity:\s*(sha\d+-[A-Za-z0-9+/=]+)/.exec(lines[index + 1] ?? '') + if (!integrity) continue + entries.push({ version: bare.slice(at + 1), integrity: integrity[1] }) + } + return entries +} + +/** + * The single lockfile entry for a package, or an error naming what is wrong. + * + * "Exactly one" matters: two resolutions of the same package in one tree means + * the bytes a pack was cut from depend on which copy the build script's relative + * path happened to reach, and a provenance string cannot describe that honestly. + */ +export function lockedSourcePackage(lockfile, packageName, expectedVersion) { + const entries = lockfileEntries(lockfile, packageName) + if (entries.length === 0) { + throw new Error(`pnpm-lock.yaml does not pin ${packageName}; grammar pack provenance cannot be checked`) + } + if (entries.length > 1) { + throw new Error( + `pnpm-lock.yaml resolves ${packageName} to ${entries.length} versions ` + + `(${entries.map((entry) => entry.version).join(', ')}); a grammar pack cannot say which one it was cut from`, + ) + } + const [entry] = entries + if (entry.version !== expectedVersion) { + throw new Error( + `grammar pack provenance says ${packageName}@${expectedVersion} but pnpm-lock.yaml pins ` + + `${packageName}@${entry.version}; update GRAMMAR_PACK_PROVENANCE and bump GRAMMAR_PACK_VERSION`, + ) + } + return entry +} diff --git a/packages/cli/scripts/npm-tarball.mjs b/packages/cli/scripts/npm-tarball.mjs new file mode 100644 index 0000000..a785d79 --- /dev/null +++ b/packages/cli/scripts/npm-tarball.mjs @@ -0,0 +1,109 @@ +/** + * Reading and authenticating an npm tarball, without adding a dependency. + * + * Used by `attest-grammar-sources.mjs` to obtain a grammar pack's upstream bytes + * from something other than the release machine's `node_modules`. A tar reader + * and an ECDSA verify are about eighty lines between them; a package that did + * this for us would be one more thing on the release machine that the check is + * supposed to be independent of. + */ +import { createHash, createPublicKey, createVerify } from 'node:crypto' +import { gunzipSync } from 'node:zlib' + +const BLOCK = 512 + +function trimmed(header, offset, length) { + const raw = header.subarray(offset, offset + length).toString('utf8') + const end = raw.indexOf('\0') + return (end === -1 ? raw : raw.slice(0, end)).trim() +} + +function octal(header, offset, length) { + const text = trimmed(header, offset, length) + return text === '' ? 0 : Number.parseInt(text, 8) +} + +/** + * The header's own checksum, so a misparse is loud. + * + * Every failure mode of this reader has to end in "member not found" or "digest + * mismatch", never in silently returning the wrong bytes. Checking the checksum + * is what makes a wrong offset stop immediately instead of walking into the + * middle of a file and calling it a header. + */ +function headerChecksumOk(header) { + const recorded = octal(header, 148, 8) + let sum = 0 + for (let index = 0; index < BLOCK; index += 1) { + sum += index >= 148 && index < 156 ? 0x20 : header[index] + } + return sum === recorded +} + +/** + * Every regular file in a gzipped tar, keyed by its full path. + * + * Handles the ustar name/prefix split and GNU long names. Pax extended headers + * are skipped rather than interpreted: an npm package whose paths need them + * would resolve to the truncated ustar name here, the caller would not find the + * member it asked for, and the attestation fails closed instead of comparing + * against the wrong file. + */ +export function readTarballMembers(tarball) { + const tar = Buffer.from(gunzipSync(tarball)) + const members = new Map() + let longName + let offset = 0 + + while (offset + BLOCK <= tar.length) { + const header = tar.subarray(offset, offset + BLOCK) + // Two zero blocks end the archive; one is enough to stop reading. + if (header.every((byte) => byte === 0)) break + if (!headerChecksumOk(header)) throw new Error(`tar header at offset ${offset} has a bad checksum`) + + const size = octal(header, 124, 12) + const type = String.fromCharCode(header[156] || 0x30) + const name = longName ?? trimmed(header, 0, 100) + const prefix = trimmed(header, 345, 155) + longName = undefined + + offset += BLOCK + const body = tar.subarray(offset, offset + size) + offset += Math.ceil(size / BLOCK) * BLOCK + + if (type === 'L') { + longName = body.toString('utf8').replace(/\0[\s\S]*$/, '') + continue + } + if (type !== '0' && type !== '\0') continue + members.set(prefix ? `${prefix}/${name}` : name, Buffer.from(body)) + } + return members +} + +/** The `sha512-…` form npm and pnpm both record, for a set of bytes. */ +export function subresourceIntegrity(bytes) { + return `sha512-${createHash('sha512').update(bytes).digest('base64')}` +} + +/** + * The registry's signature over `name@version:integrity`. + * + * This is what makes the tarball's digest something other than a number the + * same server chose: npm signs the association between a version and its + * tarball digest with a key published at `/-/npm/v1/keys`, so a registry mirror + * or a proxy cannot rewrite one without the other. + */ +export function verifyRegistrySignature({ name, version, integrity, signature, keys }) { + const key = keys.find((candidate) => candidate.keyid === signature.keyid) + if (!key) throw new Error(`registry has no published key ${signature.keyid} for ${name}@${version}`) + if (key.keytype !== 'ecdsa-sha2-nistp256' || key.scheme !== 'ecdsa-sha2-nistp256') { + throw new Error(`unsupported registry key type ${key.keytype}/${key.scheme}`) + } + const publicKey = createPublicKey({ key: Buffer.from(key.key, 'base64'), format: 'der', type: 'spki' }) + const verified = createVerify('SHA256') + .update(`${name}@${version}:${integrity}`) + .end() + .verify(publicKey, Buffer.from(signature.sig, 'base64')) + if (!verified) throw new Error(`registry signature for ${name}@${version} does not verify`) +} diff --git a/packages/cli/scripts/release-metadata.mjs b/packages/cli/scripts/release-metadata.mjs new file mode 100644 index 0000000..e39d1a5 --- /dev/null +++ b/packages/cli/scripts/release-metadata.mjs @@ -0,0 +1,47 @@ +// Canonical release identity. The published manifest is byte-compared by +// `verify-release.mjs`, so the builder, the verifier, and the verifier's own +// tests must all derive it from this module rather than restating it. + +export const CLI_REPOSITORY_SLUG = 'CodeTruss/codetruss-cli' +export const CLI_REPOSITORY_URL = `https://github.com/${CLI_REPOSITORY_SLUG}` + +// The CLI repository moved from the `DeliriumPulse` account to the `CodeTruss` +// organisation on 2026-08-07. Releases followed the transfer, and every release +// still in circulation has since been re-attested under the organisation, so one +// command covers all of them. The transferred `--repo DeliriumPulse/…` slug +// returns HTTP 404 and must never be advertised. +// +// This deliberately does NOT vary by version. It did briefly, because the +// artifacts built before the move were only attested under the building account +// and needed `--owner DeliriumPulse --signer-workflow …`. Re-attestation removed +// that split; keeping the branch would have published the weaker command for +// versions the simple one now verifies, and contradicted the release notes, +// which print only this form. + +/** The `gh` invocation that verifies any published artifact's provenance. */ +export function attestationCommand(artifactName) { + return `gh attestation verify ${artifactName} --repo ${CLI_REPOSITORY_SLUG}` +} + +/** The canonical `codetruss-cli-latest.json` body, in its byte-compared key order. */ +export function buildReleaseManifest({ pkg, sha256, sbomSha256 }) { + const versionedName = `codetruss-cli-${pkg.version}.tgz` + return { + name: pkg.name, + version: pkg.version, + url: `/downloads/${versionedName}`, + latestUrl: '/downloads/codetruss-cli-latest.tgz', + sha256, + sbomUrl: `/downloads/codetruss-cli-${pkg.version}.sbom.cdx.json`, + sbomSha256, + node: pkg.engines.node, + repository: CLI_REPOSITORY_URL, + releaseUrl: `${CLI_REPOSITORY_URL}/releases/tag/v${pkg.version}`, + attestationCommand: attestationCommand(versionedName), + } +} + +/** Serialised exactly as the published manifest is written and compared. */ +export function serialiseReleaseManifest(manifest) { + return `${JSON.stringify(manifest, null, 2)}\n` +} diff --git a/packages/cli/scripts/release-package-policy.mjs b/packages/cli/scripts/release-package-policy.mjs index a4da426..e220da3 100644 --- a/packages/cli/scripts/release-package-policy.mjs +++ b/packages/cli/scripts/release-package-policy.mjs @@ -1,3 +1,5 @@ +import { CLI_REPOSITORY_URL } from './release-metadata.mjs' + function hasEntries(value) { if (value === undefined || value === null) return false if (Array.isArray(value)) return value.length > 0 @@ -15,9 +17,9 @@ export function assertReleasePackagePolicy(pkg) { throw new Error('published CLI package identity or executable mapping is invalid') } if ( - pkg.repository?.url !== 'git+https://github.com/DeliriumPulse/codetruss-cli.git' + pkg.repository?.url !== `git+${CLI_REPOSITORY_URL}.git` || pkg.homepage !== 'https://codetruss.com/cli' - || pkg.bugs?.url !== 'https://github.com/DeliriumPulse/codetruss-cli/issues' + || pkg.bugs?.url !== `${CLI_REPOSITORY_URL}/issues` ) { throw new Error('published CLI package does not identify the canonical source, product, and support pages') } diff --git a/packages/cli/scripts/test-release-verifier.mjs b/packages/cli/scripts/test-release-verifier.mjs index f18471e..1ad5454 100644 --- a/packages/cli/scripts/test-release-verifier.mjs +++ b/packages/cli/scripts/test-release-verifier.mjs @@ -5,6 +5,7 @@ import { tmpdir } from 'node:os' import { dirname, join, resolve } from 'node:path' import { fileURLToPath } from 'node:url' import { buildDeterministicPackageArchive, PACKAGE_ARCHIVE_FILES } from './deterministic-package.mjs' +import { attestationCommand, buildReleaseManifest, serialiseReleaseManifest } from './release-metadata.mjs' import { verifyRelease } from './verify-release.mjs' const packageDir = resolve(dirname(fileURLToPath(import.meta.url)), '..') @@ -27,23 +28,53 @@ async function writeRelease(archivePackageDir = packageDir) { await writeFile(join(downloadDir, 'codetruss-cli-latest.sbom.cdx.json'), sbom) await writeFile(join(downloadDir, `${versionedName}.sha256`), `${sha256} ${versionedName}\n`) await writeFile(join(downloadDir, `${latestName}.sha256`), `${sha256} ${latestName}\n`) - await writeFile(join(downloadDir, 'codetruss-cli-latest.json'), `${JSON.stringify({ - name: pkg.name, - version: pkg.version, - url: `/downloads/${versionedName}`, - latestUrl: `/downloads/${latestName}`, - sha256, - sbomUrl: `/downloads/${versionedSbomName}`, - sbomSha256, - node: pkg.engines.node, - repository: 'https://github.com/DeliriumPulse/codetruss-cli', - releaseUrl: `https://github.com/DeliriumPulse/codetruss-cli/releases/tag/v${pkg.version}`, - attestationCommand: `gh attestation verify ${versionedName} --repo DeliriumPulse/codetruss-cli`, - }, null, 2)}\n`) + await writeFile( + join(downloadDir, 'codetruss-cli-latest.json'), + serialiseReleaseManifest(buildReleaseManifest({ pkg, sha256, sbomSha256 })), + ) return { versionedName, latestName } } try { + // The published manifest is a copy-paste instruction. A command that names a + // repository GitHub cannot resolve is worse than no command at all, so pin the + // exact string. Every release still in circulation is attested under the + // organisation, so the command does not vary by version — the assertions below + // are what would fail if a version-dependent form were reintroduced. + assert.equal( + attestationCommand('codetruss-cli-0.2.41.tgz'), + 'gh attestation verify codetruss-cli-0.2.41.tgz --repo CodeTruss/codetruss-cli', + ) + for (const version of ['0.2.14', '0.2.36', '0.2.39', '0.2.40', '0.2.41', '0.3.0', '1.0.0']) { + const command = attestationCommand(`codetruss-cli-${version}.tgz`) + assert.equal( + command, + `gh attestation verify codetruss-cli-${version}.tgz --repo CodeTruss/codetruss-cli`, + `${version} must advertise the organisation-scoped command, which is what verifies today`, + ) + assert.doesNotMatch( + command, + /--repo DeliriumPulse\//, + `${version} must not advertise the transferred repository slug, which returns HTTP 404`, + ) + } + + // The live pointer file is served to users between releases, so it must stay + // in step with the generator without waiting for the next artifact build. + const shippedManifest = JSON.parse( + await readFile(join(resolve(packageDir, '../..'), 'public', 'downloads', 'codetruss-cli-latest.json'), 'utf8'), + ) + assert.equal( + shippedManifest.attestationCommand, + attestationCommand(`codetruss-cli-${shippedManifest.version}.tgz`), + 'public/downloads/codetruss-cli-latest.json advertises a stale attestation command', + ) + assert.equal(shippedManifest.repository, 'https://github.com/CodeTruss/codetruss-cli') + assert.equal( + shippedManifest.releaseUrl, + `https://github.com/CodeTruss/codetruss-cli/releases/tag/v${shippedManifest.version}`, + ) + await mkdir(downloadDir, { recursive: true }) const names = await writeRelease() await verifyRelease({ packageDir, downloadDir }) diff --git a/packages/cli/scripts/verify-grammar-packs.mjs b/packages/cli/scripts/verify-grammar-packs.mjs index 0c67032..b1af26c 100644 --- a/packages/cli/scripts/verify-grammar-packs.mjs +++ b/packages/cli/scripts/verify-grammar-packs.mjs @@ -1,6 +1,7 @@ /** * Gate: the site may not advertise a grammar pack it is not serving, and the - * CLI may not ship a digest that no published artifact hashes to. + * CLI may not ship a pin that is not exactly what the published artifacts + * generate. * * Runs in the root `build` chain beside `cli:artifact:verify`. A checksum alone * would only prove the published file is internally consistent; this also @@ -8,6 +9,19 @@ * pack cannot silently diverge from the runtime and grammar the hosted audit * loads. That equality is the entire basis for calling the two paths the same * analysis. + * + * The two GENERATED files are compared whole, against the same renderer that + * writes them. Matching expected substrings instead — the shape this check used + * to have — proves only that each digest appears somewhere in the pin: it says + * nothing about which file name a digest is bound to, and nothing at all about + * entries the pin contains that nobody published. A hand-edited pin carrying the + * three real digests plus a fourth artifact passed that check. + * + * What this gate does NOT establish is stated plainly in `SECURITY.md`: it reads + * `node_modules` on the same machine that produced both the artifact and the + * pin, so it is an internal-consistency proof, not an independent one. The + * independent check — upstream tarball, registry signature, lockfile integrity — + * needs the network and lives in `attest-grammar-sources.mjs`. */ import { createHash } from 'node:crypto' import { readFile } from 'node:fs/promises' @@ -20,6 +34,8 @@ import { packDirectoryName, packFileUrl, } from './grammar-pack-sources.mjs' +import { GRAMMAR_FILE_ROLES, renderGrammarPin, renderGrammarSiteManifest } from './grammar-pack-render.mjs' +import { lockedSourcePackage } from './lockfile-integrity.mjs' const scriptDir = dirname(fileURLToPath(import.meta.url)) const packageDir = resolve(scriptDir, '..') @@ -29,16 +45,49 @@ function sha256(bytes) { return createHash('sha256').update(bytes).digest('hex') } +/** + * Every artifact says what the loader should do with it, exactly once. + * + * The loader refuses a pack that declares two grammars, but it should never see + * one: this is the hand-edited file where such a pack would be introduced. + */ +function assertRoles(pack, directoryName) { + for (const file of pack.files) { + if (!GRAMMAR_FILE_ROLES.includes(file.role)) { + throw new Error(`${directoryName}/${file.name} declares unknown role ${JSON.stringify(file.role)}`) + } + } + for (const role of GRAMMAR_FILE_ROLES) { + const named = pack.files.filter((file) => file.role === role).map((file) => file.name) + if (named.length !== 1) { + throw new Error( + `${directoryName} declares ${named.length} artifacts for role ${role}` + + `${named.length ? ` (${named.join(', ')})` : ''}; exactly one is required`, + ) + } + } +} + export async function verifyGrammarPacks({ grammarDir = join(repoRoot, 'public', 'downloads', 'grammars'), moduleDir = join(repoRoot, 'node_modules'), pinPath = join(packageDir, 'src', 'grammar-pack-manifest.ts'), + lockfilePath = join(repoRoot, 'pnpm-lock.yaml'), } = {}) { + // The provenance a pack claims must be the version this workspace actually + // installs, or the pack's own description of where its bytes came from is + // unchecked prose. + const lockfile = await readFile(lockfilePath, 'utf8') + for (const source of Object.values(GRAMMAR_PACK_PROVENANCE)) { + lockedSourcePackage(lockfile, source.package, source.version) + } + const manifestPacks = [] for (const pack of GRAMMAR_PACKS) { const directoryName = packDirectoryName(pack) const packDir = join(grammarDir, directoryName) + assertRoles(pack, directoryName) const files = [] for (const file of pack.files) { @@ -69,7 +118,13 @@ export async function verifyGrammarPacks({ if (sidecar !== `${digest} ${file.name}\n`) { throw new Error(`grammar pack ${directoryName}/${file.name}.sha256 does not match its artifact`) } - files.push({ name: file.name, url: packFileUrl(pack, file.name), bytes: published.length, sha256: digest }) + files.push({ + name: file.name, + role: file.role, + url: packFileUrl(pack, file.name), + bytes: published.length, + sha256: digest, + }) } manifestPacks.push({ @@ -82,27 +137,24 @@ export async function verifyGrammarPacks({ }) } - const manifestPath = join(grammarDir, 'codetruss-grammars-latest.json') - const manifest = await readFile(manifestPath, 'utf8') - const expectedManifest = `${JSON.stringify({ packs: manifestPacks }, null, 2)}\n` - if (manifest !== expectedManifest) { + const manifest = await readFile(join(grammarDir, 'codetruss-grammars-latest.json'), 'utf8') + if (manifest !== renderGrammarSiteManifest(manifestPacks)) { throw new Error('codetruss-grammars-latest.json does not match the published packs; run pnpm grammars:release') } // The CLI's compiled-in pin is the security boundary. If it disagrees with the - // published artifact, either every install fails closed or — worse, if the pin - // were stale in the other direction — the CLI would accept bytes nobody - // reviewed. Neither may reach a build. + // published artifacts, either every install fails closed or — worse, if the + // pin were wrong in the other direction — the CLI would accept bytes nobody + // reviewed. Whole-file, because every part of this file is load-bearing: the + // digests, the names they are bound to, the URLs they are fetched from, the + // roles the loader selects on, and the lookup function underneath them. const pin = await readFile(pinPath, 'utf8') - for (const pack of manifestPacks) { - for (const file of pack.files) { - if (!pin.includes(`"sha256": "${file.sha256}"`)) { - throw new Error( - `src/grammar-pack-manifest.ts does not pin ${pack.name}-${pack.version}/${file.name}; ` - + 'run pnpm grammars:release', - ) - } - } + const expectedPin = renderGrammarPin(manifestPacks) + if (pin !== expectedPin) { + throw new Error( + 'src/grammar-pack-manifest.ts is not what the published grammar packs generate ' + + `(${sha256(pin)} vs ${sha256(expectedPin)}); it is generated, not hand-written — run pnpm grammars:release`, + ) } return manifestPacks diff --git a/packages/cli/scripts/verify-release.mjs b/packages/cli/scripts/verify-release.mjs index 534e571..4ac669b 100644 --- a/packages/cli/scripts/verify-release.mjs +++ b/packages/cli/scripts/verify-release.mjs @@ -3,6 +3,7 @@ import { readFile } from 'node:fs/promises' import { dirname, join, resolve } from 'node:path' import { fileURLToPath, pathToFileURL } from 'node:url' import { assertReleasePackagePolicy } from './release-package-policy.mjs' +import { buildReleaseManifest, serialiseReleaseManifest } from './release-metadata.mjs' import { cycloneDxSerialNumber } from './generate-sbom.mjs' import { verifyDeterministicPackageArchive } from './verify-deterministic-package.mjs' @@ -24,21 +25,9 @@ export async function verifyRelease({ packageDir = defaultPackageDir, downloadDi const latestSbom = await readFile(join(downloadDir, 'codetruss-cli-latest.sbom.cdx.json')) const versionedSha = digest(versioned) const sbomSha = digest(versionedSbom) - const expectedMetadata = { - name: pkg.name, - version: pkg.version, - url: `/downloads/${versionedName}`, - latestUrl: `/downloads/${latestName}`, - sha256: versionedSha, - sbomUrl: `/downloads/${versionedSbomName}`, - sbomSha256: sbomSha, - node: pkg.engines.node, - repository: 'https://github.com/DeliriumPulse/codetruss-cli', - releaseUrl: `https://github.com/DeliriumPulse/codetruss-cli/releases/tag/v${pkg.version}`, - attestationCommand: `gh attestation verify ${versionedName} --repo DeliriumPulse/codetruss-cli`, - } + const expectedMetadata = buildReleaseManifest({ pkg, sha256: versionedSha, sbomSha256: sbomSha }) const metadataBytes = await readFile(join(downloadDir, 'codetruss-cli-latest.json')) - const expectedMetadataBytes = Buffer.from(`${JSON.stringify(expectedMetadata, null, 2)}\n`, 'utf8') + const expectedMetadataBytes = Buffer.from(serialiseReleaseManifest(expectedMetadata), 'utf8') if (!metadataBytes.equals(expectedMetadataBytes)) { throw new Error(`release metadata is not the canonical manifest for CLI ${pkg.version}; run pnpm cli:release`) } diff --git a/packages/cli/src/analysis.ts b/packages/cli/src/analysis.ts index 152e3f6..b42411e 100644 --- a/packages/cli/src/analysis.ts +++ b/packages/cli/src/analysis.ts @@ -1,5 +1,5 @@ import { createHash } from 'node:crypto' -import { runAnalyzers, type AnalyzerFinding, type AnalyzerPass, type IndexCoverage } from '@codetruss/analyzer-engine' +import { annotateSuppressions, runAnalyzers, type AnalyzerFinding, type AnalyzerPass, type IndexCoverage } from '@codetruss/analyzer-engine' import { CLI_SAST_UNCHECKED_CLASSES } from '@codetruss/analyzer-engine/security/local-profile' import { indexRepository } from './indexer.js' import { LOCAL_SAST_PASS_ID, runLocalSast } from './local-sast.js' @@ -18,9 +18,17 @@ export async function analyzeRepository(root: string) { sastUncheckedClasses: CLI_SAST_UNCHECKED_CLASSES, }) const localSast = await runLocalSast(index) + // Inline `codetruss-ignore` markers are read once, over every pass at once, + // so no analyzer can be added later and quietly miss them — and so the + // aggregate list and the per-pass lists cannot disagree about what a + // developer dismissed. The findings are annotated, never removed. + const passes = [...result.passes, localSast.pass].map((pass) => ({ + ...pass, + result: { ...pass.result, findings: annotateSuppressions(pass.result.findings, index) }, + })) return { - findings: [...result.findings, ...localSast.findings], - passes: [...result.passes, localSast.pass], + findings: passes.flatMap((pass) => pass.result.findings), + passes, index, } } finally { @@ -183,6 +191,26 @@ export function changedFindings(findings: AnalyzerFinding[], files: ChangedFile[ }) } +/** Findings a developer dismissed with an inline `codetruss-ignore: ` comment. */ +export function suppressedFindings(findings: AnalyzerFinding[]): AnalyzerFinding[] { + return findings.filter((finding) => finding.suppression?.applied) +} + +/** + * Findings that still count. A marker without a reason suppresses nothing, so + * its finding stays here — the rejected marker is disclosed separately. + */ +export function reportedFindings(findings: AnalyzerFinding[]): AnalyzerFinding[] { + return findings.filter((finding) => !finding.suppression?.applied) +} + +/** `path:line` of every marker that gave no reason, deduplicated for disclosure. */ +export function rejectedSuppressionSites(findings: AnalyzerFinding[]): string[] { + return [...new Set(findings + .filter((finding) => finding.suppression && !finding.suppression.applied) + .map((finding) => `${finding.filePath ?? ''}:${finding.suppression?.markerLine ?? 0}`))].sort() +} + export function computeVerdict(input: { agentExitCode?: number verifications: VerificationResult[] @@ -213,14 +241,19 @@ export function computeVerdict(input: { * does not grant it. Promotion is a deliberate later change, not a default. */ const isLocalSast = (finding: AnalyzerFinding) => finding.analyzerId === LOCAL_SAST_PASS_ID - const blocking = input.findings.filter( + // A dismissed finding stops gating — that is what dismissing it is for. It + // does NOT stop being evidence: it is listed with its reason on the receipt, + // and a PASS reached over one says so in its own reasons below. + const dismissed = suppressedFindings(input.findings) + const findings = reportedFindings(input.findings) + const blocking = findings.filter( (finding) => severityRank[finding.severity] >= severityRank.HIGH && (finding.category === 'SECURITY_HYGIENE' || finding.category === 'DEPENDENCY') && !isLocalSast(finding), ) if (blocking.length) failed.push(`${blocking.length} high/critical security or dependency finding(s) affect changed files`) - const localSastFindings = input.findings.filter(isLocalSast) + const localSastFindings = findings.filter(isLocalSast) if (localSastFindings.length) { const rules = [...new Set(localSastFindings.map((finding) => String(finding.metadata?.ruleId ?? 'security')))].sort() review.push(`${localSastFindings.length} local security finding(s) affect changed files (${rules.join(', ')})`) @@ -235,10 +268,13 @@ export function computeVerdict(input: { if (sensitive.length) review.push(`sensitive surfaces changed: ${sensitive.slice(0, 5).map((file) => `${file.path} (${file.sensitive})`).join(', ')}`) if (deps.length) review.push(`dependency manifests or lockfiles changed: ${deps.slice(0, 5).map((file) => file.path).join(', ')}`) if (input.startDirty) review.push('the working tree was dirty at session start, so exact agent attribution is uncertain') - const reviewFindings = input.findings.filter( + const reviewFindings = findings.filter( (finding) => severityRank[finding.severity] >= severityRank.MEDIUM && !blocking.includes(finding) && !isLocalSast(finding), ) if (reviewFindings.length) review.push(`${reviewFindings.length} medium-or-higher analyzer finding(s) affect changed files`) + if (dismissed.length) { + notes.push(`${dismissed.length} finding(s) on changed files were dismissed by an inline codetruss-ignore comment and are listed with their reasons on this receipt`) + } if (input.llm?.diffCoverage?.truncated) { review.push(`local ${input.llm.provider} review covered ${input.llm.diffCoverage.reviewedBytes} of ${input.llm.diffCoverage.totalBytes} diff bytes`) } @@ -264,9 +300,19 @@ export function analyzerReceipt( _baseline?: Awaited>, delta?: FindingDelta, ): Receipt['analyzers'] { + const relevant = delta ? [...delta.introduced, ...delta.worsened] : analysis.findings + // Dismissals are reported for the WHOLE repository, not only the delta. A + // marker written in an unchanged file is a standing instruction to look away, + // and a receipt that only mentioned the ones touched this turn would let the + // rest accumulate unseen. Both lists are omitted when empty, so a repository + // that dismisses nothing renders exactly the bytes it did before. + const suppressed = suppressedFindings(analysis.findings) + const rejected = rejectedSuppressionSites(analysis.findings) return { passes: analysis.passes, - findings: delta ? [...delta.introduced, ...delta.worsened] : analysis.findings, + findings: reportedFindings(relevant), + ...(suppressed.length ? { suppressed } : {}), + ...(rejected.length ? { rejectedSuppressions: rejected } : {}), analysisProfile: LOCAL_ANALYSIS_PROFILE, delta: delta ? { introduced: delta.introduced.length, diff --git a/packages/cli/src/grammar-pack-manifest.ts b/packages/cli/src/grammar-pack-manifest.ts index 1ab907b..6a3b19b 100644 --- a/packages/cli/src/grammar-pack-manifest.ts +++ b/packages/cli/src/grammar-pack-manifest.ts @@ -5,10 +5,19 @@ * checks a download against, and what every subsequent load re-checks on disk. * A pack whose bytes do not hash to exactly these values is never loaded, and * the run discloses Python as skipped instead. + * + * `pnpm grammars:verify` re-derives this entire file from the published + * artifacts and compares it byte for byte, so an edit here — a digest moved to + * another name, an artifact nobody published, a fourth entry — fails the build + * rather than shipping. */ +/** What the loader does with an artifact. Exactly one artifact per role, per pack. */ +export type GrammarFileRole = 'runtime' | 'runtime-wasm' | 'grammar' + export interface PinnedGrammarFile { name: string + role: GrammarFileRole /** Path under the downloads origin, e.g. `/downloads/grammars/python-1.0.0/…`. */ url: string bytes: number @@ -41,18 +50,21 @@ export const PINNED_GRAMMAR_PACKS: readonly PinnedGrammarPack[] = [ "files": [ { "name": "tree-sitter.js", + "role": "runtime", "url": "/downloads/grammars/python-1.0.0/tree-sitter.js", "bytes": 74197, "sha256": "ddcacb69cd26c07322c51b798a63805fd99c272177c9633a978f3886358ca070" }, { "name": "tree-sitter.wasm", + "role": "runtime-wasm", "url": "/downloads/grammars/python-1.0.0/tree-sitter.wasm", "bytes": 188635, "sha256": "29208e71028ab0c11dfcc941255075aad75545394467aa22d817a6356714090f" }, { "name": "tree-sitter-python.wasm", + "role": "grammar", "url": "/downloads/grammars/python-1.0.0/tree-sitter-python.wasm", "bytes": 476105, "sha256": "9056d0fb0c337810d019fae350e8167786119da98f0f282aceae7ab89ee8253b" diff --git a/packages/cli/src/grammar-pack.ts b/packages/cli/src/grammar-pack.ts index 38a932c..522e363 100644 --- a/packages/cli/src/grammar-pack.ts +++ b/packages/cli/src/grammar-pack.ts @@ -38,9 +38,12 @@ import { PINNED_GRAMMAR_PACKS, pinnedGrammarPack, type PinnedGrammarPack } from * verification or the install somewhere the digest never covered; * - the only origin is the CodeTruss downloads host — never a third-party CDN, * never a redirect off-origin; - * - any failure — absent, short, over-long, wrong digest, unreadable — resolves - * to "pack unavailable", and the run discloses Python as skipped. There is no - * degraded mode in which unverified bytes are executed. + * - a download that stalls is abandoned rather than waited out: an origin that + * never answers, or answers one byte and holds the socket open, ends in an + * error with a reason instead of a hung install; + * - any failure — absent, short, over-long, wrong digest, unreadable, stalled — + * resolves to "pack unavailable", and the run discloses Python as skipped. + * There is no degraded mode in which unverified bytes are executed. */ const PRODUCTION_DOWNLOAD_ORIGIN = 'https://codetruss.com' @@ -279,40 +282,104 @@ export async function inspectGrammarPack( return { status: 'verified', pack, dir, contents } } +/** + * How long one artifact download may take, and how long it may say nothing. + * + * Two clocks, because they catch different failures. The total budget bounds a + * transfer that is progressing but will not finish; the idle budget catches the + * drip feed — a server that writes one byte and then holds the socket open + * forever, which no total-only budget shorter than "forever" ever ends, and + * which `fetch` on its own will wait out indefinitely. `grammars install` is a + * foreground command a person is watching, so an origin that stalls has to fail + * with a sentence, not hang. + * + * The values are generous on purpose: 738 KB over a bad hotel connection is + * still well inside them, and a false abort would look exactly like a tampered + * download to a user who cannot tell the two apart. + */ +export interface GrammarDownloadLimits { + /** Whole-transfer budget for one artifact, from request to last byte. */ + totalMs: number + /** Longest silence allowed — between the request and the first byte, or between two chunks. */ + idleMs: number +} + +const DEFAULT_DOWNLOAD_LIMITS: GrammarDownloadLimits = { totalMs: 120_000, idleMs: 20_000 } + /** * Download one artifact into `target`, verifying as the bytes arrive. * * The pinned length is enforced DURING the stream rather than after it, so a * hostile or broken origin cannot fill the user's disk before the digest gets a * chance to disagree. + * + * Both deadlines drive one `AbortController` rather than `AbortSignal.timeout` + * so the reason the transfer was abandoned survives into the error: an aborted + * `fetch` reports only "This operation was aborted", which tells a user nothing + * about whether their network stalled or the origin is misbehaving. */ -async function downloadVerified(url: string, target: string, expected: { bytes: number; sha256: string }): Promise { - const response = await fetch(url, { redirect: 'error', headers: { accept: 'application/octet-stream' } }) - if (!response.ok) throw new Error(`${url} responded ${response.status}`) - if (!response.body) throw new Error(`${url} returned no body`) +async function downloadVerified( + url: string, + target: string, + expected: { bytes: number; sha256: string }, + limits: GrammarDownloadLimits, +): Promise { + const controller = new AbortController() + /** Set only when a deadline fired, so an abort is reported as itself. */ + let abandoned: string | undefined + const abandon = (reason: string) => { + if (abandoned) return + abandoned = reason + controller.abort() + } + const total = setTimeout(() => abandon(`${url} did not finish within ${limits.totalMs}ms`), limits.totalMs) + let idle: ReturnType | undefined + const restartIdle = () => { + clearTimeout(idle) + idle = setTimeout(() => abandon(`${url} sent no data for ${limits.idleMs}ms`), limits.idleMs) + } - const hash = createHash('sha256') - let received = 0 - const measured = new Readable({ read() {} }) - const source = Readable.fromWeb(response.body as Parameters[0]) - source.on('data', (chunk: Buffer) => { - received += chunk.length - if (received > expected.bytes) { - measured.destroy(new Error(`${url} sent more than the pinned ${expected.bytes} bytes`)) - source.destroy() - return - } - hash.update(chunk) - measured.push(chunk) - }) - source.on('end', () => measured.push(null)) - source.on('error', (error: Error) => measured.destroy(error)) + try { + // Started before the request, so a server that accepts the connection and + // never answers is on the same clock as one that stalls mid-body. + restartIdle() + const response = await fetch(url, { + redirect: 'error', + headers: { accept: 'application/octet-stream' }, + signal: controller.signal, + }) + if (!response.ok) throw new Error(`${url} responded ${response.status}`) + if (!response.body) throw new Error(`${url} returned no body`) - await pipeline(measured, createWriteStream(target, { mode: 0o600 })) + const hash = createHash('sha256') + let received = 0 + const measured = new Readable({ read() {} }) + const source = Readable.fromWeb(response.body as Parameters[0]) + source.on('data', (chunk: Buffer) => { + restartIdle() + received += chunk.length + if (received > expected.bytes) { + measured.destroy(new Error(`${url} sent more than the pinned ${expected.bytes} bytes`)) + source.destroy() + return + } + hash.update(chunk) + measured.push(chunk) + }) + source.on('end', () => measured.push(null)) + source.on('error', (error: Error) => measured.destroy(error)) - if (received !== expected.bytes) throw new Error(`${url} sent ${received} bytes, expected ${expected.bytes}`) - const digest = hash.digest('hex') - if (digest !== expected.sha256) throw new Error(`${url} has digest ${digest}, expected ${expected.sha256}`) + await pipeline(measured, createWriteStream(target, { mode: 0o600 })) + + if (received !== expected.bytes) throw new Error(`${url} sent ${received} bytes, expected ${expected.bytes}`) + const digest = hash.digest('hex') + if (digest !== expected.sha256) throw new Error(`${url} has digest ${digest}, expected ${expected.sha256}`) + } catch (error) { + throw abandoned ? new Error(abandoned) : error + } finally { + clearTimeout(total) + clearTimeout(idle) + } } /** @@ -358,6 +425,7 @@ export interface GrammarInstallResult { export async function installGrammarPack( name: string, env: NodeJS.ProcessEnv = process.env, + limits: GrammarDownloadLimits = DEFAULT_DOWNLOAD_LIMITS, ): Promise { const existing = await inspectGrammarPack(name, env) const pack = existing.pack @@ -375,7 +443,7 @@ export async function installGrammarPack( const scratch = await mkdtemp(join(root, `.${pack.name}-${pack.version}.`)) try { for (const file of pack.files) { - await downloadVerified(`${origin}${file.url}`, join(scratch, file.name), file) + await downloadVerified(`${origin}${file.url}`, join(scratch, file.name), file, limits) } const target = grammarPackDir(pack, env) // A failed or partial previous install is replaced wholesale rather than diff --git a/packages/cli/src/grammar-parser.ts b/packages/cli/src/grammar-parser.ts index 885510b..b6b74f2 100644 --- a/packages/cli/src/grammar-parser.ts +++ b/packages/cli/src/grammar-parser.ts @@ -5,6 +5,7 @@ import { compileFunction } from 'node:vm' import type { ParsedTree, SastLanguage, SastParser, SyntaxNode } from '@codetruss/analyzer-engine/security/lang' import { MAX_SOURCE_BYTES } from '@codetruss/analyzer-engine/security/lang' import { inspectGrammarPack, type GrammarPackState } from './grammar-pack.js' +import type { PinnedGrammarFile, PinnedGrammarPack } from './grammar-pack-manifest.js' /** * Turn a verified grammar pack into the {@link SastParser} the engine expects. @@ -101,12 +102,17 @@ export async function loadGrammarParser( if (state.status === 'failed') return { status: 'failed', kind: 'digest', reason: state.reason } const language = state.pack.language as SastLanguage - const grammarFile = state.pack.files.find((file) => file.name.startsWith('tree-sitter-')) - if (!grammarFile) return { status: 'failed', kind: 'digest', reason: 'pack declares no grammar artifact' } + const artifacts = artifactsByRole(state.pack) + if ('reason' in artifacts) { + // The pack verified; the CLI's own pin is malformed. Calling that a digest + // failure would publish a receipt accusing the user's install of tampering + // over a defect in this binary. + return { status: 'failed', kind: 'runtime', reason: artifacts.reason } + } let cached = runtimeCache.get(state.dir) if (!cached) { - cached = buildParser(state, grammarFile.name, language) + cached = buildParser(state, artifacts, language) runtimeCache.set(state.dir, cached) } const result = await cached @@ -114,6 +120,42 @@ export async function loadGrammarParser( return { status: 'verified', parser: result.parser, languages: new Set([language]) } } +/** The artifact names this loader will execute, resolved by role. */ +interface PackArtifacts { + runtime: string + runtimeWasm: string + grammar: string +} + +/** + * Which artifact fills which role — by the pin's own `role` field, never by the + * shape of a file name. + * + * `files.find((file) => file.name.startsWith('tree-sitter-'))` returned the + * FIRST match, so a pack that carried an extra `tree-sitter-evil.wasm` ordered + * ahead of `tree-sitter-python.wasm` would have had the extra file loaded as the + * grammar — and the pin verifier that only proved each digest appeared somewhere + * in the generated file would not have caught the extra entry. Requiring exactly + * one artifact per role means an ambiguous pack cannot resolve to "whichever one + * came first"; it does not resolve at all. + */ +function artifactsByRole(pack: PinnedGrammarPack): PackArtifacts | { reason: string } { + const named = (role: PinnedGrammarFile['role']): string | { reason: string } => { + const matches = pack.files.filter((file) => file.role === role) + if (matches.length !== 1) { + return { reason: `pack declares ${matches.length} artifacts for role ${role}, expected exactly one` } + } + return matches[0].name + } + const runtime = named('runtime') + const runtimeWasm = named('runtime-wasm') + const grammar = named('grammar') + if (typeof runtime !== 'string') return runtime + if (typeof runtimeWasm !== 'string') return runtimeWasm + if (typeof grammar !== 'string') return grammar + return { runtime, runtimeWasm, grammar } +} + /** * A name emscripten can carry around for the runtime WASM that resolves to no * file anywhere. @@ -153,12 +195,12 @@ function runVerifiedModule(source: Buffer, filename: string, dirname: string): u async function buildParser( state: Extract, - grammarFileName: string, + artifacts: PackArtifacts, language: SastLanguage, ): Promise<{ parser: SastParser } | { reason: string }> { - const runtimeSource = state.contents.get('tree-sitter.js') - const runtimeWasm = state.contents.get('tree-sitter.wasm') - const grammarWasm = state.contents.get(grammarFileName) + const runtimeSource = state.contents.get(artifacts.runtime) + const runtimeWasm = state.contents.get(artifacts.runtimeWasm) + const grammarWasm = state.contents.get(artifacts.grammar) if (!runtimeSource || !runtimeWasm || !grammarWasm) { return { reason: 'verified pack did not carry its own bytes' } } @@ -167,7 +209,7 @@ async function buildParser( try { const TreeSitter = runVerifiedModule( runtimeSource, - join(state.dir, 'tree-sitter.js'), + join(state.dir, artifacts.runtime), state.dir, ) as TreeSitterModule if (typeof TreeSitter?.init !== 'function') { diff --git a/packages/cli/src/hook-agent-config.ts b/packages/cli/src/hook-agent-config.ts new file mode 100644 index 0000000..02d2f9a --- /dev/null +++ b/packages/cli/src/hook-agent-config.ts @@ -0,0 +1,105 @@ +import { readFile } from 'node:fs/promises' +import { join } from 'node:path' +import type { AgentSurface } from './hook-targets.js' + +export type HookHandler = { type?: string; command?: string; args?: string[]; [key: string]: unknown } +export type HookGroup = { matcher?: string; hooks?: HookHandler[]; [key: string]: unknown } +export type HookDocument = { hooks?: Record; [key: string]: unknown } + +export const AGENT_EVENTS = ['UserPromptSubmit', 'PostToolUse', 'Stop'] as const +export type AgentEvent = typeof AGENT_EVENTS[number] + +// The internal Stop review has a five-minute hard deadline. Keep the installed +// agent envelope wider so it can persist a failure result and clean private Git +// evidence before the host terminates the hook process. +const STOP_HOOK_TIMEOUT_SECONDS = 6 * 60 + +/** The host-owned settings file each agent surface reads its hooks from. */ +export function agentSettingsPath(root: string, surface: AgentSurface): string { + return join(root, surface === 'claude' ? '.claude/settings.json' : '.codex/hooks.json') +} + +function isRecord(value: unknown): value is Record { + return Boolean(value) && typeof value === 'object' && !Array.isArray(value) +} + +export async function readHookDocument(path: string): Promise { + let text: string + try { + text = await readFile(path, 'utf8') + } catch (error) { + if ((error as NodeJS.ErrnoException).code === 'ENOENT') return {} + throw error + } + let parsed: unknown + try { + parsed = JSON.parse(text) + } catch (error) { + throw new Error(`refusing to overwrite invalid JSON in ${path}: ${error instanceof Error ? error.message : String(error)}`) + } + if (!isRecord(parsed)) throw new Error(`refusing to overwrite ${path}: top-level JSON must be an object`) + if (parsed.hooks !== undefined && !isRecord(parsed.hooks)) { + throw new Error(`refusing to overwrite ${path}: hooks must be an object`) + } + return parsed as HookDocument +} + +export function eventGroups(doc: HookDocument, path: string, event: string): HookGroup[] { + doc.hooks ??= {} + const value = doc.hooks[event] + if (value === undefined) { + const groups: HookGroup[] = [] + doc.hooks[event] = groups + return groups + } + if (!Array.isArray(value) || value.some((group) => !isRecord(group))) { + throw new Error(`refusing to overwrite ${path}: hooks.${event} must be an array of objects`) + } + for (const group of value as HookGroup[]) { + if (group.hooks !== undefined && (!Array.isArray(group.hooks) || group.hooks.some((handler) => !isRecord(handler)))) { + throw new Error(`refusing to overwrite ${path}: hooks.${event}[].hooks must be an array of objects`) + } + } + return value as HookGroup[] +} + +export function isCodeTrussHandler(handler: HookHandler): boolean { + return [handler.command, ...(handler.args ?? [])].some((value) => typeof value === 'string' && value.includes('.codetruss/hooks/agent.cjs')) +} + +export function removeCodeTrussHandlers(groups: HookGroup[]): void { + for (let index = groups.length - 1; index >= 0; index--) { + const group = groups[index] + if (!Array.isArray(group.hooks)) continue + group.hooks = group.hooks.filter((handler) => !isCodeTrussHandler(handler)) + if (group.hooks.length === 0) groups.splice(index, 1) + } +} + +function agentCommand(surface: AgentSurface): HookHandler { + if (surface === 'claude') { + return { + command: 'node', + args: ['${CLAUDE_PROJECT_DIR}/.codetruss/hooks/agent.cjs', 'claude'], + } + } + return { + command: 'node "$(git -c core.longpaths=true rev-parse --show-toplevel)/.codetruss/hooks/agent.cjs" codex', + commandWindows: "$root = git -c core.longpaths=true rev-parse --show-toplevel; if ($LASTEXITCODE -eq 0) { node (Join-Path $root '.codetruss/hooks/agent.cjs') codex }", + } +} + +/** The group matcher CodeTruss installs alongside an event's handler, if any. */ +export function agentEventMatcher(event: AgentEvent): string | undefined { + return event === 'PostToolUse' ? 'Edit|Write' : undefined +} + +export function agentHandler(surface: AgentSurface, event: AgentEvent): HookHandler { + const timeout = event === 'PostToolUse' ? 10 : event === 'UserPromptSubmit' ? 60 : STOP_HOOK_TIMEOUT_SECONDS + const statusMessage = event === 'PostToolUse' + ? 'Checking scope with CodeTruss' + : event === 'UserPromptSubmit' + ? 'Capturing CodeTruss turn baseline' + : 'Writing CodeTruss review receipt' + return { type: 'command', ...agentCommand(surface), timeout, statusMessage } +} diff --git a/packages/cli/src/hook-agent-runner.ts b/packages/cli/src/hook-agent-runner.ts new file mode 100644 index 0000000..0ce8dad --- /dev/null +++ b/packages/cli/src/hook-agent-runner.ts @@ -0,0 +1,81 @@ +/** + * The `.codetruss/hooks/agent.cjs` payload installed into a repository. + * + * This is a standalone CommonJS program executed by the host agent, not by this + * CLI, so it is kept verbatim in one place: `inspectRunner` compares the file on + * disk against this exact string to detect drift, which means any incidental + * edit here is a behaviour change for every installed repository. + */ +export const AGENT_RUNNER = `'use strict' +const { existsSync } = require('node:fs') +const { execFileSync, spawnSync } = require('node:child_process') +const { join } = require('node:path') + +const surface = process.argv[2] +const maxInputBytes = 16 * 1024 * 1024 +if (surface !== 'claude' && surface !== 'codex') { + process.stderr.write('codetruss hook: expected claude or codex\\n') + process.exit(3) +} + +function safeFailure(input, message) { + let event + let stopHookActive = false + const textInput = input.toString('utf8') + try { + const parsed = JSON.parse(textInput) + event = parsed.hook_event_name + stopHookActive = parsed.stop_hook_active === true + } catch { + const prefix = textInput.slice(0, 64 * 1024) + event = /"hook_event_name"\\s*:\\s*"([^"]+)"/.exec(prefix)?.[1] + stopHookActive = /"stop_hook_active"\\s*:\\s*true/.test(prefix) + } + const text = ('CodeTruss hook failed safely: ' + message).slice(0, 9000) + if (event === 'UserPromptSubmit' || (event === 'Stop' && !stopHookActive)) { + return { decision: 'block', reason: text } + } + return { systemMessage: text } +} + +const chunks = [] +let inputBytes = 0 +let tooLarge = false +process.stdin.on('data', (value) => { + const chunk = Buffer.from(value) + inputBytes += chunk.length + if (inputBytes > maxInputBytes) tooLarge = true + else chunks.push(chunk) +}) +process.stdin.on('end', () => { + const input = Buffer.concat(chunks) + if (tooLarge) { + process.stdout.write(JSON.stringify(safeFailure(input, 'hook input exceeded 16 MiB')) + '\\n') + process.exit(0) + } + let root + try { + root = execFileSync('git', ['-c', 'core.longpaths=true', 'rev-parse', '--show-toplevel'], { encoding: 'utf8' }).trim() + } catch { + process.stdout.write(JSON.stringify(safeFailure(input, 'could not resolve the Git repository root')) + '\\n') + process.exit(0) + } + const local = join(root, 'node_modules', '.bin', process.platform === 'win32' ? 'codetruss.cmd' : 'codetruss') + const command = existsSync(local) ? local : 'codetruss' + const result = spawnSync(command, ['hooks', 'dispatch', surface], { + cwd: root, + input, + encoding: 'utf8', + shell: process.platform === 'win32', + maxBuffer: 64 * 1024, + }) + if (result.error || result.status !== 0) { + const detail = result.error ? result.error.message : (result.stderr || 'dispatch exited with status ' + String(result.status)).trim() + process.stdout.write(JSON.stringify(safeFailure(input, detail)) + '\\n') + process.exit(0) + } + if (result.stderr) process.stderr.write(result.stderr) + if (result.stdout) process.stdout.write(result.stdout) + process.exit(0) +}) +` diff --git a/packages/cli/src/hook-doctor.ts b/packages/cli/src/hook-doctor.ts new file mode 100644 index 0000000..9823220 --- /dev/null +++ b/packages/cli/src/hook-doctor.ts @@ -0,0 +1,297 @@ +import { lstat, readFile } from 'node:fs/promises' +import { join } from 'node:path' +import { loadConfig } from './config.js' +import { + AGENT_EVENTS, + agentEventMatcher, + agentHandler, + agentSettingsPath, + eventGroups, + isCodeTrussHandler, + readHookDocument, + type HookDocument, + type HookGroup, + type HookHandler, +} from './hook-agent-config.js' +import { AGENT_RUNNER } from './hook-agent-runner.js' +import { executablePath, installedCliVersion } from './hook-executable.js' +import { BEGIN_MARKER, END_MARKER, effectivePreCommitPath, preCommitBlock } from './hook-pre-commit.js' +import { parseTargets, type AgentSurface, type HookTarget } from './hook-targets.js' +import { CLI_VERSION } from './version.js' +import { verifyCommandTrustStatus } from './verify-trust.js' + +const SUPPORTS_POSIX_FILE_MODES = process.platform !== 'win32' + +export interface HookDoctorCheck { + level: 'ok' | 'warning' | 'error' + target: HookTarget | 'config' | 'runtime' | 'agent-runtime' + message: string + path?: string +} + +export interface HookDoctorResult { + ok: boolean + checks: HookDoctorCheck[] +} + +export type HookHealthStatus = 'not_installed' | 'healthy' | 'warning' | 'unhealthy' + +export interface LocalHookHealth { + preCommit: HookHealthStatus + claude: HookHealthStatus + codex: HookHealthStatus +} + +type AddCheck = (check: HookDoctorCheck) => void + +/** + * The hooks invoke `codetruss` by name, so they run whatever PATH resolves — + * which is not always what was just installed. A stale binary earlier in PATH + * silently shadows the new one, and the installer's own "Ready" message used to + * hide it. Reported as a warning: the hooks still work, they just are not this + * version. Determined by manifest version, so a repository-local install of the + * same version is not mistaken for a shadow. + */ +async function inspectExecutableShadow(executable: string, add: AddCheck): Promise { + const resolved = await installedCliVersion(executable) + if (!resolved || resolved === CLI_VERSION) return + add({ + level: 'warning', + target: 'runtime', + message: `installed hooks resolve codetruss ${resolved}, but this CLI is ${CLI_VERSION}; put the intended install first on PATH or remove the older one`, + path: executable, + }) +} + +const AGENT_HANDLER_FIELDS = ['type', 'command', 'args', 'commandWindows', 'timeout', 'statusMessage'] as const + +/** + * Which fields of an installed agent handler no longer match what this CLI + * would write. Doctor always compared handlers exactly but reported only that + * they "differ", which reads the same for a config installed several versions + * ago as for a deliberate hand-edit. Field names only: enough to diagnose, + * without putting handler command text in the message. + */ +function agentHandlerDrift(handler: HookHandler, expected: HookHandler): string[] { + return AGENT_HANDLER_FIELDS.filter((field) => ( + JSON.stringify(handler[field] ?? null) !== JSON.stringify(expected[field] ?? null) + )) +} + +async function inspectAgentHook(root: string, surface: AgentSurface, add: AddCheck): Promise { + const path = agentSettingsPath(root, surface) + let doc: HookDocument + try { + doc = await readHookDocument(path) + } catch (error) { + add({ level: 'error', target: surface, message: error instanceof Error ? error.message : String(error), path }) + return + } + for (const event of AGENT_EVENTS) { + let groups: HookGroup[] + try { + groups = eventGroups(doc, path, event) + } catch (error) { + add({ level: 'error', target: surface, message: error instanceof Error ? error.message : String(error), path }) + return + } + const installed = groups.flatMap((group) => (group.hooks ?? []).map((handler) => ({ group, handler }))) + .filter(({ handler }) => isCodeTrussHandler(handler)) + if (installed.length !== 1) { + add({ + level: 'error', + target: surface, + message: `${event} must contain exactly one CodeTruss handler (found ${installed.length})`, + path, + }) + continue + } + const expected = agentHandler(surface, event) + const drift = agentHandlerDrift(installed[0].handler, expected) + if (installed[0].group.matcher !== agentEventMatcher(event)) drift.push('matcher') + if (drift.length) { + const detail = `(${drift.join(', ')}); run codetruss hooks install ${surface} to refresh it` + add({ level: 'error', target: surface, message: `${event} handler differs from the current safe installation ${detail}`, path }) + continue + } + add({ level: 'ok', target: surface, message: `${event} handler is current`, path }) + } + try { + const metadata = await lstat(path) + if (SUPPORTS_POSIX_FILE_MODES && (metadata.mode & 0o022) !== 0) { + add({ level: 'error', target: surface, message: 'hook configuration is writable by group or other users', path }) + } + } catch (error) { + if ((error as NodeJS.ErrnoException).code !== 'ENOENT') { + add({ level: 'error', target: surface, message: error instanceof Error ? error.message : String(error), path }) + } + } +} + +async function inspectRunner(root: string, add: AddCheck): Promise { + const path = join(root, '.codetruss', 'hooks', 'agent.cjs') + try { + const [contents, metadata] = await Promise.all([readFile(path, 'utf8'), lstat(path)]) + if (!metadata.isFile()) { + add({ level: 'error', target: 'agent-runtime', message: 'agent hook runner is not a regular file', path }) + } else if (contents !== AGENT_RUNNER) { + add({ level: 'error', target: 'agent-runtime', message: 'agent hook runner differs from this CLI version; reinstall hooks', path }) + } else if (SUPPORTS_POSIX_FILE_MODES && (metadata.mode & 0o022) !== 0) { + add({ level: 'error', target: 'agent-runtime', message: 'agent hook runner is writable by group or other users', path }) + } else { + add({ + level: 'ok', + target: 'agent-runtime', + message: SUPPORTS_POSIX_FILE_MODES + ? 'agent hook runner is current and owner-controlled' + : 'agent hook runner is current; POSIX permission checks do not apply on Windows', + path, + }) + } + } catch (error) { + add({ + level: 'error', + target: 'agent-runtime', + message: (error as NodeJS.ErrnoException).code === 'ENOENT' + ? 'agent hook runner is missing; reinstall hooks' + : error instanceof Error ? error.message : String(error), + path, + }) + } +} + +async function inspectPreCommit(root: string, add: AddCheck): Promise { + const path = effectivePreCommitPath(root) + try { + const [contents, metadata] = await Promise.all([readFile(path, 'utf8'), lstat(path)]) + const beginCount = contents.split(BEGIN_MARKER).length - 1 + const endCount = contents.split(END_MARKER).length - 1 + const begin = contents.indexOf(BEGIN_MARKER) + const end = contents.indexOf(END_MARKER, begin) + END_MARKER.length + if (beginCount !== 1 || endCount !== 1 || begin < 0 || contents.slice(begin, end) !== preCommitBlock()) { + add({ level: 'error', target: 'pre-commit', message: 'installed block is missing, duplicated, or stale; reinstall hooks', path }) + } else { + add({ level: 'ok', target: 'pre-commit', message: 'staged-review block is current', path }) + } + if (!SUPPORTS_POSIX_FILE_MODES) { + add({ level: 'ok', target: 'pre-commit', message: 'hook file is present; POSIX permission checks do not apply on Windows', path }) + } else if ((metadata.mode & 0o100) === 0) { + add({ level: 'error', target: 'pre-commit', message: 'hook is not executable by its owner', path }) + } else if ((metadata.mode & 0o022) !== 0) { + add({ level: 'error', target: 'pre-commit', message: 'hook is writable by group or other users', path }) + } else { + add({ level: 'ok', target: 'pre-commit', message: 'hook permissions are owner-controlled and executable', path }) + } + } catch (error) { + add({ + level: 'error', + target: 'pre-commit', + message: (error as NodeJS.ErrnoException).code === 'ENOENT' + ? 'hook is not installed' + : error instanceof Error ? error.message : String(error), + path, + }) + } +} + +export async function inspectHookDoctor(root: string, target: string): Promise { + const targets = parseTargets(target) + const checks: HookDoctorCheck[] = [] + const add: AddCheck = (check) => checks.push(check) + const agentTargets = targets.filter((name): name is AgentSurface => name !== 'pre-commit') + try { + const config = await loadConfig(root) + if (config.verify.length) { + const trust = await verifyCommandTrustStatus(root, config.verify) + add({ + level: trust.trusted ? 'ok' : 'error', + target: 'config', + message: trust.trusted + ? `repository verification commands are trusted (${trust.hash.slice(0, 12)})` + : `repository verification commands are untrusted (${trust.hash.slice(0, 12)}); inspect them and run codetruss verify-policy trust`, + path: join(root, '.codetruss.yml'), + }) + } + } catch (error) { + add({ level: 'error', target: 'config', message: error instanceof Error ? error.message : String(error), path: join(root, '.codetruss.yml') }) + } + if (agentTargets.length) { + try { + const config = await loadConfig(root) + if (config.allow.length) { + add({ level: 'ok', target: 'config', message: `${config.allow.length} allowed task-scope glob${config.allow.length === 1 ? '' : 's'} configured`, path: join(root, '.codetruss.yml') }) + } else { + add({ level: 'error', target: 'config', message: 'agent hooks require at least one allow glob in .codetruss.yml', path: join(root, '.codetruss.yml') }) + } + } catch (error) { + add({ level: 'error', target: 'config', message: error instanceof Error ? error.message : String(error), path: join(root, '.codetruss.yml') }) + } + await inspectRunner(root, add) + } + const cliPath = await executablePath(root) + if (cliPath) { + add({ level: 'ok', target: 'runtime', message: 'CodeTruss CLI is resolvable by installed hooks', path: cliPath }) + await inspectExecutableShadow(cliPath, add) + } else add({ level: 'error', target: 'runtime', message: 'CodeTruss CLI is not available locally or on PATH' }) + for (const name of targets) { + if (name === 'pre-commit') await inspectPreCommit(root, add) + else { + await inspectAgentHook(root, name, add) + if (name === 'codex') { + add({ + level: 'warning', + target: 'codex', + message: 'hook trust cannot be verified here; open /hooks in Codex and trust this exact project hook. New or changed hook definitions require review again', + path: join(root, '.codex', 'hooks.json'), + }) + } + } + } + const errors = checks.filter((check) => check.level === 'error').length + return { ok: errors === 0, checks } +} + +async function hookInstallations(root: string): Promise> { + const agentPresent = async (surface: AgentSurface): Promise => ( + readFile(agentSettingsPath(root, surface), 'utf8') + .then((text) => text.includes('.codetruss/hooks/agent.cjs'), () => false) + ) + return { + 'pre-commit': await readFile(effectivePreCommitPath(root), 'utf8') + .then((text) => text.includes(BEGIN_MARKER), () => false), + claude: await agentPresent('claude'), + codex: await agentPresent('codex'), + } +} + +/** Privacy-safe health summary: no hook path, command, or diagnostic text leaves this function. */ +export async function inspectLocalHookHealth(root: string): Promise { + const [installed, preCommitDoctor, claudeDoctor, codexDoctor] = await Promise.all([ + hookInstallations(root), + inspectHookDoctor(root, 'pre-commit'), + inspectHookDoctor(root, 'claude'), + inspectHookDoctor(root, 'codex'), + ]) + const doctors: Record = { + 'pre-commit': preCommitDoctor, + claude: claudeDoctor, + codex: codexDoctor, + } + const status = (target: HookTarget): HookHealthStatus => { + if (!installed[target]) return 'not_installed' + const relevant = doctors[target].checks.filter((check) => ( + check.target === target + || check.target === 'runtime' + || check.target === 'agent-runtime' + || check.target === 'config' + )) + if (relevant.some((check) => check.level === 'error')) return 'unhealthy' + if (relevant.some((check) => check.level === 'warning')) return 'warning' + return 'healthy' + } + return { + preCommit: status('pre-commit'), + claude: status('claude'), + codex: status('codex'), + } +} diff --git a/packages/cli/src/hook-executable.ts b/packages/cli/src/hook-executable.ts new file mode 100644 index 0000000..b130548 --- /dev/null +++ b/packages/cli/src/hook-executable.ts @@ -0,0 +1,49 @@ +import { constants as fsConstants } from 'node:fs' +import { access, readFile, realpath } from 'node:fs/promises' +import { delimiter, dirname, join, parse as parsePath } from 'node:path' + +/** The `codetruss` binary the installed hooks will resolve: repository-local first, then PATH. */ +export async function executablePath(root: string): Promise { + const local = join(root, 'node_modules', '.bin', process.platform === 'win32' ? 'codetruss.cmd' : 'codetruss') + if (await access(local, fsConstants.X_OK).then(() => true, () => false)) return local + const extensions = process.platform === 'win32' + ? (process.env.PATHEXT ?? '.EXE;.CMD;.BAT').split(';') + : [''] + for (const directory of (process.env.PATH ?? '').split(delimiter).filter(Boolean)) { + for (const extension of extensions) { + const candidate = join(directory, `codetruss${extension.toLowerCase()}`) + if (await access(candidate, fsConstants.X_OK).then(() => true, () => false)) return candidate + } + } + return undefined +} + +/** + * Version of the @codetruss/cli install that owns `executable`, read from its + * package manifest. Never executes the binary: resolving a shadowing install + * must not run whatever happens to be first on PATH. + */ +export async function installedCliVersion(executable: string): Promise { + let cursor: string + try { + cursor = dirname(await realpath(executable)) + } catch { + return undefined + } + const { root } = parsePath(cursor) + while (true) { + try { + const manifest = JSON.parse(await readFile(join(cursor, 'package.json'), 'utf8')) as { + name?: unknown + version?: unknown + } + if (manifest.name === '@codetruss/cli' && typeof manifest.version === 'string') return manifest.version + } catch { + // no manifest here, or unreadable — keep walking up + } + if (cursor === root) return undefined + const parent = dirname(cursor) + if (parent === cursor) return undefined + cursor = parent + } +} diff --git a/packages/cli/src/hook-pre-commit.ts b/packages/cli/src/hook-pre-commit.ts new file mode 100644 index 0000000..832a85e --- /dev/null +++ b/packages/cli/src/hook-pre-commit.ts @@ -0,0 +1,56 @@ +import { isAbsolute, resolve } from 'node:path' +import { runGitText } from './git-process.js' + +const MARKER = 'codetruss-agent-guard' +export const CODETRUSS_PRE_COMMIT_ENV = 'CODETRUSS_INTERNAL_PRE_COMMIT' +export const BEGIN_MARKER = `# ${MARKER}:begin` +export const END_MARKER = `# ${MARKER}:end` + +export function effectivePreCommitPath(root: string): string { + const raw = runGitText(root, ['rev-parse', '--git-path', 'hooks/pre-commit']).trim() + if (!raw) throw new Error('Git did not return an effective pre-commit hook path') + return isAbsolute(raw) ? resolve(raw) : resolve(root, raw) +} + +export function stripCodeTrussPreCommit(existing: string): string { + const begin = existing.indexOf(BEGIN_MARKER) + if (begin >= 0) { + const lineStart = existing.lastIndexOf('\n', begin - 1) + 1 + const end = existing.indexOf(END_MARKER, begin) + if (end < 0) throw new Error('existing CodeTruss pre-commit block is missing its end marker') + const lineEnd = existing.indexOf('\n', end) + return `${existing.slice(0, lineStart)}${lineEnd < 0 ? '' : existing.slice(lineEnd + 1)}`.replace(/\n{3,}$/g, '\n') + } + const legacy = existing.indexOf(`# ${MARKER}`) + if (legacy >= 0) { + const lineStart = existing.lastIndexOf('\n', legacy - 1) + 1 + return existing.slice(0, lineStart).replace(/\n{3,}$/g, '\n') + } + return existing +} + +export function preCommitBlock(): string { + return `${BEGIN_MARKER} +ROOT="$(git -c core.longpaths=true rev-parse --show-toplevel 2>/dev/null)" || exit 0 +CODETRUSS_STATUS=0 +if [ -x "$ROOT/node_modules/.bin/codetruss" ]; then + ${CODETRUSS_PRE_COMMIT_ENV}=1 "$ROOT/node_modules/.bin/codetruss" review --staged --task "pre-commit" || CODETRUSS_STATUS=$? +else + ${CODETRUSS_PRE_COMMIT_ENV}=1 codetruss review --staged --task "pre-commit" || CODETRUSS_STATUS=$? +fi +case "$CODETRUSS_STATUS" in + 0) ;; + 1) + printf '%s\n' 'CodeTruss REVIEW_REQUIRED: receipt created; commit allowed for human review.' >&2 + ;; + 2) + printf '%s\n' 'CodeTruss FAILED: commit blocked. Review the receipt before retrying.' >&2 + exit 2 + ;; + *) + printf '%s\n' "CodeTruss could not produce a trustworthy receipt (exit $CODETRUSS_STATUS); commit blocked." >&2 + exit "$CODETRUSS_STATUS" + ;; +esac +${END_MARKER}` +} diff --git a/packages/cli/src/hook-targets.ts b/packages/cli/src/hook-targets.ts new file mode 100644 index 0000000..e8a0b59 --- /dev/null +++ b/packages/cli/src/hook-targets.ts @@ -0,0 +1,11 @@ +/** The hook surfaces a user can install, inspect, or remove. */ +export type HookTarget = 'pre-commit' | 'claude' | 'codex' + +/** The two agent surfaces; `pre-commit` is a shell hook and handled separately. */ +export type AgentSurface = Exclude + +export function parseTargets(target: string): HookTarget[] { + const valid = new Set(['pre-commit', 'claude', 'codex', 'all']) + if (!valid.has(target)) throw new Error(`unknown hook target ${target}; expected pre-commit, claude, codex, or all`) + return target === 'all' ? ['pre-commit', 'claude', 'codex'] : [target as HookTarget] +} diff --git a/packages/cli/src/hook-writes.ts b/packages/cli/src/hook-writes.ts new file mode 100644 index 0000000..d4b5404 --- /dev/null +++ b/packages/cli/src/hook-writes.ts @@ -0,0 +1,143 @@ +import { randomUUID } from 'node:crypto' +import { chmod, lstat, mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises' +import { basename, dirname, join, resolve } from 'node:path' + +export interface PlannedWrite { + path: string + contents: Buffer + defaultMode: number + forceMode?: number +} + +interface FileSnapshot { + path: string + exists: boolean + contents?: Buffer + mode?: number +} + +export interface HookInstallPlan { + writes: PlannedWrite[] + installedPaths: string[] +} + +export function plannedWrite(path: string, contents: string | Buffer, defaultMode: number, forceMode?: number): PlannedWrite { + return { + path: resolve(path), + contents: Buffer.isBuffer(contents) ? Buffer.from(contents) : Buffer.from(contents, 'utf8'), + defaultMode, + ...(forceMode === undefined ? {} : { forceMode }), + } +} + +async function snapshotFile(path: string): Promise { + try { + const metadata = await lstat(path) + if (!metadata.isFile()) { + throw new Error(`refusing to replace non-regular hook file ${path}`) + } + return { + path, + exists: true, + contents: await readFile(path), + mode: metadata.mode & 0o777, + } + } catch (error) { + if ((error as NodeJS.ErrnoException).code === 'ENOENT') return { path, exists: false } + throw error + } +} + +async function snapshotStillMatches(snapshot: FileSnapshot): Promise { + try { + const metadata = await lstat(snapshot.path) + if (!snapshot.exists || !metadata.isFile()) return false + const contents = await readFile(snapshot.path) + return contents.equals(snapshot.contents!) && (metadata.mode & 0o777) === snapshot.mode + } catch (error) { + if ((error as NodeJS.ErrnoException).code === 'ENOENT') return !snapshot.exists + throw error + } +} + +async function writeTemporaryFile(write: PlannedWrite, mode: number): Promise { + await mkdir(dirname(write.path), { recursive: true }) + const temporary = join(dirname(write.path), `.${basename(write.path)}.codetruss-${process.pid}-${randomUUID()}.tmp`) + try { + await writeFile(temporary, write.contents, { flag: 'wx', mode }) + await chmod(temporary, mode) + return temporary + } catch (error) { + await rm(temporary, { force: true }).catch(() => undefined) + throw error + } +} + +export function mergePlannedWrites(plans: HookInstallPlan[]): { writes: PlannedWrite[]; installedPaths: string[] } { + const writes = new Map() + const installedPaths = new Set() + for (const plan of plans) { + for (const path of plan.installedPaths) installedPaths.add(path) + for (const write of plan.writes) { + const existing = writes.get(write.path) + if (existing && (!existing.contents.equals(write.contents) + || existing.defaultMode !== write.defaultMode || existing.forceMode !== write.forceMode)) { + throw new Error(`hook installation planned conflicting writes to ${write.path}`) + } + writes.set(write.path, write) + } + } + return { writes: [...writes.values()], installedPaths: [...installedPaths] } +} + +/** + * Stage every replacement in its destination directory before publishing any + * of them. If publication fails, restore the exact bytes and mode captured at + * the start of the transaction. A concurrent editor is detected before the + * first rename so CodeTruss never knowingly overwrites a newer hook config. + */ +export async function commitPlannedWrites(writes: PlannedWrite[]): Promise { + const snapshots = new Map() + const temporaryFiles = new Map() + const committed: PlannedWrite[] = [] + for (const write of writes) snapshots.set(write.path, await snapshotFile(write.path)) + try { + for (const write of writes) { + const snapshot = snapshots.get(write.path)! + const mode = write.forceMode ?? snapshot.mode ?? write.defaultMode + temporaryFiles.set(write.path, await writeTemporaryFile(write, mode)) + } + for (const snapshot of snapshots.values()) { + if (!await snapshotStillMatches(snapshot)) { + throw new Error(`hook file changed during installation and was left untouched: ${snapshot.path}`) + } + } + for (const write of writes) { + await rename(temporaryFiles.get(write.path)!, write.path) + temporaryFiles.delete(write.path) + committed.push(write) + } + } catch (error) { + const rollbackErrors: string[] = [] + for (const write of committed.reverse()) { + const snapshot = snapshots.get(write.path)! + try { + if (!snapshot.exists) { + await rm(write.path, { force: true }) + } else { + const restore = plannedWrite(write.path, snapshot.contents!, snapshot.mode!, snapshot.mode!) + const temporary = await writeTemporaryFile(restore, snapshot.mode!) + await rename(temporary, write.path) + } + } catch (rollbackError) { + rollbackErrors.push(`${write.path}: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`) + } + } + if (rollbackErrors.length) { + throw new Error(`${error instanceof Error ? error.message : String(error)}; hook rollback also failed: ${rollbackErrors.join('; ')}`) + } + throw error + } finally { + await Promise.all([...temporaryFiles.values()].map((path) => rm(path, { force: true }).catch(() => undefined))) + } +} diff --git a/packages/cli/src/hooks.ts b/packages/cli/src/hooks.ts index 2575eb9..3c424de 100644 --- a/packages/cli/src/hooks.ts +++ b/packages/cli/src/hooks.ts @@ -1,350 +1,49 @@ -import { randomUUID } from 'node:crypto' -import { constants as fsConstants } from 'node:fs' -import { access, chmod, lstat, mkdir, readFile, realpath, rename, rm, writeFile } from 'node:fs/promises' -import { basename, delimiter, dirname, isAbsolute, join, parse as parsePath, resolve } from 'node:path' +import { readFile, rm, writeFile } from 'node:fs/promises' +import { join } from 'node:path' import { loadConfig } from './config.js' -import { runGitText } from './git-process.js' -import { CLI_VERSION } from './version.js' +import { + AGENT_EVENTS, + agentEventMatcher, + agentHandler, + agentSettingsPath, + eventGroups, + isCodeTrussHandler, + readHookDocument, + removeCodeTrussHandlers, +} from './hook-agent-config.js' +import { AGENT_RUNNER } from './hook-agent-runner.js' +import { inspectHookDoctor, type HookDoctorResult } from './hook-doctor.js' +import { executablePath } from './hook-executable.js' +import { + BEGIN_MARKER, + effectivePreCommitPath, + preCommitBlock, + stripCodeTrussPreCommit, +} from './hook-pre-commit.js' +import { parseTargets, type AgentSurface, type HookTarget } from './hook-targets.js' +import { commitPlannedWrites, mergePlannedWrites, plannedWrite, type HookInstallPlan } from './hook-writes.js' import { verifyCommandTrustStatus } from './verify-trust.js' -type HookHandler = { type?: string; command?: string; args?: string[]; [key: string]: unknown } -type HookGroup = { matcher?: string; hooks?: HookHandler[]; [key: string]: unknown } -type HookDocument = { hooks?: Record; [key: string]: unknown } -type HookTarget = 'pre-commit' | 'claude' | 'codex' - -export interface HookDoctorCheck { - level: 'ok' | 'warning' | 'error' - target: HookTarget | 'config' | 'runtime' | 'agent-runtime' - message: string - path?: string -} - -export interface HookDoctorResult { - ok: boolean - checks: HookDoctorCheck[] -} - -export type HookHealthStatus = 'not_installed' | 'healthy' | 'warning' | 'unhealthy' - -export interface LocalHookHealth { - preCommit: HookHealthStatus - claude: HookHealthStatus - codex: HookHealthStatus -} - -interface PlannedWrite { - path: string - contents: Buffer - defaultMode: number - forceMode?: number -} - -interface FileSnapshot { - path: string - exists: boolean - contents?: Buffer - mode?: number -} - -interface HookInstallPlan { - writes: PlannedWrite[] - installedPaths: string[] -} - -const MARKER = 'codetruss-agent-guard' -export const CODETRUSS_PRE_COMMIT_ENV = 'CODETRUSS_INTERNAL_PRE_COMMIT' -const SUPPORTS_POSIX_FILE_MODES = process.platform !== 'win32' -const BEGIN_MARKER = `# ${MARKER}:begin` -const END_MARKER = `# ${MARKER}:end` -const AGENT_EVENTS = ['UserPromptSubmit', 'PostToolUse', 'Stop'] as const -// The internal Stop review has a five-minute hard deadline. Keep the installed -// agent envelope wider so it can persist a failure result and clean private Git -// evidence before the host terminates the hook process. -const STOP_HOOK_TIMEOUT_SECONDS = 6 * 60 -const AGENT_RUNNER = `'use strict' -const { existsSync } = require('node:fs') -const { execFileSync, spawnSync } = require('node:child_process') -const { join } = require('node:path') - -const surface = process.argv[2] -const maxInputBytes = 16 * 1024 * 1024 -if (surface !== 'claude' && surface !== 'codex') { - process.stderr.write('codetruss hook: expected claude or codex\\n') - process.exit(3) -} - -function safeFailure(input, message) { - let event - let stopHookActive = false - const textInput = input.toString('utf8') - try { - const parsed = JSON.parse(textInput) - event = parsed.hook_event_name - stopHookActive = parsed.stop_hook_active === true - } catch { - const prefix = textInput.slice(0, 64 * 1024) - event = /"hook_event_name"\\s*:\\s*"([^"]+)"/.exec(prefix)?.[1] - stopHookActive = /"stop_hook_active"\\s*:\\s*true/.test(prefix) - } - const text = ('CodeTruss hook failed safely: ' + message).slice(0, 9000) - if (event === 'UserPromptSubmit' || (event === 'Stop' && !stopHookActive)) { - return { decision: 'block', reason: text } - } - return { systemMessage: text } -} - -const chunks = [] -let inputBytes = 0 -let tooLarge = false -process.stdin.on('data', (value) => { - const chunk = Buffer.from(value) - inputBytes += chunk.length - if (inputBytes > maxInputBytes) tooLarge = true - else chunks.push(chunk) -}) -process.stdin.on('end', () => { - const input = Buffer.concat(chunks) - if (tooLarge) { - process.stdout.write(JSON.stringify(safeFailure(input, 'hook input exceeded 16 MiB')) + '\\n') - process.exit(0) - } - let root - try { - root = execFileSync('git', ['-c', 'core.longpaths=true', 'rev-parse', '--show-toplevel'], { encoding: 'utf8' }).trim() - } catch { - process.stdout.write(JSON.stringify(safeFailure(input, 'could not resolve the Git repository root')) + '\\n') - process.exit(0) - } - const local = join(root, 'node_modules', '.bin', process.platform === 'win32' ? 'codetruss.cmd' : 'codetruss') - const command = existsSync(local) ? local : 'codetruss' - const result = spawnSync(command, ['hooks', 'dispatch', surface], { - cwd: root, - input, - encoding: 'utf8', - shell: process.platform === 'win32', - maxBuffer: 64 * 1024, - }) - if (result.error || result.status !== 0) { - const detail = result.error ? result.error.message : (result.stderr || 'dispatch exited with status ' + String(result.status)).trim() - process.stdout.write(JSON.stringify(safeFailure(input, detail)) + '\\n') - process.exit(0) - } - if (result.stderr) process.stderr.write(result.stderr) - if (result.stdout) process.stdout.write(result.stdout) - process.exit(0) -}) -` - -function plannedWrite(path: string, contents: string | Buffer, defaultMode: number, forceMode?: number): PlannedWrite { - return { - path: resolve(path), - contents: Buffer.isBuffer(contents) ? Buffer.from(contents) : Buffer.from(contents, 'utf8'), - defaultMode, - ...(forceMode === undefined ? {} : { forceMode }), - } -} - -async function snapshotFile(path: string): Promise { - try { - const metadata = await lstat(path) - if (!metadata.isFile()) { - throw new Error(`refusing to replace non-regular hook file ${path}`) - } - return { - path, - exists: true, - contents: await readFile(path), - mode: metadata.mode & 0o777, - } - } catch (error) { - if ((error as NodeJS.ErrnoException).code === 'ENOENT') return { path, exists: false } - throw error - } -} - -async function snapshotStillMatches(snapshot: FileSnapshot): Promise { - try { - const metadata = await lstat(snapshot.path) - if (!snapshot.exists || !metadata.isFile()) return false - const contents = await readFile(snapshot.path) - return contents.equals(snapshot.contents!) && (metadata.mode & 0o777) === snapshot.mode - } catch (error) { - if ((error as NodeJS.ErrnoException).code === 'ENOENT') return !snapshot.exists - throw error - } -} - -async function writeTemporaryFile(write: PlannedWrite, mode: number): Promise { - await mkdir(dirname(write.path), { recursive: true }) - const temporary = join(dirname(write.path), `.${basename(write.path)}.codetruss-${process.pid}-${randomUUID()}.tmp`) - try { - await writeFile(temporary, write.contents, { flag: 'wx', mode }) - await chmod(temporary, mode) - return temporary - } catch (error) { - await rm(temporary, { force: true }).catch(() => undefined) - throw error - } -} - -function mergePlannedWrites(plans: HookInstallPlan[]): { writes: PlannedWrite[]; installedPaths: string[] } { - const writes = new Map() - const installedPaths = new Set() - for (const plan of plans) { - for (const path of plan.installedPaths) installedPaths.add(path) - for (const write of plan.writes) { - const existing = writes.get(write.path) - if (existing && (!existing.contents.equals(write.contents) - || existing.defaultMode !== write.defaultMode || existing.forceMode !== write.forceMode)) { - throw new Error(`hook installation planned conflicting writes to ${write.path}`) - } - writes.set(write.path, write) - } - } - return { writes: [...writes.values()], installedPaths: [...installedPaths] } -} - -/** - * Stage every replacement in its destination directory before publishing any - * of them. If publication fails, restore the exact bytes and mode captured at - * the start of the transaction. A concurrent editor is detected before the - * first rename so CodeTruss never knowingly overwrites a newer hook config. - */ -async function commitPlannedWrites(writes: PlannedWrite[]): Promise { - const snapshots = new Map() - const temporaryFiles = new Map() - const committed: PlannedWrite[] = [] - for (const write of writes) snapshots.set(write.path, await snapshotFile(write.path)) - try { - for (const write of writes) { - const snapshot = snapshots.get(write.path)! - const mode = write.forceMode ?? snapshot.mode ?? write.defaultMode - temporaryFiles.set(write.path, await writeTemporaryFile(write, mode)) - } - for (const snapshot of snapshots.values()) { - if (!await snapshotStillMatches(snapshot)) { - throw new Error(`hook file changed during installation and was left untouched: ${snapshot.path}`) - } - } - for (const write of writes) { - await rename(temporaryFiles.get(write.path)!, write.path) - temporaryFiles.delete(write.path) - committed.push(write) - } - } catch (error) { - const rollbackErrors: string[] = [] - for (const write of committed.reverse()) { - const snapshot = snapshots.get(write.path)! - try { - if (!snapshot.exists) { - await rm(write.path, { force: true }) - } else { - const restore = plannedWrite(write.path, snapshot.contents!, snapshot.mode!, snapshot.mode!) - const temporary = await writeTemporaryFile(restore, snapshot.mode!) - await rename(temporary, write.path) - } - } catch (rollbackError) { - rollbackErrors.push(`${write.path}: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`) - } - } - if (rollbackErrors.length) { - throw new Error(`${error instanceof Error ? error.message : String(error)}; hook rollback also failed: ${rollbackErrors.join('; ')}`) - } - throw error - } finally { - await Promise.all([...temporaryFiles.values()].map((path) => rm(path, { force: true }).catch(() => undefined))) - } -} - -function isRecord(value: unknown): value is Record { - return Boolean(value) && typeof value === 'object' && !Array.isArray(value) -} - -async function readHookDocument(path: string): Promise { - let text: string - try { - text = await readFile(path, 'utf8') - } catch (error) { - if ((error as NodeJS.ErrnoException).code === 'ENOENT') return {} - throw error - } - let parsed: unknown - try { - parsed = JSON.parse(text) - } catch (error) { - throw new Error(`refusing to overwrite invalid JSON in ${path}: ${error instanceof Error ? error.message : String(error)}`) - } - if (!isRecord(parsed)) throw new Error(`refusing to overwrite ${path}: top-level JSON must be an object`) - if (parsed.hooks !== undefined && !isRecord(parsed.hooks)) { - throw new Error(`refusing to overwrite ${path}: hooks must be an object`) - } - return parsed as HookDocument -} - -function eventGroups(doc: HookDocument, path: string, event: string): HookGroup[] { - doc.hooks ??= {} - const value = doc.hooks[event] - if (value === undefined) { - const groups: HookGroup[] = [] - doc.hooks[event] = groups - return groups - } - if (!Array.isArray(value) || value.some((group) => !isRecord(group))) { - throw new Error(`refusing to overwrite ${path}: hooks.${event} must be an array of objects`) - } - for (const group of value as HookGroup[]) { - if (group.hooks !== undefined && (!Array.isArray(group.hooks) || group.hooks.some((handler) => !isRecord(handler)))) { - throw new Error(`refusing to overwrite ${path}: hooks.${event}[].hooks must be an array of objects`) - } - } - return value as HookGroup[] -} - -function isCodeTrussHandler(handler: HookHandler): boolean { - return [handler.command, ...(handler.args ?? [])].some((value) => typeof value === 'string' && value.includes('.codetruss/hooks/agent.cjs')) -} - -function removeCodeTrussHandlers(groups: HookGroup[]): void { - for (let index = groups.length - 1; index >= 0; index--) { - const group = groups[index] - if (!Array.isArray(group.hooks)) continue - group.hooks = group.hooks.filter((handler) => !isCodeTrussHandler(handler)) - if (group.hooks.length === 0) groups.splice(index, 1) - } -} - -function agentCommand(surface: 'claude' | 'codex'): HookHandler { - if (surface === 'claude') { - return { - command: 'node', - args: ['${CLAUDE_PROJECT_DIR}/.codetruss/hooks/agent.cjs', 'claude'], - } - } - return { - command: 'node "$(git -c core.longpaths=true rev-parse --show-toplevel)/.codetruss/hooks/agent.cjs" codex', - commandWindows: "$root = git -c core.longpaths=true rev-parse --show-toplevel; if ($LASTEXITCODE -eq 0) { node (Join-Path $root '.codetruss/hooks/agent.cjs') codex }", - } -} - -function agentHandler(surface: 'claude' | 'codex', event: typeof AGENT_EVENTS[number]): HookHandler { - const timeout = event === 'PostToolUse' ? 10 : event === 'UserPromptSubmit' ? 60 : STOP_HOOK_TIMEOUT_SECONDS - const statusMessage = event === 'PostToolUse' - ? 'Checking scope with CodeTruss' - : event === 'UserPromptSubmit' - ? 'Capturing CodeTruss turn baseline' - : 'Writing CodeTruss review receipt' - return { type: 'command', ...agentCommand(surface), timeout, statusMessage } -} - -async function planAgentHook(root: string, surface: 'claude' | 'codex'): Promise { - const dir = join(root, surface === 'claude' ? '.claude' : '.codex') - const path = join(dir, surface === 'claude' ? 'settings.json' : 'hooks.json') +export { CODETRUSS_PRE_COMMIT_ENV } from './hook-pre-commit.js' +export { installedCliVersion } from './hook-executable.js' +export { + inspectHookDoctor, + inspectLocalHookHealth, + type HookDoctorCheck, + type HookDoctorResult, + type HookHealthStatus, + type LocalHookHealth, +} from './hook-doctor.js' + +async function planAgentHook(root: string, surface: AgentSurface): Promise { + const path = agentSettingsPath(root, surface) const doc = await readHookDocument(path) for (const event of AGENT_EVENTS) { const groups = eventGroups(doc, path, event) removeCodeTrussHandlers(groups) + const matcher = agentEventMatcher(event) groups.push({ - ...(event === 'PostToolUse' ? { matcher: 'Edit|Write' } : {}), + ...(matcher === undefined ? {} : { matcher }), hooks: [agentHandler(surface, event)], }) } @@ -358,55 +57,6 @@ async function planAgentHook(root: string, surface: 'claude' | 'codex'): Promise } } -function effectivePreCommitPath(root: string): string { - const raw = runGitText(root, ['rev-parse', '--git-path', 'hooks/pre-commit']).trim() - if (!raw) throw new Error('Git did not return an effective pre-commit hook path') - return isAbsolute(raw) ? resolve(raw) : resolve(root, raw) -} - -function stripCodeTrussPreCommit(existing: string): string { - const begin = existing.indexOf(BEGIN_MARKER) - if (begin >= 0) { - const lineStart = existing.lastIndexOf('\n', begin - 1) + 1 - const end = existing.indexOf(END_MARKER, begin) - if (end < 0) throw new Error('existing CodeTruss pre-commit block is missing its end marker') - const lineEnd = existing.indexOf('\n', end) - return `${existing.slice(0, lineStart)}${lineEnd < 0 ? '' : existing.slice(lineEnd + 1)}`.replace(/\n{3,}$/g, '\n') - } - const legacy = existing.indexOf(`# ${MARKER}`) - if (legacy >= 0) { - const lineStart = existing.lastIndexOf('\n', legacy - 1) + 1 - return existing.slice(0, lineStart).replace(/\n{3,}$/g, '\n') - } - return existing -} - -function preCommitBlock(): string { - return `${BEGIN_MARKER} -ROOT="$(git -c core.longpaths=true rev-parse --show-toplevel 2>/dev/null)" || exit 0 -CODETRUSS_STATUS=0 -if [ -x "$ROOT/node_modules/.bin/codetruss" ]; then - ${CODETRUSS_PRE_COMMIT_ENV}=1 "$ROOT/node_modules/.bin/codetruss" review --staged --task "pre-commit" || CODETRUSS_STATUS=$? -else - ${CODETRUSS_PRE_COMMIT_ENV}=1 codetruss review --staged --task "pre-commit" || CODETRUSS_STATUS=$? -fi -case "$CODETRUSS_STATUS" in - 0) ;; - 1) - printf '%s\n' 'CodeTruss REVIEW_REQUIRED: receipt created; commit allowed for human review.' >&2 - ;; - 2) - printf '%s\n' 'CodeTruss FAILED: commit blocked. Review the receipt before retrying.' >&2 - exit 2 - ;; - *) - printf '%s\n' "CodeTruss could not produce a trustworthy receipt (exit $CODETRUSS_STATUS); commit blocked." >&2 - exit "$CODETRUSS_STATUS" - ;; -esac -${END_MARKER}` -} - async function planPreCommit(root: string): Promise { const path = effectivePreCommitPath(root) let existing = '' @@ -434,12 +84,6 @@ async function planPreCommit(root: string): Promise { } } -function parseTargets(target: string): HookTarget[] { - const valid = new Set(['pre-commit', 'claude', 'codex', 'all']) - if (!valid.has(target)) throw new Error(`unknown hook target ${target}; expected pre-commit, claude, codex, or all`) - return target === 'all' ? ['pre-commit', 'claude', 'codex'] : [target as HookTarget] -} - async function assertHookPolicyReady(root: string, targets: HookTarget[]): Promise { const config = await loadConfig(root) if (targets.some((target) => target === 'claude' || target === 'codex') && config.allow.length === 0) { @@ -470,9 +114,8 @@ export async function installHooks(root: string, target: string): Promise for (const path of plan.installedPaths) process.stdout.write(`installed ${path}\n`) } -async function uninstallAgentHook(root: string, surface: 'claude' | 'codex'): Promise { - const dir = join(root, surface === 'claude' ? '.claude' : '.codex') - const path = join(dir, surface === 'claude' ? 'settings.json' : 'hooks.json') +async function uninstallAgentHook(root: string, surface: AgentSurface): Promise { + const path = agentSettingsPath(root, surface) const doc = await readHookDocument(path) let changed = false for (const event of AGENT_EVENTS) { @@ -515,308 +158,12 @@ export async function uninstallHooks(root: string, target: string): Promise { - const path = join(root, surface === 'claude' ? '.claude/settings.json' : '.codex/hooks.json') +async function agentInstalled(root: string, surface: AgentSurface): Promise { + const path = agentSettingsPath(root, surface) const doc = await readHookDocument(path) return AGENT_EVENTS.every((event) => eventGroups(doc, path, event).some((group) => group.hooks?.some(isCodeTrussHandler))) } -async function executablePath(root: string): Promise { - const local = join(root, 'node_modules', '.bin', process.platform === 'win32' ? 'codetruss.cmd' : 'codetruss') - if (await access(local, fsConstants.X_OK).then(() => true, () => false)) return local - const extensions = process.platform === 'win32' - ? (process.env.PATHEXT ?? '.EXE;.CMD;.BAT').split(';') - : [''] - for (const directory of (process.env.PATH ?? '').split(delimiter).filter(Boolean)) { - for (const extension of extensions) { - const candidate = join(directory, `codetruss${extension.toLowerCase()}`) - if (await access(candidate, fsConstants.X_OK).then(() => true, () => false)) return candidate - } - } - return undefined -} - -/** - * Version of the @codetruss/cli install that owns `executable`, read from its - * package manifest. Never executes the binary: resolving a shadowing install - * must not run whatever happens to be first on PATH. - */ -export async function installedCliVersion(executable: string): Promise { - let cursor: string - try { - cursor = dirname(await realpath(executable)) - } catch { - return undefined - } - const { root } = parsePath(cursor) - while (true) { - try { - const manifest = JSON.parse(await readFile(join(cursor, 'package.json'), 'utf8')) as { - name?: unknown - version?: unknown - } - if (manifest.name === '@codetruss/cli' && typeof manifest.version === 'string') return manifest.version - } catch { - // no manifest here, or unreadable — keep walking up - } - if (cursor === root) return undefined - const parent = dirname(cursor) - if (parent === cursor) return undefined - cursor = parent - } -} - -/** - * The hooks invoke `codetruss` by name, so they run whatever PATH resolves — - * which is not always what was just installed. A stale binary earlier in PATH - * silently shadows the new one, and the installer's own "Ready" message used to - * hide it. Reported as a warning: the hooks still work, they just are not this - * version. Determined by manifest version, so a repository-local install of the - * same version is not mistaken for a shadow. - */ -async function inspectExecutableShadow( - executable: string, - add: (check: HookDoctorCheck) => void, -): Promise { - const resolved = await installedCliVersion(executable) - if (!resolved || resolved === CLI_VERSION) return - add({ - level: 'warning', - target: 'runtime', - message: `installed hooks resolve codetruss ${resolved}, but this CLI is ${CLI_VERSION}; put the intended install first on PATH or remove the older one`, - path: executable, - }) -} - -function exactAgentHandler(handler: HookHandler, expected: HookHandler): boolean { - return handler.type === expected.type - && handler.command === expected.command - && JSON.stringify(handler.args) === JSON.stringify(expected.args) - && handler.commandWindows === expected.commandWindows - && handler.timeout === expected.timeout - && handler.statusMessage === expected.statusMessage -} - -async function inspectAgentHook( - root: string, - surface: 'claude' | 'codex', - add: (check: HookDoctorCheck) => void, -): Promise { - const path = join(root, surface === 'claude' ? '.claude/settings.json' : '.codex/hooks.json') - let doc: HookDocument - try { - doc = await readHookDocument(path) - } catch (error) { - add({ level: 'error', target: surface, message: error instanceof Error ? error.message : String(error), path }) - return - } - for (const event of AGENT_EVENTS) { - let groups: HookGroup[] - try { - groups = eventGroups(doc, path, event) - } catch (error) { - add({ level: 'error', target: surface, message: error instanceof Error ? error.message : String(error), path }) - return - } - const installed = groups.flatMap((group) => (group.hooks ?? []).map((handler) => ({ group, handler }))) - .filter(({ handler }) => isCodeTrussHandler(handler)) - if (installed.length !== 1) { - add({ - level: 'error', - target: surface, - message: `${event} must contain exactly one CodeTruss handler (found ${installed.length})`, - path, - }) - continue - } - const expected = agentHandler(surface, event) - const expectedMatcher = event === 'PostToolUse' ? 'Edit|Write' : undefined - if (!exactAgentHandler(installed[0].handler, expected) || installed[0].group.matcher !== expectedMatcher) { - add({ level: 'error', target: surface, message: `${event} handler differs from the current safe installation`, path }) - continue - } - add({ level: 'ok', target: surface, message: `${event} handler is current`, path }) - } - try { - const metadata = await lstat(path) - if (SUPPORTS_POSIX_FILE_MODES && (metadata.mode & 0o022) !== 0) { - add({ level: 'error', target: surface, message: 'hook configuration is writable by group or other users', path }) - } - } catch (error) { - if ((error as NodeJS.ErrnoException).code !== 'ENOENT') { - add({ level: 'error', target: surface, message: error instanceof Error ? error.message : String(error), path }) - } - } -} - -async function inspectRunner(root: string, add: (check: HookDoctorCheck) => void): Promise { - const path = join(root, '.codetruss', 'hooks', 'agent.cjs') - try { - const [contents, metadata] = await Promise.all([readFile(path, 'utf8'), lstat(path)]) - if (!metadata.isFile()) { - add({ level: 'error', target: 'agent-runtime', message: 'agent hook runner is not a regular file', path }) - } else if (contents !== AGENT_RUNNER) { - add({ level: 'error', target: 'agent-runtime', message: 'agent hook runner differs from this CLI version; reinstall hooks', path }) - } else if (SUPPORTS_POSIX_FILE_MODES && (metadata.mode & 0o022) !== 0) { - add({ level: 'error', target: 'agent-runtime', message: 'agent hook runner is writable by group or other users', path }) - } else { - add({ - level: 'ok', - target: 'agent-runtime', - message: SUPPORTS_POSIX_FILE_MODES - ? 'agent hook runner is current and owner-controlled' - : 'agent hook runner is current; POSIX permission checks do not apply on Windows', - path, - }) - } - } catch (error) { - add({ - level: 'error', - target: 'agent-runtime', - message: (error as NodeJS.ErrnoException).code === 'ENOENT' - ? 'agent hook runner is missing; reinstall hooks' - : error instanceof Error ? error.message : String(error), - path, - }) - } -} - -async function inspectPreCommit(root: string, add: (check: HookDoctorCheck) => void): Promise { - const path = effectivePreCommitPath(root) - try { - const [contents, metadata] = await Promise.all([readFile(path, 'utf8'), lstat(path)]) - const beginCount = contents.split(BEGIN_MARKER).length - 1 - const endCount = contents.split(END_MARKER).length - 1 - const begin = contents.indexOf(BEGIN_MARKER) - const end = contents.indexOf(END_MARKER, begin) + END_MARKER.length - if (beginCount !== 1 || endCount !== 1 || begin < 0 || contents.slice(begin, end) !== preCommitBlock()) { - add({ level: 'error', target: 'pre-commit', message: 'installed block is missing, duplicated, or stale; reinstall hooks', path }) - } else { - add({ level: 'ok', target: 'pre-commit', message: 'staged-review block is current', path }) - } - if (!SUPPORTS_POSIX_FILE_MODES) { - add({ level: 'ok', target: 'pre-commit', message: 'hook file is present; POSIX permission checks do not apply on Windows', path }) - } else if ((metadata.mode & 0o100) === 0) { - add({ level: 'error', target: 'pre-commit', message: 'hook is not executable by its owner', path }) - } else if ((metadata.mode & 0o022) !== 0) { - add({ level: 'error', target: 'pre-commit', message: 'hook is writable by group or other users', path }) - } else { - add({ level: 'ok', target: 'pre-commit', message: 'hook permissions are owner-controlled and executable', path }) - } - } catch (error) { - add({ - level: 'error', - target: 'pre-commit', - message: (error as NodeJS.ErrnoException).code === 'ENOENT' - ? 'hook is not installed' - : error instanceof Error ? error.message : String(error), - path, - }) - } -} - -export async function inspectHookDoctor(root: string, target: string): Promise { - const targets = parseTargets(target) - const checks: HookDoctorCheck[] = [] - const add = (check: HookDoctorCheck) => checks.push(check) - const agentTargets = targets.filter((name): name is 'claude' | 'codex' => name !== 'pre-commit') - try { - const config = await loadConfig(root) - if (config.verify.length) { - const trust = await verifyCommandTrustStatus(root, config.verify) - add({ - level: trust.trusted ? 'ok' : 'error', - target: 'config', - message: trust.trusted - ? `repository verification commands are trusted (${trust.hash.slice(0, 12)})` - : `repository verification commands are untrusted (${trust.hash.slice(0, 12)}); inspect them and run codetruss verify-policy trust`, - path: join(root, '.codetruss.yml'), - }) - } - } catch (error) { - add({ level: 'error', target: 'config', message: error instanceof Error ? error.message : String(error), path: join(root, '.codetruss.yml') }) - } - if (agentTargets.length) { - try { - const config = await loadConfig(root) - if (config.allow.length) { - add({ level: 'ok', target: 'config', message: `${config.allow.length} allowed task-scope glob${config.allow.length === 1 ? '' : 's'} configured`, path: join(root, '.codetruss.yml') }) - } else { - add({ level: 'error', target: 'config', message: 'agent hooks require at least one allow glob in .codetruss.yml', path: join(root, '.codetruss.yml') }) - } - } catch (error) { - add({ level: 'error', target: 'config', message: error instanceof Error ? error.message : String(error), path: join(root, '.codetruss.yml') }) - } - await inspectRunner(root, add) - } - const cliPath = await executablePath(root) - if (cliPath) { - add({ level: 'ok', target: 'runtime', message: 'CodeTruss CLI is resolvable by installed hooks', path: cliPath }) - await inspectExecutableShadow(cliPath, add) - } else add({ level: 'error', target: 'runtime', message: 'CodeTruss CLI is not available locally or on PATH' }) - for (const name of targets) { - if (name === 'pre-commit') await inspectPreCommit(root, add) - else { - await inspectAgentHook(root, name, add) - if (name === 'codex') { - add({ - level: 'warning', - target: 'codex', - message: 'hook trust cannot be verified here; open /hooks in Codex and trust this exact project hook. New or changed hook definitions require review again', - path: join(root, '.codex', 'hooks.json'), - }) - } - } - } - const errors = checks.filter((check) => check.level === 'error').length - return { ok: errors === 0, checks } -} - -async function hookInstallations(root: string): Promise> { - const agentPresent = async (surface: 'claude' | 'codex'): Promise => { - const path = join(root, surface === 'claude' ? '.claude/settings.json' : '.codex/hooks.json') - return readFile(path, 'utf8').then((text) => text.includes('.codetruss/hooks/agent.cjs'), () => false) - } - return { - 'pre-commit': await readFile(effectivePreCommitPath(root), 'utf8') - .then((text) => text.includes(BEGIN_MARKER), () => false), - claude: await agentPresent('claude'), - codex: await agentPresent('codex'), - } -} - -/** Privacy-safe health summary: no hook path, command, or diagnostic text leaves this function. */ -export async function inspectLocalHookHealth(root: string): Promise { - const [installed, preCommitDoctor, claudeDoctor, codexDoctor] = await Promise.all([ - hookInstallations(root), - inspectHookDoctor(root, 'pre-commit'), - inspectHookDoctor(root, 'claude'), - inspectHookDoctor(root, 'codex'), - ]) - const doctors: Record = { - 'pre-commit': preCommitDoctor, - claude: claudeDoctor, - codex: codexDoctor, - } - const status = (target: HookTarget): HookHealthStatus => { - if (!installed[target]) return 'not_installed' - const relevant = doctors[target].checks.filter((check) => ( - check.target === target - || check.target === 'runtime' - || check.target === 'agent-runtime' - || check.target === 'config' - )) - if (relevant.some((check) => check.level === 'error')) return 'unhealthy' - if (relevant.some((check) => check.level === 'warning')) return 'warning' - return 'healthy' - } - return { - preCommit: status('pre-commit'), - claude: status('claude'), - codex: status('codex'), - } -} - export async function doctorHooks(root: string, target: string): Promise { const result = await inspectHookDoctor(root, target) for (const check of result.checks) { @@ -836,7 +183,7 @@ export async function hookStatus(root: string, target: string): Promise { path = effectivePreCommitPath(root) installed = await readFile(path, 'utf8').then((text) => text.includes(BEGIN_MARKER), () => false) } else { - path = join(root, name === 'claude' ? '.claude/settings.json' : '.codex/hooks.json') + path = agentSettingsPath(root, name) installed = await agentInstalled(root, name) } process.stdout.write(`${installed ? 'installed' : 'not installed'}\t${name}\t${path}\n`) diff --git a/packages/cli/src/receipt.ts b/packages/cli/src/receipt.ts index 4c2e955..62e977f 100644 --- a/packages/cli/src/receipt.ts +++ b/packages/cli/src/receipt.ts @@ -456,6 +456,67 @@ function commentSignalLines(receipt: Receipt): string[] { ] } +function tableCell(value: string): string { + return value.replaceAll('|', '\\|') +} + +function findingLocation(finding: Receipt['analyzers']['findings'][number]): string { + return finding.filePath ? `\`${finding.filePath}${finding.line ? `:${finding.line}` : ''}\`` : 'repository' +} + +/** At most this many dismissals are tabulated; the signed JSON always holds them all. */ +const SUPPRESSION_ROW_LIMIT = 100 +/** At most this many reason-less marker locations are named inline. */ +const REJECTED_MARKER_LIMIT = 20 + +/** + * What a `codetruss-ignore` comment did to this analysis. + * + * A dismissed finding is REPORTED as dismissed, never omitted. The product's + * whole claim is that the receipt states what was and was not flagged; a receipt + * that silently dropped a finding because a comment in the repository told it to + * would make "nothing was found" reachable by editing a comment, and the + * signature would then be attesting to a sentence the evidence does not support. + * So the finding, its location, and the exact reason its author gave all survive + * into the signed bytes, and the reader decides whether the reason is good. + * + * Markers that gave no reason are named too. They dismiss nothing — a reason is + * required precisely because the reason is the evidence — and a developer who + * wrote one has to be able to find out why nothing happened. + * + * Emits nothing when a repository dismissed nothing, so every receipt signed + * before suppression existed still renders, and verifies, byte for byte. + */ +function suppressionLines(receipt: Receipt): string[] { + const suppressed = receipt.analyzers.suppressed ?? [] + const rejected = receipt.analyzers.rejectedSuppressions ?? [] + if (suppressed.length === 0 && rejected.length === 0) return [] + const rows = suppressed.slice(0, SUPPRESSION_ROW_LIMIT) + const namedMarkers = rejected.slice(0, REJECTED_MARKER_LIMIT) + return [ + '', + `## Suppressed findings (${suppressed.length})`, + ...(suppressed.length ? [ + '', + 'The analyzers above produced these findings, and a `codetruss-ignore: ` comment in the source dismissed them. They did not affect the verdict. This list covers the whole repository, not only the changed files.', + '', + '| Severity | Analyzer | Location | Finding | Reason given |', + '|---|---|---|---|---|', + ...rows.map((finding) => `| ${finding.severity} | ${finding.analyzerId ?? 'unknown'} | ${findingLocation(finding)} | ${tableCell(finding.title)} | ${tableCell(finding.suppression?.reason ?? '')} |`), + ...(suppressed.length > rows.length + ? ['', `${suppressed.length - rows.length} further dismissed finding(s) are recorded in the signed JSON and not tabulated here.`] + : []), + ] : [ + '', + 'Nothing was dismissed in this repository.', + ]), + ...(rejected.length ? [ + '', + `${rejected.length} \`codetruss-ignore\` marker(s) gave no reason and therefore dismissed nothing: ${namedMarkers.map((site) => `\`${site}\``).join(', ')}${rejected.length > namedMarkers.length ? `, and ${rejected.length - namedMarkers.length} more` : ''}. A dismissal is accepted only as \`codetruss-ignore: \`, because the reason is the evidence. Those findings are still reported above.`, + ] : []), + ] +} + /** Which historical rendering of the analysis block to reproduce. */ type ReceiptMarkdownVariant = 'current' | 'legacy-scores' | 'prior-profile' @@ -494,7 +555,8 @@ function renderMarkdownInternal(receipt: Receipt, variant: ReceiptMarkdownVarian '', '| Severity | Analyzer | Location | Finding |', '|---|---|---|---|', - ...receipt.analyzers.findings.slice(0, 100).map((finding) => `| ${finding.severity} | ${finding.analyzerId ?? 'unknown'} | ${finding.filePath ? `\`${finding.filePath}${finding.line ? `:${finding.line}` : ''}\`` : 'repository'} | ${finding.title.replaceAll('|', '\\|')} |`), + ...receipt.analyzers.findings.slice(0, 100).map((finding) => `| ${finding.severity} | ${finding.analyzerId ?? 'unknown'} | ${findingLocation(finding)} | ${tableCell(finding.title)} |`), + ...suppressionLines(receipt), '', // Emits nothing when no finding carries a fix, so every receipt signed // before suggestions existed still renders to its original bytes. @@ -665,6 +727,10 @@ export async function createSyncEnvelope(receipt: Receipt): Promise Boolean(path) && !pathRelatedToChanges(path, changedPaths))), ] for (const finding of receipt.analyzers.findings) collectPotentialPaths(finding.metadata, possiblePrivatePaths) + for (const finding of receipt.analyzers.suppressed ?? []) { + if (finding.filePath && !pathRelatedToChanges(finding.filePath, changedPaths)) possiblePrivatePaths.push(finding.filePath) + collectPotentialPaths(finding.metadata, possiblePrivatePaths) + } for (const pass of receipt.analyzers.passes) { for (const finding of pass.result.findings) collectPotentialPaths(finding.metadata, possiblePrivatePaths) } @@ -689,7 +755,7 @@ export async function createSyncEnvelope(receipt: Receipt): Promise findings .filter((finding) => !finding.filePath || pathRelatedToChanges(finding.filePath, changedPaths)) .map((finding) => { const sanitized = { @@ -698,12 +764,30 @@ export async function createSyncEnvelope(receipt: Receipt): Promise pathRelatedToChanges(site.slice(0, site.lastIndexOf(':')), changedPaths)) + if (sites.length) synced.analyzers.rejectedSuppressions = sites + else delete synced.analyzers.rejectedSuppressions + } synced.verifications = synced.verifications.map((item) => ({ ...item, command: '[redacted for sync]', output: '' })) synced.evidence = { patchSha256: receipt.evidence.patchSha256, diff --git a/packages/cli/src/types.ts b/packages/cli/src/types.ts index 38342c4..df1f3d6 100644 --- a/packages/cli/src/types.ts +++ b/packages/cli/src/types.ts @@ -146,8 +146,21 @@ export interface LlmReview { interface AnalyzerReceiptEvidence { passes: AnalyzerPass[] - /** Only findings introduced or worsened between the reviewed snapshots. */ + /** Only findings introduced or worsened between the reviewed snapshots, minus any dismissed inline. */ findings: AnalyzerFinding[] + /** + * Findings dismissed by an inline `codetruss-ignore: ` comment, each + * carrying the reason given. Whole-repository, not delta-scoped, and present + * only when this repository dismissed something — which is what keeps every + * receipt signed before suppression existed rendering byte for byte. + */ + suppressed?: AnalyzerFinding[] + /** + * `path:line` of markers that gave no reason and therefore dismissed nothing. + * Their findings stay in `findings`; this exists so a comment is never seen to + * fail in silence. + */ + rejectedSuppressions?: string[] delta?: { introduced: number; worsened: number; recurring: number; resolved: number } index: Pick } diff --git a/packages/cli/test/grammar-loader.test.ts b/packages/cli/test/grammar-loader.test.ts index 5a15a7d..dc8221e 100644 --- a/packages/cli/test/grammar-loader.test.ts +++ b/packages/cli/test/grammar-loader.test.ts @@ -4,7 +4,11 @@ import { tmpdir } from 'node:os' import { dirname, join } from 'node:path' import { fileURLToPath } from 'node:url' import { afterEach, describe, expect, it, vi } from 'vitest' -import { pinnedGrammarPack } from '../src/grammar-pack-manifest.js' +import { + pinnedGrammarPack, + type PinnedGrammarFile, + type PinnedGrammarPack, +} from '../src/grammar-pack-manifest.js' import type { GrammarPackState } from '../src/grammar-pack.js' /** @@ -97,6 +101,78 @@ describe('the loader executes verified bytes, never a path', () => { }) }) +/** + * Which artifact is the grammar is a question the pin answers explicitly. + * + * It used to be answered by `files.find(f => f.name.startsWith('tree-sitter-'))` + * — the first match — while the pin verifier only proved that each published + * digest appeared SOMEWHERE in the generated file. The two together meant a pin + * with an extra entry ordered ahead of the real grammar passed review and was + * loaded instead of it. + */ +describe('the grammar is selected by role, not by name shape', () => { + it('ignores an injected artifact that would have won the old name-prefix match', async () => { + const dir = await scratchDir() + const contents = await verifiedContents() + contents.set('tree-sitter-evil.wasm', Buffer.from('not wasm')) + const injected: PinnedGrammarPack = { + ...pack, + files: [ + // No role, because the generated pin had no such field when this shape + // was hand-editable — and ordered first, matching `tree-sitter-`, which + // is exactly what the old `find` resolved as the grammar. + { + name: 'tree-sitter-evil.wasm', + url: '/downloads/grammars/python-1.0.0/tree-sitter-evil.wasm', + bytes: 8, + sha256: 'a'.repeat(64), + } as PinnedGrammarFile, + ...pack.files, + ], + } + + stub.current = { status: 'verified', pack: injected, dir, contents } + const load = await loadGrammarParser('python') + + // Against the old loader the injected file is the grammar, `Language.load` + // is handed `not wasm`, and this is a runtime failure instead. + expect(load.status).toBe('verified') + if (load.status !== 'verified') throw new Error('expected a verified load') + const tree = await load.parser.parse('python', 'import os\nos.system(name)\n') + expect(tree).not.toBeNull() + expect(tree!.hasError).toBe(false) + tree!.release() + }) + + it('refuses a pack that declares two grammars rather than loading the first', async () => { + const dir = await scratchDir() + const contents = await verifiedContents() + contents.set('tree-sitter-evil.wasm', Buffer.from('not wasm')) + const ambiguous: PinnedGrammarPack = { + ...pack, + files: [ + { + name: 'tree-sitter-evil.wasm', + role: 'grammar', + url: '/downloads/grammars/python-1.0.0/tree-sitter-evil.wasm', + bytes: 8, + sha256: 'a'.repeat(64), + }, + ...pack.files, + ], + } + + stub.current = { status: 'verified', pack: ambiguous, dir, contents } + const load = await loadGrammarParser('python') + + // A malformed pin is this binary's defect, not the user's install, so it + // must not render the receipt sentence that accuses their pack of tampering. + expect(load).toMatchObject({ status: 'failed', kind: 'runtime' }) + if (load.status !== 'failed') throw new Error('expected a failed load') + expect(load.reason).toContain('role grammar') + }) +}) + describe('why a pack was unusable is carried, not collapsed', () => { it('calls a pack that failed inspection a digest failure', async () => { const dir = await scratchDir() diff --git a/packages/cli/test/grammar-pack.test.ts b/packages/cli/test/grammar-pack.test.ts index c84cc22..f44f655 100644 --- a/packages/cli/test/grammar-pack.test.ts +++ b/packages/cli/test/grammar-pack.test.ts @@ -1,6 +1,7 @@ import { createHash } from 'node:crypto' -import { createServer, type Server } from 'node:http' +import { createServer, type Server, type ServerResponse } from 'node:http' import { chmod, lstat, mkdir, mkdtemp, readFile, rename, rm, stat, symlink, writeFile } from 'node:fs/promises' +import type { Socket } from 'node:net' import { tmpdir } from 'node:os' import { dirname, join } from 'node:path' import { fileURLToPath } from 'node:url' @@ -20,7 +21,10 @@ const repoRoot = join(dirname(fileURLToPath(import.meta.url)), '..', '..', '..') const publishedDir = join(repoRoot, 'public', 'downloads', 'grammars') const cleanup: string[] = [] +/** Per-test servers, torn down with their still-open sockets. */ +const servers: Array<() => Promise> = [] afterEach(async () => { + await Promise.all(servers.splice(0).map((close) => close())) await Promise.all(cleanup.splice(0).map((path) => rm(path, { recursive: true, force: true }))) }) @@ -354,6 +358,79 @@ describe.skipIf(process.platform === 'win32')('symlinks and loose permissions', }) }) +/** + * A hostile or broken origin must not be able to hang `grammars install`. + * + * Both cases here are servers that never violate any integrity rule — they just + * never finish. Without a deadline the install waits for them forever, which is + * a foreground command a person is watching with no output and no way to know + * whether it is working. + */ +describe('a stalled download is abandoned, not waited out', () => { + /** A server that answers `handler` and is torn down with the test. */ + async function stallingOrigin(handler: (res: ServerResponse) => void): Promise { + const sockets = new Set() + const stalling = createServer((_req, res) => handler(res)) + stalling.on('connection', (socket) => { + sockets.add(socket) + socket.on('close', () => sockets.delete(socket)) + }) + await new Promise((resolve) => stalling.listen(0, '127.0.0.1', resolve)) + servers.push(async () => { + for (const socket of sockets) socket.destroy() + await new Promise((resolve) => stalling.close(() => resolve())) + }) + const address = stalling.address() + return `http://127.0.0.1:${typeof address === 'object' && address ? address.port : 0}` + } + + /** {@link scratchEnv}, pointed at a server that will not finish. */ + async function stalledEnv(handler: (res: ServerResponse) => void): Promise { + return { ...(await scratchEnv()), [DEV_GRAMMAR_ORIGIN_ENV]: await stallingOrigin(handler) } + } + + it('gives up on a body that sends one byte and never ends', async () => { + // The exact shape the review demonstrated: a valid response, one byte, and + // then silence. Nothing about it is short, over-long or mis-hashed — the + // stream simply never ends, so only a clock can end it. + const env = await stalledEnv((res) => { + res.writeHead(200, { 'content-type': 'application/octet-stream' }) + res.write(Buffer.from([0x41])) + }) + + await expect(installGrammarPack('python', env, { totalMs: 5_000, idleMs: 250 })) + .rejects.toThrow(/sent no data for 250ms/) + // A failed install leaves nothing a later run could mistake for a pack. + await expect(inspectGrammarPack('python', env)).resolves.toMatchObject({ status: 'absent' }) + }) + + it('gives up on a server that accepts the connection and never answers', async () => { + const env = await stalledEnv(() => {}) + await expect(installGrammarPack('python', env, { totalMs: 5_000, idleMs: 250 })) + .rejects.toThrow(/sent no data for 250ms/) + }) + + it('gives up on a drip feed that never trips the idle clock', async () => { + // Progress without an end: a byte every 50ms keeps resetting the idle timer, + // so the total budget is the only thing that can stop it. 738 KB at this + // rate would take ten hours. + const env = await stalledEnv((res) => { + res.writeHead(200, { 'content-type': 'application/octet-stream' }) + const drip = setInterval(() => res.write(Buffer.from([0x41])), 50) + res.on('close', () => clearInterval(drip)) + }) + + await expect(installGrammarPack('python', env, { totalMs: 600, idleMs: 5_000 })) + .rejects.toThrow(/did not finish within 600ms/) + }) + + it('leaves a healthy install unaffected by the deadlines', async () => { + const env = await scratchEnv() + await expect(installGrammarPack('python', env, { totalMs: 30_000, idleMs: 10_000 })) + .resolves.toMatchObject({ alreadyInstalled: false }) + }) +}) + describe('uninstalling', () => { it('removes the pack and reports absence as the end state', async () => { const env = await scratchEnv() diff --git a/packages/cli/test/grammar-pin-verify.test.ts b/packages/cli/test/grammar-pin-verify.test.ts new file mode 100644 index 0000000..6fe2b98 --- /dev/null +++ b/packages/cli/test/grammar-pin-verify.test.ts @@ -0,0 +1,156 @@ +import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises' +import { tmpdir } from 'node:os' +import { dirname, join } from 'node:path' +import { fileURLToPath } from 'node:url' +import { afterEach, describe, expect, it } from 'vitest' +import { PINNED_GRAMMAR_PACKS } from '../src/grammar-pack-manifest.js' +import { lockedSourcePackage, lockfileEntries } from '../scripts/lockfile-integrity.mjs' +import { verifyGrammarPacks } from '../scripts/verify-grammar-packs.mjs' + +/** + * `pnpm grammars:verify` is the gate a hand-edited pin has to get past. + * + * The pin is generated and says so, which makes it exactly the file a reviewer + * skims. Everything the CLI will download and execute is described there, so the + * gate has to prove the whole file is what the published artifacts generate — + * not that each digest turns up somewhere inside it. + */ +const packageDir = join(dirname(fileURLToPath(import.meta.url)), '..') +const repoRoot = join(packageDir, '..', '..') +const realPinPath = join(packageDir, 'src', 'grammar-pack-manifest.ts') +const realLockfilePath = join(repoRoot, 'pnpm-lock.yaml') + +const cleanup: string[] = [] +afterEach(async () => { + await Promise.all(cleanup.splice(0).map((path) => rm(path, { recursive: true, force: true }))) +}) + +async function scratchFile(name: string, contents: string): Promise { + const dir = await mkdtemp(join(tmpdir(), 'codetruss-pin-')) + cleanup.push(dir) + const path = join(dir, name) + await writeFile(path, contents, 'utf8') + return path +} + +/** + * The check this gate used to perform, kept here as the thing being disproved. + * + * Every assertion below that a tampered pin is rejected is paired with the + * observation that this returned true for it — otherwise "the new check fails on + * a bad pin" would not tell anyone whether the old one did too. + */ +function oldSubstringCheck(pin: string): boolean { + return PINNED_GRAMMAR_PACKS.every((pack) => ( + pack.files.every((file) => pin.includes(`"sha256": "${file.sha256}"`)) + )) +} + +describe('the compiled-in pin is verified whole, not sampled', () => { + it('accepts the pin this repository actually ships', async () => { + const packs = await verifyGrammarPacks() + expect(packs).toHaveLength(PINNED_GRAMMAR_PACKS.length) + expect(packs[0].files.map((file) => file.role)).toEqual(['runtime', 'runtime-wasm', 'grammar']) + }) + + it('rejects a fourth artifact added beside the three real digests', async () => { + // F8's bypass verbatim: keep every published digest, add an entry nobody + // published, and order it where the old name-prefix loader would pick it up + // as the grammar. The pack directory check would then demand the extra file + // be present, and the CLI would download and load it. + const real = await readFile(realPinPath, 'utf8') + const injected = [ + ' {', + ' "name": "tree-sitter-evil.wasm",', + ' "role": "grammar",', + ' "url": "/downloads/grammars/python-1.0.0/tree-sitter-evil.wasm",', + ' "bytes": 8,', + ` "sha256": "${'a'.repeat(64)}"`, + ' },', + '', + ].join('\n') + const tampered = real.replace(' "files": [\n', ` "files": [\n${injected}`) + expect(tampered).not.toBe(real) + expect(oldSubstringCheck(tampered)).toBe(true) + + await expect(verifyGrammarPacks({ pinPath: await scratchFile('pin.ts', tampered) })) + .rejects.toThrow(/not what the published grammar packs generate/) + }) + + it('rejects digests bound to the wrong file name', async () => { + // Both real digests are still present — they have simply swapped artifacts, + // so the CLI would demand the runtime hash from the WASM and vice versa. + const real = await readFile(realPinPath, 'utf8') + const [runtime, runtimeWasm] = PINNED_GRAMMAR_PACKS[0].files + const swapped = real + .replace(runtime.sha256, 'PLACEHOLDER') + .replace(runtimeWasm.sha256, runtime.sha256) + .replace('PLACEHOLDER', runtimeWasm.sha256) + expect(swapped).not.toBe(real) + expect(oldSubstringCheck(swapped)).toBe(true) + + await expect(verifyGrammarPacks({ pinPath: await scratchFile('pin.ts', swapped) })) + .rejects.toThrow(/not what the published grammar packs generate/) + }) + + it('rejects a hand edit below the digests, where a skimming reviewer stops', async () => { + // Nothing about the digests changes; `pinnedGrammarPack` simply stops being + // a lookup. The old check read the array and never looked at the code. + const real = await readFile(realPinPath, 'utf8') + const rewritten = real.replace( + 'return PINNED_GRAMMAR_PACKS.find((pack) => pack.name === name)', + 'return PINNED_GRAMMAR_PACKS[0]', + ) + expect(rewritten).not.toBe(real) + expect(oldSubstringCheck(rewritten)).toBe(true) + + await expect(verifyGrammarPacks({ pinPath: await scratchFile('pin.ts', rewritten) })) + .rejects.toThrow(/not what the published grammar packs generate/) + }) +}) + +/** + * The provenance strings are a claim about where a pack's bytes came from, and + * until they were checked against the lockfile they were only a comment: a + * dependency bump with a stale string published a pack that misidentified its + * own source, and nothing failed. + */ +describe('pack provenance is checked against the workspace lockfile', () => { + it('rejects a lockfile that pins a different version than the pack claims', async () => { + const lockfile = [ + 'packages:', + '', + ' web-tree-sitter@0.99.0:', + ' resolution: {integrity: sha512-AAAA==}', + '', + ' tree-sitter-wasms@0.1.11:', + ' resolution: {integrity: sha512-BBBB==}', + '', + ].join('\n') + + await expect(verifyGrammarPacks({ lockfilePath: await scratchFile('pnpm-lock.yaml', lockfile) })) + .rejects.toThrow(/web-tree-sitter@0.22.6 but pnpm-lock.yaml pins web-tree-sitter@0.99.0/) + }) + + it('reads the integrity of a single-version dependency out of the real lockfile', async () => { + const lockfile = await readFile(realLockfilePath, 'utf8') + const entry = lockedSourcePackage(lockfile, 'web-tree-sitter', '0.22.6') + expect(entry.integrity).toMatch(/^sha512-/) + // Scoped names split at the version, not at the scope. + expect(lockfileEntries(lockfile, '@types/node').length).toBeGreaterThan(0) + }) + + it('refuses to guess when a dependency resolves to two versions', () => { + const lockfile = [ + 'packages:', + '', + ' web-tree-sitter@0.22.6:', + ' resolution: {integrity: sha512-AAAA==}', + '', + ' web-tree-sitter@0.23.0:', + ' resolution: {integrity: sha512-BBBB==}', + '', + ].join('\n') + expect(() => lockedSourcePackage(lockfile, 'web-tree-sitter', '0.22.6')).toThrow(/2 versions/) + }) +}) diff --git a/packages/cli/test/grammar-sast.test.ts b/packages/cli/test/grammar-sast.test.ts index 5fe3522..0f48322 100644 --- a/packages/cli/test/grammar-sast.test.ts +++ b/packages/cli/test/grammar-sast.test.ts @@ -73,6 +73,14 @@ async function pythonRepo(): Promise { ' url = request.args.get("url")', ' return requests.get(url).text', '', + '', + '# The DB-API two-step: the cursor is a short local, not a DB-ish name.', + 'def get_user(conn):', + ' user_id = request.args.get("id")', + ' cur = conn.cursor()', + ' cur.execute("SELECT * FROM users WHERE id = " + user_id)', + ' return cur.fetchall()', + '', ].join('\n')) await writeFile(join(root, 'app', 'clean.ts'), 'export const a = 1\n') return root @@ -143,6 +151,7 @@ describe('the local pass with the grammar pack installed', () => { expect(rules).toContain('command-injection') expect(rules).toContain('path-traversal') expect(rules).toContain('ssrf') + expect(rules).toContain('sql-injection') for (const finding of result.findings) { expect(finding.analyzerId).toBe('local-sast') expect(finding.filePath).toBe('app/views.py') diff --git a/packages/cli/test/hooks.test.ts b/packages/cli/test/hooks.test.ts index 199bbec..589c31d 100644 --- a/packages/cli/test/hooks.test.ts +++ b/packages/cli/test/hooks.test.ts @@ -438,6 +438,61 @@ describe('hook installation', () => { } }) + it('names the drifted fields and the remedy when an installed agent handler is stale', async () => { + // The repository's own .codex/hooks.json sat several CLI versions behind + // the installer: no `core.longpaths=true` in the command, and a Stop + // timeout of 300 where the installer had moved to 360. Doctor caught it, + // but said only "differs" — indistinguishable from a deliberate hand-edit, + // and with no command to run next. + const root = await repo() + await writeConfig(root) + const bin = join(root, 'node_modules', '.bin', process.platform === 'win32' ? 'codetruss.cmd' : 'codetruss') + await mkdir(dirname(bin), { recursive: true }) + await writeFile(bin, process.platform === 'win32' ? '@exit /b 0\r\n' : '#!/bin/sh\nexit 0\n') + await chmod(bin, 0o755) + await installHooks(root, 'codex') + + const path = join(root, '.codex', 'hooks.json') + const document = JSON.parse(await readFile(path, 'utf8')) as { + hooks: Record> }>> + } + const staleHandler = (event: string) => document.hooks[event] + .flatMap((group) => group.hooks) + .find((handler) => String(handler.command).includes('.codetruss/hooks/agent.cjs'))! + const stop = staleHandler('Stop') + stop.timeout = 300 + stop.command = String(stop.command).replace(' -c core.longpaths=true', '') + stop.commandWindows = String(stop.commandWindows).replace(' -c core.longpaths=true', '') + staleHandler('UserPromptSubmit').statusMessage = 'Capturing baseline' + document.hooks.PostToolUse[0].matcher = 'Edit' + await writeFile(path, `${JSON.stringify(document, null, 2)}\n`) + + const doctor = await inspectHookDoctor(root, 'codex') + expect(doctor.ok).toBe(false) + const drifted = (event: string) => doctor.checks + .find((check) => check.target === 'codex' && check.message.startsWith(`${event} handler differs`)) + expect(drifted('Stop')?.message).toBe( + 'Stop handler differs from the current safe installation (command, commandWindows, timeout);' + + ' run codetruss hooks install codex to refresh it', + ) + expect(drifted('UserPromptSubmit')?.message).toBe( + 'UserPromptSubmit handler differs from the current safe installation (statusMessage);' + + ' run codetruss hooks install codex to refresh it', + ) + expect(drifted('PostToolUse')?.message).toBe( + 'PostToolUse handler differs from the current safe installation (matcher);' + + ' run codetruss hooks install codex to refresh it', + ) + for (const event of ['UserPromptSubmit', 'PostToolUse', 'Stop']) { + expect(drifted(event)?.level).toBe('error') + } + + // Reinstalling is the remedy the message names, so it must actually work. + await installHooks(root, 'codex') + const repaired = await inspectHookDoctor(root, 'codex') + expect(repaired.checks.filter((check) => check.message.includes('handler differs'))).toEqual([]) + }) + it('warns when an older install shadows the codetruss the hooks will run', async () => { // D6: the installer's readiness check was a bare `command -v codetruss`, // which succeeds just as happily when an older binary sits earlier in PATH. diff --git a/packages/cli/test/npm-tarball.test.ts b/packages/cli/test/npm-tarball.test.ts new file mode 100644 index 0000000..0579dec --- /dev/null +++ b/packages/cli/test/npm-tarball.test.ts @@ -0,0 +1,79 @@ +import { createSign, generateKeyPairSync } from 'node:crypto' +import { readFile } from 'node:fs/promises' +import { dirname, join } from 'node:path' +import { fileURLToPath } from 'node:url' +import { describe, expect, it } from 'vitest' +import { readTarballMembers, verifyRegistrySignature } from '../scripts/npm-tarball.mjs' + +/** + * The two primitives the release-time attestation rests on. + * + * `attest-grammar-sources.mjs` itself needs the network, so it is not a test — + * but nothing about reading a tarball or checking a signature does, and those + * are the parts where a quiet bug would turn the attestation into a formality + * that passes on anything. + */ +const repoRoot = join(dirname(fileURLToPath(import.meta.url)), '..', '..', '..') +/** A real npm tarball this repository publishes, used as the fixture. */ +const cliTarball = join(repoRoot, 'public', 'downloads', 'codetruss-cli-latest.tgz') + +describe('reading an npm tarball', () => { + it('returns the members of a real published package', async () => { + const members = readTarballMembers(await readFile(cliTarball)) + const manifest = members.get('package/package.json') + expect(manifest).toBeInstanceOf(Buffer) + expect(JSON.parse(manifest!.toString('utf8')).name).toBe('@codetruss/cli') + // Every member is a file the archive actually carries, with its real length. + expect(members.get('package/dist/cli.cjs')!.length).toBeGreaterThan(1_000) + expect(members.get('package/does-not-exist')).toBeUndefined() + }) + + it('refuses an archive whose headers do not check out rather than guessing', async () => { + // Corrupt the length field of the first header. A reader that trusted it + // would resynchronise onto file content, read a data block as a header, and + // hand back whatever it found under whatever name it thought it had. + const { gunzipSync, gzipSync } = await import('node:zlib') + const tar = Buffer.from(gunzipSync(await readFile(cliTarball))) + tar.write('00000000777', 124, 11, 'ascii') + expect(() => readTarballMembers(gzipSync(tar))).toThrow(/bad checksum/) + }) +}) + +describe('verifying a registry signature', () => { + const keyPair = generateKeyPairSync('ec', { namedCurve: 'prime256v1' }) + const keyid = 'SHA256:test' + const keys = [{ + keyid, + keytype: 'ecdsa-sha2-nistp256', + scheme: 'ecdsa-sha2-nistp256', + key: keyPair.publicKey.export({ format: 'der', type: 'spki' }).toString('base64'), + }] + const name = 'web-tree-sitter' + const version = '0.22.6' + const integrity = 'sha512-AAAA==' + const sign = (message: string) => createSign('SHA256') + .update(message) + .end() + .sign(keyPair.privateKey) + .toString('base64') + + it('accepts a signature over exactly name@version:integrity', () => { + const signature = { keyid, sig: sign(`${name}@${version}:${integrity}`) } + expect(() => verifyRegistrySignature({ name, version, integrity, signature, keys })).not.toThrow() + }) + + it('rejects a signature over a different tarball digest', () => { + // The attack the signature exists to stop: a mirror serving the right + // version with someone else's bytes. The digest is inside the signed + // message, so a substituted one cannot carry the old signature. + const signature = { keyid, sig: sign(`${name}@${version}:sha512-BBBB==`) } + expect(() => verifyRegistrySignature({ name, version, integrity, signature, keys })) + .toThrow(/does not verify/) + }) + + it('rejects a key the registry never published', () => { + const signature = { keyid: 'SHA256:unknown', sig: sign(`${name}@${version}:${integrity}`) } + expect(() => verifyRegistrySignature({ name, version, integrity, signature, keys })) + .toThrow(/no published key/) + }) +}) diff --git a/packages/cli/test/release-scripts.d.ts b/packages/cli/test/release-scripts.d.ts new file mode 100644 index 0000000..694c748 --- /dev/null +++ b/packages/cli/test/release-scripts.d.ts @@ -0,0 +1,35 @@ +/** + * Types for the release scripts the tests drive. + * + * `packages/cli/scripts/*.mjs` run under plain node with no build step and are + * deliberately outside the TypeScript program. These declarations exist only so + * the tests that exercise them are type-checked like the rest of the suite. + */ +declare module '*/verify-grammar-packs.mjs' { + export function verifyGrammarPacks(options?: { + grammarDir?: string + moduleDir?: string + pinPath?: string + lockfilePath?: string + }): Promise }>> +} + +declare module '*/lockfile-integrity.mjs' { + export function lockfileEntries(lockfile: string, packageName: string): Array<{ version: string; integrity: string }> + export function lockedSourcePackage( + lockfile: string, + packageName: string, + expectedVersion: string, + ): { version: string; integrity: string } +} + +declare module '*/npm-tarball.mjs' { + export function readTarballMembers(tarball: Uint8Array): Map + export function verifyRegistrySignature(input: { + name: string + version: string + integrity: string + signature: { keyid: string; sig: string } + keys: Array<{ keyid: string; keytype: string; scheme: string; key: string }> + }): void +} diff --git a/packages/cli/test/suppression.test.ts b/packages/cli/test/suppression.test.ts new file mode 100644 index 0000000..71c8dc3 --- /dev/null +++ b/packages/cli/test/suppression.test.ts @@ -0,0 +1,224 @@ +import { createHash } from 'node:crypto' +import { mkdtemp, readFile } from 'node:fs/promises' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { afterEach, describe, expect, it } from 'vitest' +import type { AnalyzerFinding, RepoIndex } from '@codetruss/analyzer-engine' +import { analyzerReceipt, computeVerdict } from '../src/analysis.js' +import { createSyncEnvelope, newSessionId, renderMarkdown, verifyReceipt, writeReceipt } from '../src/receipt.js' +import { LOCAL_ANALYSIS_PROFILE, type ChangedFile, type Receipt } from '../src/types.js' + +const originalKey = process.env.CODETRUSS_SIGNING_KEY +afterEach(() => { if (originalKey === undefined) delete process.env.CODETRUSS_SIGNING_KEY; else process.env.CODETRUSS_SIGNING_KEY = originalKey }) + +function finding(overrides: Partial = {}): AnalyzerFinding { + return { + analyzerId: 'secrets', + category: 'SECURITY_HYGIENE', + severity: 'HIGH', + title: 'Possible Database URL with credentials committed in db.ts', + description: 'Line 4 of src/db.ts appears to contain a credential.', + filePath: 'src/db.ts', + line: 4, + impactScore: 95, + ...overrides, + } +} + +function dismissed(reason: string, overrides: Partial = {}): AnalyzerFinding { + return finding({ ...overrides, suppression: { reason, markerLine: 4, applied: true } }) +} + +const changedFile: ChangedFile = { + path: 'src/db.ts', change: 'modified', classification: 'allowed', dependency: false, additions: 1, deletions: 0, +} + +function verdictInput(findings: AnalyzerFinding[]) { + return { verifications: [], files: [changedFile], startDirty: false, findings } +} + +function analysis(findings: AnalyzerFinding[]) { + return { + findings, + passes: [{ id: 'secrets', result: { findings, complete: true } }], + index: { totalLoc: 10, languages: { TypeScript: 10 }, primaryLanguage: 'TypeScript' } as unknown as RepoIndex, + } +} + +function receiptFixture(root: string, patch = 'diff evidence'): Receipt { + const now = new Date('2026-08-06T09:00:00.123Z') + return { + receiptVersion: 1, sessionId: newSessionId(now), createdAt: now.toISOString(), finishedAt: now.toISOString(), durationMs: 0, + mode: 'review', task: 'suppression receipt', repoRoot: root, startCommit: 'abc', endCommit: 'abc', + git: { baselineTree: 'a'.repeat(40), finalTree: 'b'.repeat(40) }, policy: { sha256: 'c'.repeat(64) }, + startDirty: false, startDirtyFiles: [], + scope: { allow: ['src/**'], deny: [] }, + files: [changedFile], + diff: { + sha256: createHash('sha256').update(patch).digest('hex'), + bytes: Buffer.byteLength(patch), + totalBytes: Buffer.byteLength(patch), + truncated: false, + }, + analyzers: { passes: [], findings: [], analysisProfile: LOCAL_ANALYSIS_PROFILE, index: { totalLoc: 0, languages: {}, primaryLanguage: null } }, + verifications: [], coverageNotes: ['local'], verdict: 'PASS', reasons: ['no changes'], evidence: {}, + } +} + +describe('a dismissed finding stops gating', () => { + it('lets a verdict pass over a dismissed high-severity security finding, and says so', () => { + const outcome = computeVerdict(verdictInput([dismissed('rotated; kept until the migration window closes')])) + + expect(outcome.verdict).toBe('PASS') + expect(outcome.reasons).toContain( + '1 finding(s) on changed files were dismissed by an inline codetruss-ignore comment and are listed with their reasons on this receipt', + ) + }) + + it('still fails on the identical finding when nothing dismissed it', () => { + const outcome = computeVerdict(verdictInput([finding()])) + + expect(outcome.verdict).toBe('FAILED') + expect(outcome.reasons[0]).toContain('high/critical security or dependency finding') + }) + + it('still fails when the marker gave no reason, because that dismisses nothing', () => { + const outcome = computeVerdict(verdictInput([ + finding({ suppression: { reason: '', markerLine: 4, applied: false } }), + ])) + + expect(outcome.verdict).toBe('FAILED') + expect(outcome.reasons.join(' ')).not.toContain('dismissed by an inline') + }) + + it('does not count a dismissed medium finding toward review either', () => { + const outcome = computeVerdict(verdictInput([ + dismissed('duplicate of the handler above, intentional', { severity: 'MEDIUM', category: 'TECH_DEBT', impactScore: 40 }), + ])) + + expect(outcome.verdict).toBe('PASS') + }) +}) + +describe('the receipt records what was dismissed', () => { + it('splits the delta into reported and dismissed, and reports dismissals repository-wide', () => { + const reported = finding({ line: 9, title: 'Possible AWS access key committed in aws.ts' }) + const inDelta = dismissed('fixture credential for the local compose stack') + const elsewhere = dismissed('vendored sample kept verbatim', { filePath: 'vendor/sample.ts', line: 2 }) + const rejected = finding({ filePath: 'src/api.ts', line: 12, suppression: { reason: '', markerLine: 11, applied: false } }) + + const envelope = analyzerReceipt( + analysis([reported, inDelta, elsewhere, rejected]), + undefined, + { introduced: [reported, inDelta, rejected], worsened: [], recurring: [elsewhere], resolved: [] }, + ) + + expect(envelope.findings).toEqual([reported, rejected]) + expect(envelope.suppressed).toEqual([inDelta, elsewhere]) + expect(envelope.rejectedSuppressions).toEqual(['src/api.ts:11']) + }) + + it('omits both fields entirely when the repository dismissed nothing', () => { + const envelope = analyzerReceipt(analysis([finding()]), undefined, { + introduced: [finding()], worsened: [], recurring: [], resolved: [], + }) + + expect(envelope).not.toHaveProperty('suppressed') + expect(envelope).not.toHaveProperty('rejectedSuppressions') + }) + + it('names the finding, its location, and the exact reason its author gave', () => { + const receipt = receiptFixture('/tmp/repo') + receipt.analyzers.suppressed = [dismissed('compose-only default | never deployed')] + + const markdown = renderMarkdown(receipt) + + expect(markdown).toContain('## Suppressed findings (1)') + expect(markdown).toContain('This list covers the whole repository, not only the changed files.') + // The pipe in the reason is escaped, so one comment cannot break the table. + expect(markdown).toContain( + '| HIGH | secrets | `src/db.ts:4` | Possible Database URL with credentials committed in db.ts | compose-only default \\| never deployed |', + ) + }) + + it('explains a marker that dismissed nothing rather than letting it fail in silence', () => { + const receipt = receiptFixture('/tmp/repo') + receipt.analyzers.rejectedSuppressions = ['src/api.ts:11'] + + const markdown = renderMarkdown(receipt) + + expect(markdown).toContain('## Suppressed findings (0)') + expect(markdown).toContain('Nothing was dismissed in this repository.') + expect(markdown).toContain('1 `codetruss-ignore` marker(s) gave no reason and therefore dismissed nothing: `src/api.ts:11`') + expect(markdown).toContain('Those findings are still reported above.') + }) + + it('renders a receipt that dismissed nothing exactly as before, so earlier signatures keep verifying', () => { + const receipt = receiptFixture('/tmp/repo') + receipt.analyzers.findings = [finding()] + + expect(renderMarkdown(receipt)).not.toContain('Suppressed findings') + // A receipt whose signed JSON never knew about the field renders the same + // bytes as one that knows about it and has nothing to report. + const aware = structuredClone(receipt) + aware.analyzers.suppressed = [] + aware.analyzers.rejectedSuppressions = [] + expect(renderMarkdown(aware)).toBe(renderMarkdown(receipt)) + }) + + it('signs and verifies a receipt carrying dismissals', async () => { + const root = await mkdtemp(join(tmpdir(), 'codetruss-suppression-')) + const dir = join(root, 'receipts') + process.env.CODETRUSS_SIGNING_KEY = join(root, 'signing.pem') + const receipt = receiptFixture(root) + receipt.analyzers.suppressed = [dismissed('reviewed 2026-08-06, dev-only container')] + receipt.analyzers.rejectedSuppressions = ['src/api.ts:11'] + + const paths = await writeReceipt(dir, receipt, 'diff evidence') + const verified = await verifyReceipt(dir, receipt.sessionId) + const markdown = await readFile(paths.markdown, 'utf8') + + expect(verified.analyzers.suppressed).toHaveLength(1) + expect(markdown).toContain('reviewed 2026-08-06, dev-only container') + }) +}) + +describe('sync keeps dismissals private-safe', () => { + it('drops unrelated dismissals, redacts private paths from a reason, and keeps the related one', async () => { + const root = await mkdtemp(join(tmpdir(), 'codetruss-suppression-sync-')) + const dir = join(root, 'receipts') + process.env.CODETRUSS_SIGNING_KEY = join(root, 'signing.pem') + const receipt = receiptFixture(root, 'private patch') + receipt.analyzers.suppressed = [ + dismissed('mirrors the constant in private/unrelated.ts', { metadata: { otherFile: 'private/unrelated.ts' } }), + dismissed('whole-repo dismissal that must not sync', { filePath: 'private/unrelated.ts', line: 7 }), + ] + receipt.analyzers.rejectedSuppressions = ['src/db.ts:3', 'private/unrelated.ts:6'] + await writeReceipt(dir, receipt, 'private patch') + + const envelope = await createSyncEnvelope(receipt) + const synced = JSON.parse(envelope.signedReceipt) as Receipt + + expect(synced.analyzers.suppressed).toHaveLength(1) + expect(synced.analyzers.suppressed?.[0].filePath).toBe('src/db.ts') + expect(synced.analyzers.suppressed?.[0].suppression?.reason).toBe('mirrors the constant in [redacted unrelated path]') + expect(synced.analyzers.suppressed?.[0]).not.toHaveProperty('metadata') + expect(synced.analyzers.rejectedSuppressions).toEqual(['src/db.ts:3']) + expect(envelope.signedReceipt).not.toContain('private/unrelated.ts') + }) + + it('removes the fields entirely when nothing dismissed relates to a synced path', async () => { + const root = await mkdtemp(join(tmpdir(), 'codetruss-suppression-sync-empty-')) + const dir = join(root, 'receipts') + process.env.CODETRUSS_SIGNING_KEY = join(root, 'signing.pem') + const receipt = receiptFixture(root) + receipt.analyzers.suppressed = [dismissed('unrelated', { filePath: 'private/unrelated.ts', line: 7 })] + receipt.analyzers.rejectedSuppressions = ['private/unrelated.ts:6'] + await writeReceipt(dir, receipt, 'diff evidence') + + const synced = JSON.parse((await createSyncEnvelope(receipt)).signedReceipt) as Receipt + + expect(synced.analyzers).not.toHaveProperty('suppressed') + expect(synced.analyzers).not.toHaveProperty('rejectedSuppressions') + }) +}) diff --git a/public/downloads/codetruss-cli-0.2.41.sbom.cdx.json b/public/downloads/codetruss-cli-0.2.41.sbom.cdx.json new file mode 100644 index 0000000..6faedbb --- /dev/null +++ b/public/downloads/codetruss-cli-0.2.41.sbom.cdx.json @@ -0,0 +1,170 @@ +{ + "$schema": "https://cyclonedx.org/schema/bom-1.6.schema.json", + "bomFormat": "CycloneDX", + "serialNumber": "urn:uuid:c69a8bce-8d55-58bb-b05e-2933e91d1b21", + "specVersion": "1.6", + "version": 1, + "metadata": { + "component": { + "type": "application", + "bom-ref": "pkg:npm/%40codetruss/cli@0.2.41", + "name": "@codetruss/cli", + "version": "0.2.41", + "description": "Local-first scope, quality, and verification receipts for coding agents", + "licenses": [ + { + "license": { + "name": "CodeTruss CLI Proprietary License" + } + } + ], + "purl": "pkg:npm/%40codetruss/cli@0.2.41" + }, + "properties": [ + { + "name": "codetruss:distribution", + "value": "single-file JavaScript bundle" + }, + { + "name": "codetruss:runtimeDependencies", + "value": "0" + } + ] + }, + "components": [ + { + "type": "library", + "bom-ref": "pkg:npm/%40codetruss/analyzer-engine@0.1.0", + "name": "@codetruss/analyzer-engine", + "version": "0.1.0", + "licenses": [ + { + "license": { + "name": "CodeTruss CLI Proprietary License" + } + } + ], + "purl": "pkg:npm/%40codetruss/analyzer-engine@0.1.0", + "properties": [ + { + "name": "codetruss:bundled", + "value": "true" + } + ] + }, + { + "type": "library", + "bom-ref": "pkg:npm/balanced-match@4.0.4", + "name": "balanced-match", + "version": "4.0.4", + "licenses": [ + { + "license": { + "id": "MIT" + } + } + ], + "purl": "pkg:npm/balanced-match@4.0.4", + "properties": [ + { + "name": "codetruss:bundled", + "value": "true" + } + ] + }, + { + "type": "library", + "bom-ref": "pkg:npm/brace-expansion@5.0.7", + "name": "brace-expansion", + "version": "5.0.7", + "licenses": [ + { + "license": { + "id": "MIT" + } + } + ], + "purl": "pkg:npm/brace-expansion@5.0.7", + "properties": [ + { + "name": "codetruss:bundled", + "value": "true" + } + ] + }, + { + "type": "library", + "bom-ref": "pkg:npm/minimatch@10.2.5", + "name": "minimatch", + "version": "10.2.5", + "licenses": [ + { + "license": { + "id": "BlueOak-1.0.0" + } + } + ], + "purl": "pkg:npm/minimatch@10.2.5", + "properties": [ + { + "name": "codetruss:bundled", + "value": "true" + } + ] + }, + { + "type": "library", + "bom-ref": "pkg:npm/yaml@2.9.0", + "name": "yaml", + "version": "2.9.0", + "licenses": [ + { + "license": { + "id": "ISC" + } + } + ], + "purl": "pkg:npm/yaml@2.9.0", + "properties": [ + { + "name": "codetruss:bundled", + "value": "true" + } + ] + } + ], + "dependencies": [ + { + "ref": "pkg:npm/%40codetruss/analyzer-engine@0.1.0", + "dependsOn": [] + }, + { + "ref": "pkg:npm/%40codetruss/cli@0.2.41", + "dependsOn": [ + "pkg:npm/%40codetruss/analyzer-engine@0.1.0", + "pkg:npm/minimatch@10.2.5", + "pkg:npm/yaml@2.9.0" + ] + }, + { + "ref": "pkg:npm/balanced-match@4.0.4", + "dependsOn": [] + }, + { + "ref": "pkg:npm/brace-expansion@5.0.7", + "dependsOn": [ + "pkg:npm/balanced-match@4.0.4" + ] + }, + { + "ref": "pkg:npm/minimatch@10.2.5", + "dependsOn": [ + "pkg:npm/brace-expansion@5.0.7" + ] + }, + { + "ref": "pkg:npm/yaml@2.9.0", + "dependsOn": [] + } + ] +} diff --git a/public/downloads/codetruss-cli-0.2.41.tgz b/public/downloads/codetruss-cli-0.2.41.tgz new file mode 100644 index 0000000000000000000000000000000000000000..5370c2732d89a9a027f2154bdbaf6e6c560aa797 GIT binary patch literal 873560 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%0Dk-awTHJCMDj| z@2yvD%;Nb7xH`gm$M3!#Va?Epf~?{_pzeTez4`-^;<(2Ya4

xOK36El4Hvi~a0N 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

DfvK!-2R zATG=qhG$Fdb^0=oC`yIuT~C@*KjcdTFMkHsLgHfeIZJd7^#Sx7f*Y47e3gi!TwJcr zD{$xcx3*#imUGlV+#Het+#yVQTmn5#877<@Yuao{>9O_$++N+@NTy441|22~O zi=Ub1>Rao(GtH1=cgE0>f4&*_2REns^TRPy0+$m!V0E-NhxMMf2@fRf;3){~8(>nS%j1iewQ&SAXgmDrJUo2FO1Q z;IJ_>|8y(V+v9U6?<#L5;^C##tY-I+0S<5@*mn#N`%9MaV|O&hD(rJJXW${j@YRw< z$_^9e9Y~2YsqRKXupd_uTI4Lq_QP-G^2#Qg(`RQB`%l108D09Q$)82)9BM=$QI)Q0@~6%mAy@1S zrFF7eLG(ISV7E2?8;fU?j>`uJb@YrEk}8$G8XM&}4oo}n?`l=nxq_fP6g)=Ex1YY6 zpjQYnylD*>T%mKoGDf!!(5gyird5G$ajkgkE7&ZG3-ZhzT61xxdkBR#3{6l$G#|ow zQB*Ggud7fF+O#YJ+m@op91TXDI*mJ^$Y~nLm1%ggI-v^S40*u0uoWTj-ABnzhl4dm z^}+4Tw!>2ypSAC7P2`0s-3{D{*q(`2NNFEi>y(Of;5SE053f(BN9qv~p~Llezr!Pf#5*mz6W|LJmh=+O%^-QU*Grf`LaB=(O#D}KN@`|Odb@YQC7xFt zEP=KaBNZ1{CUDKCmtH~<@8(A7a$M;0QfS0+(;7bp<^dm?A)iL>EY1@+EIw;3n*L8uy- zO4%~=ty=PJ&H8#2x9hq2hCSUN2eeY_`D>+)&Al=(0yJ4Lfj@K^YdX)GjTbK<(Moe3 zNa;Aj>0Ec>gM8Dt;3h~st3Y8p``XWd7yTzF_igw57L>ydI_Xne4hRiZWPFJYnTGTzMoUvj`>kTX&Hp^b-X7IgVpj3MT;2DY=xedhka5t8q-J7 z1d>#<$t01nkKM%g9a2d5fsvD>5R=2G4RZ6eJ1Lr1+ z)gfe_!<7Pj?E%verWyb8I4~G<|Hz5Wq%NW@j-xlY?AmIaH>H#bR6kYrRu}&lgr}#A z|8Wq1)(;mCo>Z5l$MdRKE(H|lkgY&}lGB9%6&A`tWZNsfTfy{e82t23F7$dl5ngftosC)@Ae&F7CQUK#V}7_3HMc8Ib% zy0W2Ppe3HwsfUFZb++haX)I^8<0eYutdQrnWAO{r>2vH&p1CtST^gPlYubmG31;FJ zp4^-Oncoq+<@x{vYJP(?@3ses87v*>mc{KU`#iy$csDm{ut(Hub{gB+=1rLK+0NzY z6oPJOik;KW2F`(B=BT0{O4s$bNTB-jxQ7!3z>MW#P5yx6e=Hvlz`;MW0~TQ`+p){Z z{oe7YJ-RC6_8oZo_0(RC3hZnC;yD>G$wh|ZX<-2$Jz6T1Qa4I z!Pi_);F5D2yb=C>{B3stM+ta8XgWG@<*Zxwi|2*ssPfs{BStTb?udb6og&u4ow z*p>9s4=)-(+aqHbBk(4#8ZfDc-@WJAwpMxz3u2}4WIn092E)VZq|)QN0?(dSy{Ux- zH&l3GmFkx&p~SQ5i{`w^MsA{{<7Io&<{@U$k=x0!Fjq{Sdq|Gi$```ylkeep@(z}E z@I(|h-i{}EnMij6*(0&|`4v13mdRFgX_@-3uZpS;aT0JMiceCPiMV=}tArWM5^+5E7GFU<@djZw1zr9%h?Ppa;9IBqo*LY6`by9PQWR%L{?_lK)u5 z`C|&X9V?){<}}qP*WqP0dgI1(DZ!oxTmi*~WF(kIsq&%MF6qe?lsHEq6qh}-zV+kb z>e|}+&d!^+2vRU^2%CCw1ktU7lRtF(q5e?JWz(Y?4vuflKfj}X=Z^fJZ|@#w*0*d{mPm9NF&*82L(ox_dS+i%|P%H=s)j$jm{HxjgmgXE$jB>nDy z25tJiFUI#|eh{vof!4Hwr3CVu7R(T@^X&pt|&~ zR(|;G@F6HjEVcJjl(rXG;xNrUoK5=aaN*`~;USBnkcgZ|YGXgnN7z&CE-(ux22)&228^@)p}hYu6|0w0jl zr9TMpO^UfKGfDt{kAW_*OW+R*eZ_I@t<(-!0B2c(cHgi6^YF*j%^%j|&{?ZBr!y)u zl8+{6FD~q(<{bpno~U&@Sn+^NN3GJI4{AJk z_w2tQ&_I}~;qfLUfbT8`?{#0+C=k?+yO0OEwe~3>P`%c{>>VvGE|%J`KFW3ltvy;; zSSTH*315F*T6`#Xa;K#Q|MB(LD2illRO&svmZqmi2XQzM;h^*7M$`QSB@wH^JlXFCAz`Q8|l;9)6&ATl8g_dmDV|=&z)~(FjF@A@H&%*=Nx8x z3F~k^{LtO$g3?TOhwFn6uX>*pGL5U*sE?vrBvWduis9r6RkAzmyHZW)+r#*QJ5a#6w6;3PbLKYN0ex>oGO1(I@ zZN%dtLIXI&F@D+9(p_-XS9$H?V_ROwO(=$!(*H{5L&3LPTz2ehuXj!LU1 zcs25@Yqt^PrvnS?(LEWLD*F(f_K&Iv+bofe8cJ7F{W|!7R99fhyx{_nw9p`sqG(?+7q1fLasn;+cq4ha6&KFo6$HQy%Oow zz*Rr0y~nX?18W4x|T1PRB!bG%oqX&?ip) zONi-2LugILL}e93R;I&+j^Kc86V!=gmAQ4>BeX_`5G@yU^kELhzlEF^`-;@#n0!oYuqsW03#ALCP zhurrgBm%nia_#7krR92Q2f;H-(yfD#eU>CBjRKN0yd99$26z zG#@;9rqGeo(&Imk_xA^nOOVP#?&H#n7bUu30Ayg>M>Wjtt2)RH7YR>w%L*)>j=J3p zx)J?kUc8@->EyPci69xI&(1X&f}jy^)=G=g;kX1hVA@`Z1fVp-aZgO|j^yJ(4;&wE z8WGROXLt%-=~Ig3xXED4L7}rGqC*LiDI`)u+PHGS$sZI6m}84j)h zk7+Xx*x(pp%2bxDyR}ddTFD%qEA%l;m8?DV)1ql0*|%eJ13bYNa{6!F=a4niX8#Qj z;3ZW3rQ9C@nh4PqLKG3^F*X+HYa9d6ji+3fdI1464Qw249Nc!%GMUan1##O7vWHA! z!N4vUi$nJdXc|=Z3F1R{97kC9YuDOn^FARm2b>Yr2LSV}bOECtxHzP!qtyuwVl$A~560_P=+4T~lmuptH^<9w09^6|aX4?3K5~boO{!?XoW$g$F=>mly>Cgm7>jc=mDg*H28 z6A$E(T(ULtJ(7@iU&V%T>A2hHY68u`y+lp*m93eI1B6ofLB{S@UdLKte&oS6=vqmjqX4Uwpl{3q zMx|XCylsHD?bY*eQP7q=rO@To5;R5Fy@mq-0fTB&VwD3vvU(AEBKG)JA<6+!uoIX^ zsq1Xmr$9M@4#V96u^ihPuihVv#KaO%9K{x67=n$2moZ59dg58lorHBSt!*6ctndD? zeYmr`zKz?fWZ-nU-7A;me~Ut!8YKul7~psbj@g$Ni*z3S`oo{GPFkuY7|}?+OPJkB zchTs7qtu^_m!80vQDhUx{^kq+MTKlzN!Dwnj<0&%{t5Qy?=cJ?_98cL15g18MCVw0 z8U(Q2)li@UkPq>H_rX$HDq1<9`(SZ=W-EtMUw>FGBM_{u{>IkM?&{{It~?0wzq+_! zrhi*y3UHksuBs-ITv^vJUoEuY1bI3Pa;oYD!dg15n^KWuEyou*tDAVSAAMN#7jb zsW_y^^B&!CqXgl$H#o2Y{XSw0!P6HP*^%<|@OZ2hNH|ZgyfQi*Lym8hE&({GWFHH+B zqB=QRLXMZEZ$M{JDFeL;#Byit?Z)=*;hVQF*Wbbe6w)s7UEF2ySI{4qH!QH8NFB!1 zctN286J7U{+mqm_CvlZOi4h)AGUZ+bv{_JJ{3};2xb@Q|D8ud6_O=ijw2`AEiC>c# zqBE{dC~f+Sndw6mKebx3I}73mU!QE`I4x0a>0dt@RPP?$9wlX(x>&f&0x9is)Lpg6 zA!xm=c%e6!vLW~H~2^Fh9cH8e=tcI{=CP=YhE+Msj1#=G>BDn7UxhkN(?b>6&}+hA2hP>5Hz;O&R502OmyxyD*` z-@$U*C?k;O4?N0UW!xIPl*ZkND{P2yvHo)7hu7sAA`sK6^4h?vf^%+VNJpaPDDo#< z>Jve`UfO~&f#^Ew9m5A|q{Kq6CXdxljv5g6p>15iZ3>d?@c$$4?c3TqvbE3W--ad5ErdK||pWXpz$)`|O)=`7F;P#l^2@A$SQzn>gpK_w1* zyHm)1)jNBGGt}+6(TuDtY;Ack)@<~W%vek@4Do@2-;_ov-0pz82>GCUWrj$Q?W0nb z*P=eHl?efz&}omd$~2mJRMn(tsv?SN*SLC&sLITZ#4S?$s!!=_Z<3uZQ+)sj>c$y5gp!X z-!wSxL)YK0ta(bkECqhH^n0nd%fl^2Y0n#RP!sc3Xck@Hgk8ao@~{Ck;&%3hE8uQ` z`{kjGHw2xa_Sd0S3Mbh1_>!k)1#>Bg)Q`Pk=?vu!n;gGeR+sb?Vk5A9#1HC3{aMIU znHXgn5=d+oj`>OrgTK%vDdpGYFBzD-WQca=*6=QW06fpw~$}GP8a%X4UaOOR>UwtQ|eIDnqllR zXl}U}6?-VIaRG(A5I1Pc6p9-o-xwqV(dIn)`+_IGC$dx!MHLNcd=HbcVGLOaz^Ym z93ex9r5@M5ukDB0h@oLIk~*3*+hFfKn!pR=)UvSm4u{BUGQ6J!;;-#$W>6&~a__Vl z4f8DOr~*QGOEUy)2`=!;H5 zq4L3cBr%hg`2nDfN^GAF?-SQGM2um{Sox>lRs*1-Y>c-*^*zI*eztCGu;UjV^E)h% zPF?f!hqmD$Xz%b?quCeX!OCLhVuBU4anFMK##a6L*Vgiu}+CX8T zQ6J*0o1dYK6HC!xPjg7;(}USeIA@fHRDElW(o4r7(+*8|__5Z$Hv#?G4itK{PpD#+VsEs+V9 z`Z=r=t`l_WK!<7x83(#68HZ82V4tvMio4f-D-5FVTtG3R&_5mYak`n??bmkEeHr@`td{L*8FtPI_jVI z_6d>buoXZuPR$^)2DX;Wzb`WJrhG9Ae@ZvxN?5knrdw7~lz`Gz^mfs_>pm2Xbju&W zAx+zMqtR>a3M%QrZtLM#Ek9`E{-O+)YBn!wuk{?ijKkIeHQTobjf3`(eZwl8oqgDx z@UkCwb4>bFsXa3N=;6$F#^}>^8m=0i@ zvP1hli4+%jab)Do>}c~s3^V1NX-V^4W~Pv^q)i&zFXGGh^93p2sZP2B>u4E&DJ~fs za`?;QqSa7rJ=)vzT}R}ZgzM6w+J%YIDfqUtaei9XiNr(E((59R?oPBb+;U|45|^tl z0f|r&BI1%5U*U%X%=0V-WDC)o&M1I~W1mteO%#SJlKn>6hbJ7itNoh5#Z|;se{jeB zaD4LVu)j}Ro!y8ePiO{SyZ9aqr!*5aQKD;b_T<0~`+zeNX2b^?x;7{YkYCw zmN@!yNQzTsqk||Y=M3KDbak>!g(k=W0h46kBTE| zI1*)RHW34IURYg$+>3RGMnuwP@PO{h&;U9xVSHO{;Rv*~WPmQtPPPHknHT}AMG@{# z#fGz^1hUMx0IEy~*k^5K0{$)R6jE0(Zsd7U#}y1Auz(mo?jh%H^|00?z~UIVW)Ol> z_?Fwxp)Sr}Kt=ls^GANZM!l7L*rTz4gxFQfi?5!$Q5mN)!5?=%X%vinefL1ERjMvJ z^J^~+I4|)3=!aF&ea_p?36Yno_ynk4Fb|K_VDLtERC2MpSy57S^aJLgBAxfuEZ8b#sE^!4kfGK&;OrLd&uFGckNUD$4%f2+n`?2Fo~OAZ8|6wgi@p%6UAGJQQNI4%{Cg4UlJpj|Z-MsU z*oU^;fZ_oB0;~~Rn3?$H_El^wT5FKRSJ*|9R;6}eCtF*p_BKvJ#;lb(?AbZV{Gz!O(9m_{IAd&jV`2W)+vf{EY-A<~CpUmEq82VH-=ZdMB`plm z=)Yq^*;|Y(=*%_85~-zZ1ou%qX_;zgN4)W6C#?^08YaNG)ZQ{(x2v$^sgE+qJzDdV z!i0HT7pjPh6$f}8YeR`DY!?gINHGHq2fG6bVm&LAuVuj?>4yIHu=QO@H)Ml}$|6?| zjmr*c5+y3ISr-f3vG4EQ%XB8s#u(o%`|uansxKCMO(fad7imTDS*%JX=gylk8Z{0ojiV_lk;0NjQYuQ+l3$p>*YM~_70!i`5Z$|m)AI@b z-5em#ETnGf#d>9bo++;>M*fBknlqb{CwWeyZjb}WL1cN9IN@j$|9Nrw`O44TM>F_*(3o)?OOC0R=-mm-Zzdo)S zaA4o4h#C*2LoZf};;$iySKMqi>V(!kV(czXMI?=^_5#eQ+1?+>{isoYPe!6R2!vJ= zD=%_vgLxvAXVZzkU~g8J7U#d<3AU51cvB5(V|Dwl z@2z+3+Ro7V&7#}*=H|le&YV~)UiZ*HDrcX<>jzUvY+JMh&KLqY(R?YMl zgT3hRhLRMv@L6%auD%F?n>KE~p%V_nT(I(eN4Ft!@{|Z>nqlj+{9-5y4`#N0BqYG=H+dNkOI0%-;I_Bb$I zRF|Z(gXja@#f;v0XtxqFr`2M%pcb#NR6PCcVkUC$p>;oY?u-byiB_fQnv37P=M790`jB)dn;Y)b2#C zrm8x{*TAt_>56r-6`p6LOBKTp?6(z)0$8~S-oizZh+{%Waj?=@+%Q&?2FJ&d?wVeY z!$az)zg`P9U5c}H&C$WS{@>m6Kn32&Jj^%aLvfQdLu@JkSmyW45z1%nX?JG!v}0w% zt~)go`7K+LIi0UB%&&2VU!9ZYV;QZ7!G+<0`t!PuXZQfsh)b=5Y|)wC+MWAxac&b} zC1EUGJ^IV_*6#FmD?AY#=gv<)^p9itckee(vBsQjzb(*Ix39kaORMyx%=;+*WfF;T zblE$HAKvOtsfL;F$~DX#E0Ou>V6R3Jm%PEl4^xO}Q^nhXa=pT9sr3F8JnQBbCp_Ai zy`!g_<;T%2l^%b2aw1+=dGp-j@4YZo0LtpM-##uqwoVWm_KSSDKA(Kv-0hv74Y#uv z^CZTK(xa;mLZy!S7USa$3enWm4`gjzu7O*%wmw%IEzH+OE2|63>+7#-&Dqtp#h>Oj z7HR-5S=nszWT<5LKmr)zfQaatu${baDvb94p`j4KS7tI3aMfm~yR8pErwpj->igEU zeoPM%bws$r+MBf{_fg7_35r)Z{@{?e{qwLnJ2KS4(Ggl=00g2lTyB2m+NtD8ZM}Qq|ZN`_pnEMf3-XhDL>J2t5YrUHl zLK6>w7%BB9bs=_awgCx}aE~P)$zjyn-Fr0it-8$U`$)~NEE9tVwA!L|3y9CP_-4av z6H?rRhOKb>+63dxhWPoL*ROwh`)Xx)&U+I>lPx9Rx21k_7}T2 zILce<0)~&!GTnw$+bm!*Ohyv-B|eOMY>08Obx$m{N4P8mL=d;2b2*oRlR`IJ?S-l% zngJ!(I|fP((9HwEczx_YiDA~#IXPlfl>epe{|eQvANg|eq5Jq@5Z_oZ0d=Y3SEJ-D zNI{h#rQ9%)2Pye0ipnH`4h6UsD~|wdL`&uu1->@x@kE{CM%cG;sD3#6FuzX674l4C z(c`-YT2(p|`)+S1;n-zAbt5=ocQT-G#vs)P1+MT54dk8tRbli8!wt@w_9C$G^R-y5Jw31vfw~E$n-lpBIgUx|K#${W9H%KM2kx2>>la&yrt`RjW zqF0+cCT){t(U?S#x-Bd>0riUK|1p?LLdIqUCSa z7B}F`zr0*rSYF5!*!9Kb7c0?;cV|AKz!9L`lQlit9%~NDgs-76TM|LV*^+OuSzxQj z>LtdO2XyF-5_?cQBw3xKPXI|lq7iuTu|g*NxM~JNJ3nxm;shM+7&5YFtur?nX{d*W z!xdSN8{_c`{s6v}!Ff3}$O&BS82BrBWGeoY^9jdolrReOlKNbYj(!okj+18^9F2fI zM#vSBVuF_8EcfZ;;!q$5ly=?UA2^?5-3@`P!gdpiiFr}TN%#^cQhWjAt4fdeiHF%W z6mawKQFoo!5^{LX{vXkq6oJU}xn~)zs~*^I^1duODI2H(=(e&^PUezbhUCMwaJC8A z9ilsSpWm)Sazn5&9PPGEFfJjR;_kFWP)AOEV1TF74l-J-h?E`69=Lb!MmfdThLtKW zqwwJL)-X5_gfit$aB%O-v=IAnyD%JkA@OAu6%>(S!a?2qHX^O)MmOFq5Dhz9`^Zg% zc4rW~ziw@gwzdOeu9!$BQJHd0)f8O{8jrznL1>#W@8I=d0%qu8TBuP;C>NMz1q-G- zV@ufBSE?0VXlZ=z-5U%G_I~f)xDaGb)#%-78oh-pm18`1^tLGvHmfW{z*s|f{s`MK zBXq%YutZ|l)anW#?b7J)!xfbF0KTCgDR5lkO$67Ne0GAa7 z&)KELLD%r}B5t1J)X-#XhB3W3HYWfXnr?`U=2+{kEy)~g@+L!Zg{OU2wo&EoMu~G5 zOijIZ`J}?rSaf$X(ntNC@wd^aBno|j_HVN_-a`DD+fV7`vpcW7EtqhD*|ErEW~gA< zanqKb!8Yk)ZFF`d!C~4mmnVp3*gqmc2hI z+`<6osEruKsC)5poR>n=!+W%PcpMYm0QZO9rFXkw&&htKO*ga$tF|!SaERifIS>#n z+&^rVP{Q(tv}=Qq@e_q+%|N4Y zZ@+CULLW64PToPhIvjusni!GAp&(6|l>sgL`E;BwbB)ycN${?=$?#~lJ~iz~uH1<{ zh1G3gWqJ+W>ZSP)9h8h!?PM`oStq}qx0DG)$ZsXVCV|z`G@J-(7`8d8_!5@fB>Z@+8T zEH)NEsq?AclxBU3VXZgCq=S?w-YeWod7g4MH%1SIO5(CMiEF+C3KS^PZ(6wzb9rbU zJ?vDh>R;ekB8YjNkUpPxN?eHNG8QGRyo@MUjDWBBZenM%>?jjpW!+Rpr6#ONOD|y} zo_8mpdYkkwQ`-bfj2WN=qTP`U-73hb;Jw8OT5D#A_f0y<{eh!P(W^QU>l+vimxw&b zV}f^9aT6XWZa1>F;Aq6EH70kn1oTIwiD(*x0vU7hvV2L;PY5$$(|sRL3+E7-*a&=1 z_9VhEh#@Qa?Wa~ww(Z@3oD5hM;sRA4f{h%pk8fKfz7;E&EIA=lJP#>@`rJcUY^RSS zxWV$e(kZSEHe~>h;9HZU*Z=lMHffQvA2i0?O@a>KSv2UGMQHX$lU$^)8;x?V4f9Tj zAK?+>RwpG6fP8Y*LOQnQCMM&@1g^z5g$7m)X6b%#Xypj=lT^nm%ckvKo_j6L@hJzY zk*B*KSs?w0;6%xOqyE|1$=OKU$uKiV(G8e1KL@>w!}HP6V4tWGZD-Otf4&?^dO9

$C{!Db0cAcb2WA0J}t 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 >FaIs~i@cPP-N@l_NyM5F1k z$)igOxznwjPONZVQ)#aA=bI%D^L}@@3DX{FOt%k5?n}O;B2^J6f)+4t`N`m6X*(lsf@pZav29?>G5jklkH`!q6vNo zkn#Cdo-qUINT-jUAE;WyA7^W@Vy9iun#2PPjdFL#%{{T7`i`!ycwuUMz^v;$as3V& zpqU)&uv7-T&(F6ERVB0(VP^{8$GQCmU^959Uf9Q+L49rM!nC!YA9cPLl}K`h1+aqSM!iWu$$GVd+E^U=9?4J%U`c_uf;MPnOXOJhhf zQ_eV+PHtQT1<0ohHI+OfV5@$G4u!sg&=(fXrNxTM|eK?v1@rp;yLeiV?F=kiU;2^+3~v57QR4__0{sDc2~NE9ZzXd zW#@jr`TPkXl|e8Jf<|E+=;lqza!K4{4DYOz}%W?dy0+_Tk) zRf0%XD2s}YWQomtiGyQale%r=v1ZDZlIUXam4wcgXA2UVU1TU%+JTO9!IOU#cm5-jBYKWxBEkzr!$4F@GNArS_A3w- z0$}#wZH_LF+WhYh6i=wqL{(3xyo(qsn+tD4XMx(V?4ad!(+CtJ)^1>h;9mmgBwE%q zm3<8rgGPLCt42HmSEdJ_b6vR~_Tu1%*J5v`3){i-dKIxCD$^MTy5SriouR%>y*^zh zsMk#;PdFJ~Age zAG&tFC|0iu_mcU-gh^7jdPI{jHoM1siiu`dAQGiYGMFT+$Eg5Z=TgxON;NEb=KSI(0*A|4!T=wv^pq(VE z#+Kcu$Wh|?1>Ui7A!=IGrKgdu;5*1u607W*)Z&{xrjJ;6JUz27m?F;FBMuDd0XS0k z^fUAphE8O>-(XPM6O}7au$O6B+s>pDDHL9M2J7sTb#e2^V%;=u@{uS}i48NUNxZyg zTT(FNcvNAut7~>Gsid%S1!t~06Sli~753|U$HWl|w-sEoVdE%FlxeMH0lql*yEZoQ zjrvva>9r4_61n#P?i@IajeCbeFC-|u45o=vUns`Ij$nu&ly5x>ZL?dmawz4!0zZld z(M$Q;9OxJ32m|HWu|Jt8nFSr$Itl0wVlIi1Q5vyC+ae5JYl;&c_IR(HW}_^2pIQOtk?1 z0`6>NGr^5=1)SsD6Ar0ktI|3I>Z6I3ygHrfaG7`iU~g;ZyThk@Yv1W2;~@MNPFcPx zhq&dNzP(r;U`^p3Gfth@y`rph0L(G*B$va}XN0JsvoIo_%Vu@^cKe*eG0IL^=DJjD z-kp$-;o>5)FTnNxii4cdm}`ZXtjy^|lV=|IU(wH-bXr-j*{A!~c0QFFsfCU*3Y%`t zP#~FT0f7}p!Jnedif-+XK_eoIx}mH8iCbdKNz8L57=u#u7(#QQwWbHWSSrGjQ1;EW zyooQ{!Sknw`uN%#!WV`$jviiqtQ_b%gYmBQOuk1AU_ljhB3G=YT=uv-AeiQirsLt9 z_#V@>Kwt|_BNmBLs@2!+57NqvEMw1k%5(&FA+LalWeGi=hvvfZrxXN2Yq1)@Cf^{w z-mFZ6(>W<^Y$e0G+EQwrf5*zUN&BERk`<-(<^RAc*Kdr5UTn+2x5SA|!IB^0SXWxa z3@r&U>#>AeZ4DdaY3+kcy6nnXvl1U53~wGWGHWGSQ4i^h*(}m0dx}I-(ssTn8F!rP(w+hN zsx&|UIVawXYw5_>v4}!iBcll|1^Sv;qfVY+zzhW{RpE7PNX8WTFO4N4Y26%OZ0L;s z0%`MU8Q1v8SNx*WA0A+I(DhT{0;B~avqXAa3t~O*5(&Kgt)Go#>X4K?YI_;U2BX-9 zt#0VUYQ|F5c^2M!DnH+J(~^A^D>){+uq?&ibb2lF37xIHlG3?pKZR*fp

s+9H4%mhrV^K6ubuvG{ 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
  • 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 z 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

    <-Zo)y^L{NFj+yvs?5;m(!xI8EyP3#?L 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 v9h?fS{Qh2t1C##Unf* literal 0 HcmV?d00001 diff --git a/public/downloads/codetruss-cli-0.2.41.tgz.sha256 b/public/downloads/codetruss-cli-0.2.41.tgz.sha256 new file mode 100644 index 0000000..5db4f9e --- /dev/null +++ b/public/downloads/codetruss-cli-0.2.41.tgz.sha256 @@ -0,0 +1 @@ +cb0b9d69f15e7113a6a523a6b139ec3150b7d07459e5242b2c047fc9aac57d04 codetruss-cli-0.2.41.tgz diff --git a/public/downloads/codetruss-cli-latest.json b/public/downloads/codetruss-cli-latest.json index ffc0697..c8cf0a6 100644 --- a/public/downloads/codetruss-cli-latest.json +++ b/public/downloads/codetruss-cli-latest.json @@ -1,13 +1,13 @@ { "name": "@codetruss/cli", - "version": "0.2.40", - "url": "/downloads/codetruss-cli-0.2.40.tgz", + "version": "0.2.41", + "url": "/downloads/codetruss-cli-0.2.41.tgz", "latestUrl": "/downloads/codetruss-cli-latest.tgz", - "sha256": "5d64313b8b60acbd1f93e2246557967885a98fdc8c486ea7b2a6417fd8acdac2", - "sbomUrl": "/downloads/codetruss-cli-0.2.40.sbom.cdx.json", - "sbomSha256": "a78a5a08993e2266cee606f892d2afe118880605dd911d3f2149f99e4e8001bd", + "sha256": "cb0b9d69f15e7113a6a523a6b139ec3150b7d07459e5242b2c047fc9aac57d04", + "sbomUrl": "/downloads/codetruss-cli-0.2.41.sbom.cdx.json", + "sbomSha256": "9cad0195baee5d1655dab3410ec350f6198be193722badf1cccdd8663c296a4a", "node": ">=20.9.0", - "repository": "https://github.com/DeliriumPulse/codetruss-cli", - "releaseUrl": "https://github.com/DeliriumPulse/codetruss-cli/releases/tag/v0.2.40", - "attestationCommand": "gh attestation verify codetruss-cli-0.2.40.tgz --repo DeliriumPulse/codetruss-cli" + "repository": "https://github.com/CodeTruss/codetruss-cli", + "releaseUrl": "https://github.com/CodeTruss/codetruss-cli/releases/tag/v0.2.41", + "attestationCommand": "gh attestation verify codetruss-cli-0.2.41.tgz --repo CodeTruss/codetruss-cli" } diff --git a/public/downloads/codetruss-cli-latest.sbom.cdx.json b/public/downloads/codetruss-cli-latest.sbom.cdx.json index fe75706..6faedbb 100644 --- a/public/downloads/codetruss-cli-latest.sbom.cdx.json +++ b/public/downloads/codetruss-cli-latest.sbom.cdx.json @@ -1,15 +1,15 @@ { "$schema": "https://cyclonedx.org/schema/bom-1.6.schema.json", "bomFormat": "CycloneDX", - "serialNumber": "urn:uuid:87fe717b-5581-5c87-9d3e-571c96b13a30", + "serialNumber": "urn:uuid:c69a8bce-8d55-58bb-b05e-2933e91d1b21", "specVersion": "1.6", "version": 1, "metadata": { "component": { "type": "application", - "bom-ref": "pkg:npm/%40codetruss/cli@0.2.40", + "bom-ref": "pkg:npm/%40codetruss/cli@0.2.41", "name": "@codetruss/cli", - "version": "0.2.40", + "version": "0.2.41", "description": "Local-first scope, quality, and verification receipts for coding agents", "licenses": [ { @@ -18,7 +18,7 @@ } } ], - "purl": "pkg:npm/%40codetruss/cli@0.2.40" + "purl": "pkg:npm/%40codetruss/cli@0.2.41" }, "properties": [ { @@ -139,7 +139,7 @@ "dependsOn": [] }, { - "ref": "pkg:npm/%40codetruss/cli@0.2.40", + "ref": "pkg:npm/%40codetruss/cli@0.2.41", "dependsOn": [ "pkg:npm/%40codetruss/analyzer-engine@0.1.0", "pkg:npm/minimatch@10.2.5", diff --git a/public/downloads/codetruss-cli-latest.tgz b/public/downloads/codetruss-cli-latest.tgz index 4b5b0c697265614876e9be8949887b28c37ab67d..5370c2732d89a9a027f2154bdbaf6e6c560aa797 100644 GIT binary patch delta 18534 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 diff --git a/public/downloads/codetruss-cli-latest.tgz.sha256 b/public/downloads/codetruss-cli-latest.tgz.sha256 index a8db5aa..a068e29 100644 --- a/public/downloads/codetruss-cli-latest.tgz.sha256 +++ b/public/downloads/codetruss-cli-latest.tgz.sha256 @@ -1 +1 @@ -5d64313b8b60acbd1f93e2246557967885a98fdc8c486ea7b2a6417fd8acdac2 codetruss-cli-latest.tgz +cb0b9d69f15e7113a6a523a6b139ec3150b7d07459e5242b2c047fc9aac57d04 codetruss-cli-latest.tgz diff --git a/release-reference.json b/release-reference.json index 17042be..dee2195 100644 --- a/release-reference.json +++ b/release-reference.json @@ -1,8 +1,8 @@ { "schemaVersion": 1, - "version": "0.2.40", - "websiteArchive": "https://codetruss.com/downloads/codetruss-cli-0.2.40.tgz", - "archiveSha256": "5d64313b8b60acbd1f93e2246557967885a98fdc8c486ea7b2a6417fd8acdac2", - "sbomSha256": "a78a5a08993e2266cee606f892d2afe118880605dd911d3f2149f99e4e8001bd", - "bundleSha256": "7dcf9a22457c6790c08b2ddc1186f1c73213e58ad6920aa7f164c8ce2d3e9076" + "version": "0.2.41", + "websiteArchive": "https://codetruss.com/downloads/codetruss-cli-0.2.41.tgz", + "archiveSha256": "cb0b9d69f15e7113a6a523a6b139ec3150b7d07459e5242b2c047fc9aac57d04", + "sbomSha256": "9cad0195baee5d1655dab3410ec350f6198be193722badf1cccdd8663c296a4a", + "bundleSha256": "6cbc571a3f6a7108dd46e232e79c753b00abdbc6d6ff6798f92728d7e6ac91a7" } From ffe6fb3ab5be8def2459056eff411e4d323bd40d Mon Sep 17 00:00:00 2001 From: Zack Whitson Date: Fri, 7 Aug 2026 11:05:18 -0500 Subject: [PATCH 2/2] Pin the grammar pack sources in the mirror lockfile 0.2.41 adds a lockfile integrity check: a grammar pack records the package and version its wasm was cut from, and `verify-grammar-packs` refuses to believe that provenance unless pnpm-lock.yaml pins exactly one resolution of each source package. The mirror is its own workspace with its own lockfile, and it pinned neither, so the check failed with pnpm-lock.yaml does not pin web-tree-sitter; grammar pack provenance cannot be checked on every Node 22 and Node 24 leg. Node 20.9 passed, which is why this was not visible until the grammar tests ran. Declares the same two packages the monorepo declares, at the same versions the pack provenance names, and regenerates the lockfile. The released archive is unaffected: it rebuilds to cb0b9d69f15e7113a6a523a6b139ec3150b7d07459e5242b2c047fc9aac57d04 with and without this change. Co-Authored-By: Claude Opus 5 --- package.json | 4 ++++ pnpm-lock.yaml | 17 +++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/package.json b/package.json index 12f642d..3075109 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,10 @@ "test:install": "pnpm --filter @codetruss/cli test:install", "validate": "pnpm typecheck && pnpm release:artifact && pnpm test && pnpm release:verify && pnpm test:install" }, + "dependencies": { + "tree-sitter-wasms": "0.1.11", + "web-tree-sitter": "0.22.6" + }, "devDependencies": { "@codetruss/analyzer-engine": "workspace:*", "@codetruss/cli": "workspace:*" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 24575c5..bd94cd6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,6 +7,13 @@ settings: importers: .: + dependencies: + tree-sitter-wasms: + specifier: 0.1.11 + version: 0.1.11 + web-tree-sitter: + specifier: 0.22.6 + version: 0.22.6 devDependencies: '@codetruss/analyzer-engine': specifier: workspace:* @@ -557,6 +564,9 @@ packages: resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} engines: {node: '>=14.0.0'} + tree-sitter-wasms@0.1.11: + resolution: {integrity: sha512-26sE4+qoTi1CbzHdo9sHs9pRE/jXVFVRigSG/5TNAbwhSMVjHfMAg4UjmOhAFAIx5UxgoQuaURwqhm0SRNrpWA==} + tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -657,6 +667,9 @@ packages: jsdom: optional: true + web-tree-sitter@0.22.6: + resolution: {integrity: sha512-hS87TH71Zd6mGAmYCvlgxeGDjqd9GTeqXNqTT+u0Gs51uIozNIaaq/kUAbV/Zf56jb2ZOyG8BxZs2GG9wbLi6Q==} + why-is-node-running@2.3.0: resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} engines: {node: '>=8'} @@ -1057,6 +1070,8 @@ snapshots: tinyrainbow@3.1.0: {} + tree-sitter-wasms@0.1.11: {} + tslib@2.8.1: optional: true @@ -1111,6 +1126,8 @@ snapshots: transitivePeerDependencies: - msw + web-tree-sitter@0.22.6: {} + why-is-node-running@2.3.0: dependencies: siginfo: 2.0.0