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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/publish-npm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ on:
workflow_dispatch:
inputs:
tag:
description: Existing attested GitHub release tag, for example v0.2.13
description: Existing attested GitHub release tag, for example v0.2.14
required: true
type: string
confirm_publish:
Expand Down
15 changes: 14 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,17 @@
CodeTruss CLI follows semantic versioning. Release artifacts and their SHA-256
checksums are published at <https://codetruss.com/downloads/codetruss-cli-latest.json>.

## 0.2.13 — 2026-07-14
## 0.2.14 — 2026-07-14

- Give the deterministic CycloneDX SBOM a canonical UUIDv5 serial number and
enforce that identity in release verification, making the SBOM directly
compatible with GitHub artifact attestations without weakening reproducible
package bytes.
- Retain the complete v0.2.13 Windows long-path and installer hardening in a new
immutable candidate after GitHub rejected the prior candidate's otherwise
valid SBOM because it did not carry a top-level serial number.

## 0.2.13 — 2026-07-14 (unpublished)

- Enable Git for Windows long-path support command-locally for every
CodeTruss-owned Git process and generated hook entry point. Exact private
Expand All @@ -12,6 +22,9 @@ checksums are published at <https://codetruss.com/downloads/codetruss-cli-latest
- Preserve the complete v0.2.12 SBOM, authentication-network-contract, hook,
verifier-isolation, and local-evidence hardening in a new immutable release
after the Windows compatibility matrix rejected the prior candidate.
- Release publication retained this candidate without downloadable assets after
GitHub's SBOM attestation action required a top-level CycloneDX serial number.
v0.2.14 carries the deterministic identity fix.

## 0.2.12 — 2026-07-14 (unpublished)

Expand Down
8 changes: 4 additions & 4 deletions docs/RELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,17 @@ npm account, npm token, pre-existing npm package, or npm environment approval.
Update `release-reference.json` from the immutable website candidate. The
verifier rejects any archive, SBOM, or executable digest that differs from
that checked-in reference.
2. Create and push the matching annotated tag, for example `v0.2.13` for package
version `0.2.13`.
2. Create and push the matching annotated tag, for example `v0.2.14` for package
version `0.2.14`.
3. `.github/workflows/release.yml` installs the locked dependency graph, runs
typechecking and tests, builds the package, verifies a clean global install,
creates GitHub build-provenance and SBOM attestations, and finally creates the
GitHub release.
4. Verify the downloaded archive independently:

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

The release workflow intentionally has no npm environment, npm credentials, or
Expand Down
15 changes: 14 additions & 1 deletion packages/cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,17 @@
CodeTruss CLI follows semantic versioning. Release artifacts and their SHA-256
checksums are published at <https://codetruss.com/downloads/codetruss-cli-latest.json>.

## 0.2.13 — 2026-07-14
## 0.2.14 — 2026-07-14

- Give the deterministic CycloneDX SBOM a canonical UUIDv5 serial number and
enforce that identity in release verification, making the SBOM directly
compatible with GitHub artifact attestations without weakening reproducible
package bytes.
- Retain the complete v0.2.13 Windows long-path and installer hardening in a new
immutable candidate after GitHub rejected the prior candidate's otherwise
valid SBOM because it did not carry a top-level serial number.

## 0.2.13 — 2026-07-14 (unpublished)

- Enable Git for Windows long-path support command-locally for every
CodeTruss-owned Git process and generated hook entry point. Exact private
Expand All @@ -12,6 +22,9 @@ checksums are published at <https://codetruss.com/downloads/codetruss-cli-latest
- Preserve the complete v0.2.12 SBOM, authentication-network-contract, hook,
verifier-isolation, and local-evidence hardening in a new immutable release
after the Windows compatibility matrix rejected the prior candidate.
- Release publication retained this candidate without downloadable assets after
GitHub's SBOM attestation action required a top-level CycloneDX serial number.
v0.2.14 carries the deterministic identity fix.

## 0.2.12 — 2026-07-14 (unpublished)

Expand Down
2 changes: 1 addition & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@codetruss/cli",
"version": "0.2.13",
"version": "0.2.14",
"description": "Local-first scope, quality, and verification receipts for coding agents",
"license": "SEE LICENSE IN LICENSE",
"type": "module",
Expand Down
15 changes: 15 additions & 0 deletions packages/cli/scripts/generate-sbom.mjs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { createHash } from 'node:crypto'
import { createRequire } from 'node:module'
import { readFile, writeFile } from 'node:fs/promises'
import { dirname, join, resolve } from 'node:path'
Expand All @@ -6,6 +7,7 @@ import { fileURLToPath } from 'node:url'
const scriptDir = dirname(fileURLToPath(import.meta.url))
const packageDir = resolve(scriptDir, '..')
const require = createRequire(import.meta.url)
const UUID_URL_NAMESPACE = Buffer.from('6ba7b8119dad11d180b400c04fd430c8', 'hex')

export function npmPurl(name, version) {
const scoped = /^(@[^/]+)\/([^/]+)$/.exec(name)
Expand All @@ -15,6 +17,18 @@ export function npmPurl(name, version) {
return `pkg:npm/${packagePath}@${encodeURIComponent(version)}`
}

export function cycloneDxSerialNumber(name, version) {
const uuid = createHash('sha1')
.update(UUID_URL_NAMESPACE)
.update(`${name}@${version}`, 'utf8')
.digest()
.subarray(0, 16)
uuid[6] = (uuid[6] & 0x0f) | 0x50
uuid[8] = (uuid[8] & 0x3f) | 0x80
const hex = uuid.toString('hex')
return `urn:uuid:${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`
}

function licenseEntry(value) {
return value
? [{ license: /^[A-Za-z0-9-.+]+$/.test(value) ? { id: value } : { name: value } }]
Expand Down Expand Up @@ -80,6 +94,7 @@ export async function generateSbom() {
const bom = {
$schema: 'https://cyclonedx.org/schema/bom-1.6.schema.json',
bomFormat: 'CycloneDX',
serialNumber: cycloneDxSerialNumber(cli.name, cli.version),
specVersion: '1.6',
version: 1,
metadata: {
Expand Down
18 changes: 17 additions & 1 deletion packages/cli/scripts/test-deterministic-package.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { mkdir, mkdtemp, readFile, rm, symlink, utimes, writeFile } from 'node:f
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { buildDeterministicPackageArchive, deterministicGzip, PACKAGE_ARCHIVE_FILES } from './deterministic-package.mjs'
import { npmPurl } from './generate-sbom.mjs'
import { cycloneDxSerialNumber, npmPurl } from './generate-sbom.mjs'
import { decodeDeterministicGzip, verifyDeterministicPackageArchive } from './verify-deterministic-package.mjs'

function rejects(fn, pattern) {
Expand Down Expand Up @@ -32,6 +32,22 @@ assert.equal(
'pkg:npm/%40codetruss/analyzer-engine@0.1.0',
)
assert.equal(npmPurl('yaml', '2.8.1'), 'pkg:npm/yaml@2.8.1')
assert.equal(
cycloneDxSerialNumber('@codetruss/cli', '0.2.14'),
'urn:uuid:ba46f3f9-7de6-5197-8fe5-af77d139712c',
)
assert.equal(
cycloneDxSerialNumber('@codetruss/cli', '0.2.14'),
cycloneDxSerialNumber('@codetruss/cli', '0.2.14'),
)
assert.notEqual(
cycloneDxSerialNumber('@codetruss/cli', '0.2.14'),
cycloneDxSerialNumber('@codetruss/cli', '0.2.13'),
)
assert.match(
cycloneDxSerialNumber('@codetruss/cli', '0.2.14'),
/^urn:uuid:[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/,
)

for (const size of [0, 65_535, 65_536, 131_070]) {
const input = Buffer.alloc(size, size & 0xff)
Expand Down
14 changes: 13 additions & 1 deletion packages/cli/scripts/test-release-verifier.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ async function writeRelease(archivePackageDir = packageDir) {
const entries = verifyDeterministicPackageArchive(archive)
const bundle = entries.get('package/dist/cli.cjs')
if (!bundle) throw new Error('fixture archive does not contain the CLI executable')
const sbom = await readFile(join(packageDir, 'SBOM.cdx.json'))
const sbom = await readFile(join(archivePackageDir, 'SBOM.cdx.json'))
const archiveSha256 = digest(archive)
const sbomSha256 = digest(sbom)
await writeFile(join(releaseDir, sbomName), sbom)
Expand Down Expand Up @@ -105,6 +105,18 @@ try {
const sidecarPath = join(releaseDir, `codetruss-cli-${pkg.version}.tgz.sha256`)
await writeFile(sidecarPath, `${await readFile(sidecarPath, 'utf8')}untrusted trailing bytes\n`)
await assert.rejects(() => verify(), /not the canonical checksum/)

for (const file of PACKAGE_ARCHIVE_FILES) {
await copyFile(join(packageDir, file.source), join(tamperedPackageDir, file.source))
}
const noncanonicalSbom = JSON.parse(await readFile(join(tamperedPackageDir, 'SBOM.cdx.json'), 'utf8'))
delete noncanonicalSbom.serialNumber
await writeFile(join(tamperedPackageDir, 'SBOM.cdx.json'), `${JSON.stringify(noncanonicalSbom, null, 2)}\n`)
await writeRelease(tamperedPackageDir)
await assert.rejects(
() => verify(tamperedPackageDir),
/does not have the canonical CycloneDX identity/,
)
} finally {
await rm(scratch, { recursive: true, force: true })
}
Expand Down
7 changes: 7 additions & 0 deletions packages/cli/scripts/verify-release.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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 { cycloneDxSerialNumber } from './generate-sbom.mjs'
import { verifyDeterministicPackageArchive } from './verify-deterministic-package.mjs'

const scriptPath = fileURLToPath(import.meta.url)
Expand Down Expand Up @@ -94,6 +95,12 @@ export async function verifyRelease({
if (packagedSbom.metadata?.component?.name !== pkg.name || packagedSbom.metadata?.component?.version !== pkg.version) {
throw new Error(`${sbomName} does not identify ${pkg.name}@${pkg.version}`)
}
const expectedSerialNumber = cycloneDxSerialNumber(pkg.name, pkg.version)
if (packagedSbom.bomFormat !== 'CycloneDX'
|| packagedSbom.specVersion !== '1.6'
|| packagedSbom.serialNumber !== expectedSerialNumber) {
throw new Error(`${sbomName} does not have the canonical CycloneDX identity ${expectedSerialNumber}`)
}

return { version: pkg.version, sha256: archiveSha256 }
}
Expand Down
170 changes: 170 additions & 0 deletions public/downloads/codetruss-cli-0.2.14.sbom.cdx.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
{
"$schema": "https://cyclonedx.org/schema/bom-1.6.schema.json",
"bomFormat": "CycloneDX",
"serialNumber": "urn:uuid:ba46f3f9-7de6-5197-8fe5-af77d139712c",
"specVersion": "1.6",
"version": 1,
"metadata": {
"component": {
"type": "application",
"bom-ref": "pkg:npm/%40codetruss/cli@0.2.14",
"name": "@codetruss/cli",
"version": "0.2.14",
"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.14"
},
"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.14",
"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": []
}
]
}
Binary file added public/downloads/codetruss-cli-0.2.14.tgz
Binary file not shown.
1 change: 1 addition & 0 deletions public/downloads/codetruss-cli-0.2.14.tgz.sha256
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
7fd85c939a3224c0678f6541b52ba4dd7e40b68a8dfaffd5bd6c8e0c12425da1 codetruss-cli-0.2.14.tgz
Loading
Loading