import crypto from "node:crypto"; import type { SystemRunApprovalBinding, SystemRunApprovalPlan } from "./exec-approvals.js"; import { normalizeEnvVarKey } from "./host-env-security.js"; import { normalizeNonEmptyString, normalizeStringArray } from "./system-run-normalize.js"; type NormalizedSystemRunEnvEntry = [key: string, value: string]; export function normalizeSystemRunApprovalPlan(value: unknown): SystemRunApprovalPlan | null { if (!value || typeof value !== "object" || Array.isArray(value)) { return null; } const candidate = value as Record; const argv = normalizeStringArray(candidate.argv); if (argv.length === 0) { return null; } return { argv, cwd: normalizeNonEmptyString(candidate.cwd), rawCommand: normalizeNonEmptyString(candidate.rawCommand), agentId: normalizeNonEmptyString(candidate.agentId), sessionKey: normalizeNonEmptyString(candidate.sessionKey), }; } function normalizeSystemRunEnvEntries(env: unknown): NormalizedSystemRunEnvEntry[] { if (!env || typeof env !== "object" || Array.isArray(env)) { return []; } const entries: NormalizedSystemRunEnvEntry[] = []; for (const [rawKey, rawValue] of Object.entries(env as Record)) { if (typeof rawValue !== "string") { continue; } const key = normalizeEnvVarKey(rawKey, { portable: true }); if (!key) { continue; } entries.push([key, rawValue]); } entries.sort((a, b) => a[0].localeCompare(b[0])); return entries; } function hashSystemRunEnvEntries(entries: NormalizedSystemRunEnvEntry[]): string | null { if (entries.length === 0) { return null; } return crypto.createHash("sha256").update(JSON.stringify(entries)).digest("hex"); } export function buildSystemRunApprovalEnvBinding(env: unknown): { envHash: string | null; envKeys: string[]; } { const entries = normalizeSystemRunEnvEntries(env); return { envHash: hashSystemRunEnvEntries(entries), envKeys: entries.map(([key]) => key), }; } export function buildSystemRunApprovalBinding(params: { argv: unknown; cwd?: unknown; agentId?: unknown; sessionKey?: unknown; env?: unknown; }): { binding: SystemRunApprovalBinding; envKeys: string[] } { const envBinding = buildSystemRunApprovalEnvBinding(params.env); return { binding: { argv: normalizeStringArray(params.argv), cwd: normalizeNonEmptyString(params.cwd), agentId: normalizeNonEmptyString(params.agentId), sessionKey: normalizeNonEmptyString(params.sessionKey), envHash: envBinding.envHash, }, envKeys: envBinding.envKeys, }; } function argvMatches(expectedArgv: string[], actualArgv: string[]): boolean { if (expectedArgv.length === 0 || expectedArgv.length !== actualArgv.length) { return false; } for (let i = 0; i < expectedArgv.length; i += 1) { if (expectedArgv[i] !== actualArgv[i]) { return false; } } return true; } export type SystemRunApprovalMatchResult = | { ok: true } | { ok: false; code: "APPROVAL_REQUEST_MISMATCH" | "APPROVAL_ENV_BINDING_MISSING" | "APPROVAL_ENV_MISMATCH"; message: string; details?: Record; }; type SystemRunApprovalMismatch = Extract; const APPROVAL_REQUEST_MISMATCH_MESSAGE = "approval id does not match request"; function requestMismatch(details?: Record): SystemRunApprovalMatchResult { return { ok: false, code: "APPROVAL_REQUEST_MISMATCH", message: APPROVAL_REQUEST_MISMATCH_MESSAGE, details, }; } export function matchSystemRunApprovalEnvHash(params: { expectedEnvHash: string | null; actualEnvHash: string | null; actualEnvKeys: string[]; }): SystemRunApprovalMatchResult { if (!params.expectedEnvHash && !params.actualEnvHash) { return { ok: true }; } if (!params.expectedEnvHash && params.actualEnvHash) { return { ok: false, code: "APPROVAL_ENV_BINDING_MISSING", message: "approval id missing env binding for requested env overrides", details: { envKeys: params.actualEnvKeys }, }; } if (params.expectedEnvHash !== params.actualEnvHash) { return { ok: false, code: "APPROVAL_ENV_MISMATCH", message: "approval id env binding mismatch", details: { envKeys: params.actualEnvKeys, expectedEnvHash: params.expectedEnvHash, actualEnvHash: params.actualEnvHash, }, }; } return { ok: true }; } export function matchSystemRunApprovalBinding(params: { expected: SystemRunApprovalBinding; actual: SystemRunApprovalBinding; actualEnvKeys: string[]; }): SystemRunApprovalMatchResult { if (!argvMatches(params.expected.argv, params.actual.argv)) { return requestMismatch(); } if (params.expected.cwd !== params.actual.cwd) { return requestMismatch(); } if (params.expected.agentId !== params.actual.agentId) { return requestMismatch(); } if (params.expected.sessionKey !== params.actual.sessionKey) { return requestMismatch(); } return matchSystemRunApprovalEnvHash({ expectedEnvHash: params.expected.envHash, actualEnvHash: params.actual.envHash, actualEnvKeys: params.actualEnvKeys, }); } export function missingSystemRunApprovalBinding(params: { actualEnvKeys: string[]; }): SystemRunApprovalMatchResult { return requestMismatch({ envKeys: params.actualEnvKeys, }); } export function toSystemRunApprovalMismatchError(params: { runId: string; match: SystemRunApprovalMismatch; }): { ok: false; message: string; details: Record } { const details: Record = { code: params.match.code, runId: params.runId, }; if (params.match.details) { Object.assign(details, params.match.details); } return { ok: false, message: params.match.message, details, }; }