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 apps/desktop/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@t3tools/desktop",
"version": "0.1.8",
"version": "0.1.9",
"private": true,
"type": "module",
"main": "dist-electron/main.cjs",
Expand Down
2 changes: 1 addition & 1 deletion apps/server/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "t3",
"version": "0.1.8",
"version": "0.1.9",
"private": true,
"license": "MIT",
"bin": {
Expand Down
2 changes: 1 addition & 1 deletion apps/web/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@t3tools/web",
"version": "0.1.8",
"version": "0.1.9",
"private": true,
"type": "module",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion packages/contracts/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@t3tools/contracts",
"version": "0.1.8",
"version": "0.1.9",
"private": true,
"files": [
"dist"
Expand Down
70 changes: 56 additions & 14 deletions scripts/desktop-release-evidence.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,39 @@ import * as NodePath from "node:path";

import { writeDesktopReleaseEvidence } from "./desktop-release-evidence.ts";

function runtimeProvenance(version = "0.1.9", platform = "darwin", arch = "arm64"): string {
const statement = {
_type: "https://in-toto.io/Statement/v1",
predicateType: "https://slsa.dev/provenance/v1",
predicate: {
buildDefinition: {
buildType: "https://sigma-code.dev/build-types/portable-cli",
externalParameters: { version, targetPlatform: platform, targetArch: arch },
},
},
};
return `${JSON.stringify({
payloadType: "application/vnd.in-toto+json",
payload: Buffer.from(`${JSON.stringify(statement, null, 2)}\n`, "utf8").toString("base64"),
signatures: [],
})}\n`;
}

it("writes checksum, runtime provenance, and unsigned preview status", async () => {
const root = await NodeFSP.mkdtemp(
NodePath.join(NodeOS.tmpdir(), "sigma-desktop-evidence-test-"),
);
const artifactPath = NodePath.join(root, "Sigma-Code-0.1.8-arm64.dmg");
const artifactPath = NodePath.join(root, "Sigma-Code-0.1.9-arm64.dmg");
const runtimeProvenancePath = NodePath.join(root, "agent-cli-darwin-arm64.provenance.json");
await NodeFSP.writeFile(artifactPath, "dmg fixture", "utf8");
await NodeFSP.writeFile(
runtimeProvenancePath,
'{"predicate":{"buildDefinition":{"externalParameters":{"version":"0.1.8","targetPlatform":"darwin","targetArch":"arm64"}}}}\n',
"utf8",
);
await NodeFSP.writeFile(runtimeProvenancePath, runtimeProvenance(), "utf8");

const result = await writeDesktopReleaseEvidence({
artifactPath,
runtimeProvenancePath,
platform: "darwin",
arch: "arm64",
version: "0.1.8",
version: "0.1.9",
signingMode: "unsigned",
codesign: "not-signed",
gatekeeper: "not-applicable",
Expand All @@ -42,22 +56,18 @@ it("rejects signed evidence unless every notarization gate passed", async () =>
const root = await NodeFSP.mkdtemp(
NodePath.join(NodeOS.tmpdir(), "sigma-desktop-evidence-signed-test-"),
);
const artifactPath = NodePath.join(root, "Sigma-Code-0.1.8-arm64.dmg");
const artifactPath = NodePath.join(root, "Sigma-Code-0.1.9-arm64.dmg");
const runtimeProvenancePath = NodePath.join(root, "runtime.json");
await NodeFSP.writeFile(artifactPath, "dmg fixture", "utf8");
await NodeFSP.writeFile(
runtimeProvenancePath,
'{"predicate":{"buildDefinition":{"externalParameters":{"version":"0.1.8","targetPlatform":"darwin","targetArch":"arm64"}}}}\n',
"utf8",
);
await NodeFSP.writeFile(runtimeProvenancePath, runtimeProvenance(), "utf8");
let failure: unknown;
try {
await writeDesktopReleaseEvidence({
artifactPath,
runtimeProvenancePath,
platform: "darwin",
arch: "arm64",
version: "0.1.8",
version: "0.1.9",
signingMode: "signed",
codesign: "pass",
gatekeeper: "pass",
Expand All @@ -69,3 +79,35 @@ it("rejects signed evidence unless every notarization gate passed", async () =>
assert.instanceOf(failure, Error);
assert.match((failure as Error).message, /stapler=pass/u);
});

it("rejects a raw provenance statement instead of treating it as a DSSE envelope", async () => {
const root = await NodeFSP.mkdtemp(
NodePath.join(NodeOS.tmpdir(), "sigma-desktop-evidence-envelope-test-"),
);
const artifactPath = NodePath.join(root, "Sigma-Code-0.1.9-arm64.dmg");
const runtimeProvenancePath = NodePath.join(root, "runtime.json");
await NodeFSP.writeFile(artifactPath, "dmg fixture", "utf8");
await NodeFSP.writeFile(
runtimeProvenancePath,
'{"predicate":{"buildDefinition":{"externalParameters":{"version":"0.1.9","targetPlatform":"darwin","targetArch":"arm64"}}}}\n',
"utf8",
);
let failure: unknown;
try {
await writeDesktopReleaseEvidence({
artifactPath,
runtimeProvenancePath,
platform: "darwin",
arch: "arm64",
version: "0.1.9",
signingMode: "unsigned",
codesign: "not-signed",
gatekeeper: "not-applicable",
stapler: "not-applicable",
});
} catch (error) {
failure = error;
}
assert.instanceOf(failure, Error);
assert.match((failure as Error).message, /DSSE envelope/u);
});
66 changes: 65 additions & 1 deletion scripts/desktop-release-evidence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@
// @effect-diagnostics nodeBuiltinImport:off

import * as NodeCrypto from "node:crypto";
import * as NodeBuffer from "node:buffer";
import * as NodeFSP from "node:fs/promises";
import * as NodePath from "node:path";

const APP_ID = "io.github.hututuqqq.sigmacode";
const MACOS_MINIMUM_SYSTEM_VERSION = "13.5";
const RUNTIME_PROVENANCE_PAYLOAD_TYPE = "application/vnd.in-toto+json";
const MAX_RUNTIME_PROVENANCE_PAYLOAD_BYTES = 1024 * 1024;

type Platform = "darwin" | "win32";
type Arch = "arm64" | "x64";
Expand Down Expand Up @@ -63,14 +66,75 @@ function validateOptions(options: DesktopReleaseEvidenceOptions): void {
}
}

function decodeRuntimeProvenanceStatement(value: unknown): Record<string, unknown> {
if (!value || typeof value !== "object" || Array.isArray(value)) {
throw new Error("Runtime provenance must be a DSSE envelope object.");
}
const envelope = value as Record<string, unknown>;
const keys = Object.keys(envelope).sort();
if (JSON.stringify(keys) !== JSON.stringify(["payload", "payloadType", "signatures"])) {
throw new Error("Runtime provenance DSSE envelope has unexpected fields.");
}
if (envelope.payloadType !== RUNTIME_PROVENANCE_PAYLOAD_TYPE) {
throw new Error("Runtime provenance uses an unsupported DSSE payload type.");
}
if (!Array.isArray(envelope.signatures)) {
throw new Error("Runtime provenance DSSE signatures must be an array.");
}
if (
typeof envelope.payload !== "string" ||
envelope.payload.length === 0 ||
!/^[A-Za-z0-9+/]+={0,2}$/u.test(envelope.payload) ||
envelope.payload.length > Math.ceil(MAX_RUNTIME_PROVENANCE_PAYLOAD_BYTES / 3) * 4
) {
throw new Error("Runtime provenance DSSE payload is not bounded canonical base64.");
}
const payload = NodeBuffer.Buffer.from(envelope.payload, "base64");
if (
payload.length === 0 ||
payload.length > MAX_RUNTIME_PROVENANCE_PAYLOAD_BYTES ||
payload.toString("base64") !== envelope.payload
) {
throw new Error("Runtime provenance DSSE payload is not bounded canonical base64.");
}
const payloadText = payload.toString("utf8");
if (!NodeBuffer.Buffer.from(payloadText, "utf8").equals(payload)) {
throw new Error("Runtime provenance DSSE payload is not valid UTF-8.");
}
let statement: unknown;
try {
statement = JSON.parse(payloadText);
} catch (error) {
throw new Error(
`Runtime provenance DSSE payload is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,
{ cause: error },
);
}
if (!statement || typeof statement !== "object" || Array.isArray(statement)) {
throw new Error("Runtime provenance DSSE payload must contain an object statement.");
}
const typed = statement as Record<string, unknown>;
const predicate = typed.predicate as
| { readonly buildDefinition?: { readonly buildType?: unknown } }
| undefined;
if (
typed._type !== "https://in-toto.io/Statement/v1" ||
typed.predicateType !== "https://slsa.dev/provenance/v1" ||
predicate?.buildDefinition?.buildType !== "https://sigma-code.dev/build-types/portable-cli"
) {
throw new Error("Runtime provenance DSSE payload is not a Sigma portable Runtime statement.");
}
return typed;
}

export async function writeDesktopReleaseEvidence(options: DesktopReleaseEvidenceOptions): Promise<{
readonly checksumPath: string;
readonly provenancePath: string;
readonly signingPath: string;
}> {
validateOptions(options);
const runtimeProvenanceRaw = await NodeFSP.readFile(options.runtimeProvenancePath, "utf8");
const runtimeProvenance = JSON.parse(runtimeProvenanceRaw) as Record<string, unknown>;
const runtimeProvenance = decodeRuntimeProvenanceStatement(JSON.parse(runtimeProvenanceRaw));
const runtimeParameters = (
runtimeProvenance.predicate as
| { readonly buildDefinition?: { readonly externalParameters?: unknown } }
Expand Down
Loading