diff --git a/CHANGELOG.md b/CHANGELOG.md index a51cefc..089607d 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.62 on GitHub](https://github.com/CodeTruss/codetruss-cli/releases/tag/v0.2.62), +The current public release is [v0.2.63 on GitHub](https://github.com/CodeTruss/codetruss-cli/releases/tag/v0.2.63), distributed from . npm publication is a separate, manually dispatched step, so the npm `latest` tag can trail the website and the GitHub release; the dispatch for this @@ -13,6 +13,18 @@ were superseded before distribution. ## Unreleased +## 0.2.63 — 2026-08-09 + +- **`codetruss sync` no longer relabels who produced a receipt.** The sync + envelope used to overwrite the receipt's signing identity with whatever key + ran the sync, so a teammate exporting your receipt made the hosted record + claim they signed it. The producer's public key and fingerprint now travel + unchanged, and the exporting key is named separately in + `evidence.exporter`; the envelope signature stays the exporter's. The + hosted side pins API credentials to the exporting key (the machine actually + syncing) and displays the producer as the signer, with the exporter shown + when the two differ. + ## 0.2.62 — 2026-08-09 - **The comment analyzer learned the difference between deferring with a diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 8c728e8..d94bdc2 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -5,6 +5,18 @@ checksums are published at ({ ...item, command: '[redacted for sync]', output: '' })) + // The producer's signing identity stays exactly as the receipt recorded it; + // the exporting key signs the envelope and is named separately. Overwriting + // the producer fields here (the old behavior) relabeled a teammate's receipt + // as whoever ran `codetruss sync`. + const producer = receipt.evidence.publicKey && receipt.evidence.keyFingerprint + ? { publicKey: receipt.evidence.publicKey, keyFingerprint: receipt.evidence.keyFingerprint } + : { publicKey: key.publicKey, keyFingerprint: key.fingerprint } synced.evidence = { patchSha256: receipt.evidence.patchSha256, - publicKey: key.publicKey, - keyFingerprint: key.fingerprint, + ...producer, + exporter: { publicKey: key.publicKey, keyFingerprint: key.fingerprint }, } synced.coverageNotes = [ ...synced.coverageNotes, diff --git a/packages/cli/src/types.ts b/packages/cli/src/types.ts index d6be2de..127d707 100644 --- a/packages/cli/src/types.ts +++ b/packages/cli/src/types.ts @@ -250,7 +250,10 @@ export interface Receipt { coverageNotes: string[] verdict: Verdict reasons: string[] - evidence: { markdownSha256?: string; patchFile?: string; patchSha256?: string; signatureFile?: string; publicKey?: string; keyFingerprint?: string } + /** `exporter` appears only on hosted-sync copies: the key that signed the + * sync envelope, kept separate so exporting a teammate's receipt can never + * relabel who produced it (`publicKey`/`keyFingerprint` stay the producer's). */ + evidence: { markdownSha256?: string; patchFile?: string; patchSha256?: string; signatureFile?: string; publicKey?: string; keyFingerprint?: string; exporter?: { publicKey: string; keyFingerprint: string } } } export interface ReviewOptions { diff --git a/packages/cli/test/receipt.test.ts b/packages/cli/test/receipt.test.ts index bf4c0c2..05b1796 100644 --- a/packages/cli/test/receipt.test.ts +++ b/packages/cli/test/receipt.test.ts @@ -615,8 +615,10 @@ describe('signed receipts', () => { suggestion: 'Extract [redacted unrelated path] and src/changed.ts into one module.', filePath: 'src/changed.ts', line: 3, impactScore: 55, effort: 'medium', }]) - expect(Object.keys(synced.evidence).sort()).toEqual(['keyFingerprint', 'patchSha256', 'publicKey']) - expect(verifyBytes(envelope.signedReceipt, synced.evidence.publicKey!, envelope.signature)).toBe(true) + expect(Object.keys(synced.evidence).sort()).toEqual(['exporter', 'keyFingerprint', 'patchSha256', 'publicKey']) + // The envelope is signed by the exporting key, named separately so the + // producer identity fields cannot be relabeled by whoever runs the sync. + expect(verifyBytes(envelope.signedReceipt, synced.evidence.exporter!.publicKey, envelope.signature)).toBe(true) expect(envelope.signedReceipt).not.toContain('secret prompt') expect(envelope.signedReceipt).not.toContain('sensitive output') expect(envelope.signedReceipt).not.toContain('"command": "test"') diff --git a/public/downloads/codetruss-cli-0.2.63.sbom.cdx.json b/public/downloads/codetruss-cli-0.2.63.sbom.cdx.json new file mode 100644 index 0000000..feff3f5 --- /dev/null +++ b/public/downloads/codetruss-cli-0.2.63.sbom.cdx.json @@ -0,0 +1,170 @@ +{ + "$schema": "https://cyclonedx.org/schema/bom-1.6.schema.json", + "bomFormat": "CycloneDX", + "serialNumber": "urn:uuid:5587fc86-bda2-5368-8379-3f323f53b318", + "specVersion": "1.6", + "version": 1, + "metadata": { + "component": { + "type": "application", + "bom-ref": "pkg:npm/%40codetruss/cli@0.2.63", + "name": "@codetruss/cli", + "version": "0.2.63", + "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.63" + }, + "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.9", + "name": "brace-expansion", + "version": "5.0.9", + "licenses": [ + { + "license": { + "id": "MIT" + } + } + ], + "purl": "pkg:npm/brace-expansion@5.0.9", + "properties": [ + { + "name": "codetruss:bundled", + "value": "true" + } + ] + }, + { + "type": "library", + "bom-ref": "pkg:npm/minimatch@10.2.6", + "name": "minimatch", + "version": "10.2.6", + "licenses": [ + { + "license": { + "id": "BlueOak-1.0.0" + } + } + ], + "purl": "pkg:npm/minimatch@10.2.6", + "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.63", + "dependsOn": [ + "pkg:npm/%40codetruss/analyzer-engine@0.1.0", + "pkg:npm/minimatch@10.2.6", + "pkg:npm/yaml@2.9.0" + ] + }, + { + "ref": "pkg:npm/balanced-match@4.0.4", + "dependsOn": [] + }, + { + "ref": "pkg:npm/brace-expansion@5.0.9", + "dependsOn": [ + "pkg:npm/balanced-match@4.0.4" + ] + }, + { + "ref": "pkg:npm/minimatch@10.2.6", + "dependsOn": [ + "pkg:npm/brace-expansion@5.0.9" + ] + }, + { + "ref": "pkg:npm/yaml@2.9.0", + "dependsOn": [] + } + ] +} diff --git a/public/downloads/codetruss-cli-0.2.63.tgz b/public/downloads/codetruss-cli-0.2.63.tgz new file mode 100644 index 0000000..7bdc2fb Binary files /dev/null and b/public/downloads/codetruss-cli-0.2.63.tgz differ diff --git a/public/downloads/codetruss-cli-0.2.63.tgz.sha256 b/public/downloads/codetruss-cli-0.2.63.tgz.sha256 new file mode 100644 index 0000000..4131d7a --- /dev/null +++ b/public/downloads/codetruss-cli-0.2.63.tgz.sha256 @@ -0,0 +1 @@ +cc63fdbc491b2ef7ac579bd721488e4a876287930995a0b347b74024180be748 codetruss-cli-0.2.63.tgz diff --git a/public/downloads/codetruss-cli-latest.json b/public/downloads/codetruss-cli-latest.json index 0728dad..adb614c 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.62", - "url": "/downloads/codetruss-cli-0.2.62.tgz", + "version": "0.2.63", + "url": "/downloads/codetruss-cli-0.2.63.tgz", "latestUrl": "/downloads/codetruss-cli-latest.tgz", - "sha256": "35ad8c84288014ced4a184e702480bd478310e9c990fe18498c0e1038388e8a9", - "sbomUrl": "/downloads/codetruss-cli-0.2.62.sbom.cdx.json", - "sbomSha256": "4d5b7fb2eeaf1a17bb9f9891161b098f5a6dffd53fa7650c30de045d7c456284", + "sha256": "cc63fdbc491b2ef7ac579bd721488e4a876287930995a0b347b74024180be748", + "sbomUrl": "/downloads/codetruss-cli-0.2.63.sbom.cdx.json", + "sbomSha256": "6e89ef8f91c0d0cdc893845876af3b1fa225744a89f85c2eac2f5263c7d6dc4a", "node": ">=20.9.0", "repository": "https://github.com/CodeTruss/codetruss-cli", - "releaseUrl": "https://github.com/CodeTruss/codetruss-cli/releases/tag/v0.2.62", - "attestationCommand": "gh attestation verify codetruss-cli-0.2.62.tgz --repo CodeTruss/codetruss-cli" + "releaseUrl": "https://github.com/CodeTruss/codetruss-cli/releases/tag/v0.2.63", + "attestationCommand": "gh attestation verify codetruss-cli-0.2.63.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 49aad24..feff3f5 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:4db60289-e4d1-5336-82e9-d4493a35b1d1", + "serialNumber": "urn:uuid:5587fc86-bda2-5368-8379-3f323f53b318", "specVersion": "1.6", "version": 1, "metadata": { "component": { "type": "application", - "bom-ref": "pkg:npm/%40codetruss/cli@0.2.62", + "bom-ref": "pkg:npm/%40codetruss/cli@0.2.63", "name": "@codetruss/cli", - "version": "0.2.62", + "version": "0.2.63", "description": "Local-first scope, quality, and verification receipts for coding agents", "licenses": [ { @@ -18,7 +18,7 @@ } } ], - "purl": "pkg:npm/%40codetruss/cli@0.2.62" + "purl": "pkg:npm/%40codetruss/cli@0.2.63" }, "properties": [ { @@ -139,7 +139,7 @@ "dependsOn": [] }, { - "ref": "pkg:npm/%40codetruss/cli@0.2.62", + "ref": "pkg:npm/%40codetruss/cli@0.2.63", "dependsOn": [ "pkg:npm/%40codetruss/analyzer-engine@0.1.0", "pkg:npm/minimatch@10.2.6", diff --git a/public/downloads/codetruss-cli-latest.tgz b/public/downloads/codetruss-cli-latest.tgz index 41d7a26..7bdc2fb 100644 Binary files a/public/downloads/codetruss-cli-latest.tgz and b/public/downloads/codetruss-cli-latest.tgz differ diff --git a/public/downloads/codetruss-cli-latest.tgz.sha256 b/public/downloads/codetruss-cli-latest.tgz.sha256 index fd5296f..263eed6 100644 --- a/public/downloads/codetruss-cli-latest.tgz.sha256 +++ b/public/downloads/codetruss-cli-latest.tgz.sha256 @@ -1 +1 @@ -35ad8c84288014ced4a184e702480bd478310e9c990fe18498c0e1038388e8a9 codetruss-cli-latest.tgz +cc63fdbc491b2ef7ac579bd721488e4a876287930995a0b347b74024180be748 codetruss-cli-latest.tgz