Files
workinf_Blender_Wasm/web/app/src/testing/script-sandbox-recovery.ts
mes123456 380cbed4ff
Some checks failed
M6 deployable RC / quick (push) Has been cancelled
M6 deployable RC / chromium (push) Has been cancelled
M6 deployable RC / release (push) Has been cancelled
Checkpoint web parity through Chromium input tasks
2026-08-19 10:39:03 -04:00

57 lines
3.3 KiB
TypeScript

export const SCRIPT_SANDBOX_RECOVERY_SCHEMA = 1 as const;
export interface ScriptSandboxRecoveryAuditEntry {
sequence: 1 | 2;
requestId: string;
previousEntrySha256: string | null;
entrySha256: string;
sourceSha256: string;
manifestSha256: string;
}
export interface ScriptSandboxRecoveryReceipt {
schemaVersion: typeof SCRIPT_SANDBOX_RECOVERY_SCHEMA;
operation: "SCRIPT_SANDBOX_RECOVERY";
previousGeneration: number;
nextGeneration: number;
mainRevisionBefore: number;
mainRevisionAfter: number;
sourceSha256: string;
manifestSha256: string;
audit: {
entries: 2;
first: ScriptSandboxRecoveryAuditEntry;
second: ScriptSandboxRecoveryAuditEntry;
};
recovered: true;
execution: "DISABLED";
}
const SHA256 = /^[a-f0-9]{64}$/;
const REQUEST_ID = /^[-A-Za-z0-9:_./]{1,256}$/;
function assertDigest(value: string, name: string): void {
if (!SHA256.test(value)) throw new Error(`SCRIPT_SANDBOX_RECOVERY_INVALID: ${name} must be a SHA-256 digest`);
}
function assertEntry(entry: ScriptSandboxRecoveryAuditEntry, expectedSequence: 1 | 2, sourceSha256: string, manifestSha256: string): void {
if (entry.sequence !== expectedSequence || !REQUEST_ID.test(entry.requestId)) throw new Error("SCRIPT_SANDBOX_RECOVERY_INVALID: audit sequence or request id is invalid");
if (expectedSequence === 1 ? entry.previousEntrySha256 !== null : !entry.previousEntrySha256) throw new Error("SCRIPT_SANDBOX_RECOVERY_INVALID: audit previous hash is invalid");
assertDigest(entry.entrySha256, `audit[${expectedSequence}].entrySha256`);
if (entry.previousEntrySha256 !== null) assertDigest(entry.previousEntrySha256, `audit[${expectedSequence}].previousEntrySha256`);
if (entry.sourceSha256 !== sourceSha256 || entry.manifestSha256 !== manifestSha256) throw new Error("SCRIPT_SANDBOX_RECOVERY_INVALID: audit source or manifest hash drifted");
}
export function createScriptSandboxRecoveryReceipt(input: Omit<ScriptSandboxRecoveryReceipt, "schemaVersion" | "operation" | "recovered" | "execution">): ScriptSandboxRecoveryReceipt {
if (!Number.isSafeInteger(input.previousGeneration) || !Number.isSafeInteger(input.nextGeneration) || input.nextGeneration !== input.previousGeneration + 1) throw new Error("SCRIPT_SANDBOX_RECOVERY_INVALID: generation must advance once");
if (!Number.isSafeInteger(input.mainRevisionBefore) || input.mainRevisionBefore < 0 || input.mainRevisionAfter !== input.mainRevisionBefore) throw new Error("SCRIPT_SANDBOX_RECOVERY_INVALID: Main revision changed during recovery");
assertDigest(input.sourceSha256, "sourceSha256");
assertDigest(input.manifestSha256, "manifestSha256");
if (input.audit.entries !== 2) throw new Error("SCRIPT_SANDBOX_RECOVERY_INVALID: audit entry count is invalid");
assertEntry(input.audit.first, 1, input.sourceSha256, input.manifestSha256);
assertEntry(input.audit.second, 2, input.sourceSha256, input.manifestSha256);
if (input.audit.second.previousEntrySha256 !== input.audit.first.entrySha256) throw new Error("SCRIPT_SANDBOX_RECOVERY_INVALID: audit hash chain is not continuous");
if (input.audit.first.requestId === input.audit.second.requestId) throw new Error("SCRIPT_SANDBOX_RECOVERY_INVALID: audit request id is replayed");
return { schemaVersion: SCRIPT_SANDBOX_RECOVERY_SCHEMA, operation: "SCRIPT_SANDBOX_RECOVERY", ...input, recovered: true, execution: "DISABLED" };
}