UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

474 lines (473 loc) 20.5 kB
import { c as isRecord } from "./record-coerce-DItp3I4t.js"; import { r as truncateUtf16Safe } from "./utf16-slice-D_ngcYKd.js"; import { p as redactSensitiveText } from "./redact-BtvPPfTi.js"; import { t as createSubsystemLogger } from "./subsystem-Dy2tqXOS.js"; import { randomUUID } from "node:crypto"; import { Type } from "typebox"; import { Value } from "typebox/value"; import { isProxy } from "node:util/types"; //#region src/audit/execution-identity-ordering.ts function sortUniqueExecutionIdentityEntries(values, key) { return [...new Map(values.map((value) => [key(value), value])).values()].toSorted((a, b) => { const left = key(a); const right = key(b); return left < right ? -1 : left > right ? 1 : 0; }); } //#endregion //#region src/audit/execution-identity-spawn-admission.ts const EXECUTION_IDENTITY_SPAWN_ADMISSION_FACTS = Symbol("executionIdentitySpawnAdmissionFacts"); function ownDataDescriptor(value, key) { const descriptor = Object.getOwnPropertyDescriptor(value, key); if (!descriptor) return; if (!descriptor.enumerable || !("value" in descriptor)) throw new Error("execution identity spawn admission carrier is invalid"); return descriptor; } function copyBaseFacts(value) { const prototype = Object.getPrototypeOf(value); if (prototype !== Object.prototype && prototype !== null) throw new Error("execution identity spawn admission carrier is invalid"); const descriptors = Object.getOwnPropertyDescriptors(value); for (const key of Reflect.ownKeys(descriptors)) { const descriptor = descriptors[key]; if (!descriptor || !descriptor.enumerable || !("value" in descriptor) || typeof key !== "string" && (key !== EXECUTION_IDENTITY_SPAWN_ADMISSION_FACTS || typeof descriptor.value !== "string")) throw new Error("execution identity spawn admission carrier is invalid"); if (typeof key !== "string") delete descriptors[key]; } return Object.create(null, descriptors); } function validRef(value, maxLength) { return typeof value === "string" && value.length >= 1 && value.length <= maxLength; } function validateEnvelopeExtension(lineage, missingEvidence) { if (!Array.isArray(missingEvidence) || missingEvidence.length > 16 || !missingEvidence.every((item) => validRef(item, 256))) throw new Error("execution identity spawn missing-evidence facts are invalid"); if (lineage === void 0 || lineage === null) return { missingEvidence }; if (!isRecord(lineage) || lineage.parentContextId !== void 0 && !validRef(lineage.parentContextId, 256) || lineage.parentExecutionId !== void 0 && !validRef(lineage.parentExecutionId, 256) || lineage.parentRunId !== void 0 && !validRef(lineage.parentRunId, 256) || !validRef(lineage.parentAgentId, 256) || lineage.relation !== "sessions_spawn" || !validRef(lineage.rawRequesterRef, 4096) || !validRef(lineage.rawControllerRef, 4096) || !Number.isSafeInteger(lineage.depth) || typeof lineage.depth !== "number" || lineage.depth < 1 || lineage.depth > 64 || !Array.isArray(lineage.localPolicyRefs) || lineage.localPolicyRefs.length > 16 || !lineage.localPolicyRefs.every((item) => validRef(item, 4096)) || !Array.isArray(lineage.targetPolicyRefs) || lineage.targetPolicyRefs.length > 16 || !lineage.targetPolicyRefs.every((item) => validRef(item, 4096))) throw new Error("execution identity spawn lineage facts are invalid"); return { lineage: { ...lineage.parentContextId !== void 0 ? { parentContextId: lineage.parentContextId } : {}, ...lineage.parentExecutionId !== void 0 ? { parentExecutionId: lineage.parentExecutionId } : {}, ...lineage.parentRunId !== void 0 ? { parentRunId: lineage.parentRunId } : {}, parentAgentId: lineage.parentAgentId, relation: lineage.relation, rawRequesterRef: lineage.rawRequesterRef, rawControllerRef: lineage.rawControllerRef, depth: lineage.depth, localPolicyRefs: lineage.localPolicyRefs, targetPolicyRefs: lineage.targetPolicyRefs }, missingEvidence }; } function uniqueSorted(values) { return [...new Set(values)].toSorted(); } function executionIdentitySpawnAdmission(operationInput) { const { operation, value } = operationInput; if (operation === "serialize") { const extension = validateEnvelopeExtension(value, operationInput.extra); return JSON.stringify([extension.lineage ?? null, extension.missingEvidence]); } if (operation === "parse") { if (typeof value !== "string") throw new Error("execution identity spawn admission facts are invalid"); const parsed = JSON.parse(value); if (!Array.isArray(parsed) || parsed.length !== 2) throw new Error("execution identity spawn admission facts are invalid"); const extension = validateEnvelopeExtension(parsed[0], parsed[1]); return [extension.lineage, extension.missingEvidence]; } if (!isRecord(value)) throw new Error("execution identity spawn admission carrier is invalid"); if (operation === "attach") { if (typeof operationInput.extra !== "string") return value; const carried = Object.create(Object.getPrototypeOf(value), Object.getOwnPropertyDescriptors(value)); Object.defineProperty(carried, EXECUTION_IDENTITY_SPAWN_ADMISSION_FACTS, { configurable: true, enumerable: true, value: operationInput.extra, writable: true }); return carried; } if (operation === "base-facts") return copyBaseFacts(value); if (operation === "extend-envelope") { const [lineage, missingEvidence] = executionIdentitySpawnAdmission({ operation: "parse", value: typeof operationInput.extra === "string" ? operationInput.extra : JSON.stringify([null, []]) }); const normalizedLineage = lineage ? { ...lineage, localPolicyRefs: uniqueSorted(lineage.localPolicyRefs), targetPolicyRefs: uniqueSorted(lineage.targetPolicyRefs) } : void 0; return { ...value, ...normalizedLineage ? { lineage: normalizedLineage } : {}, missingEvidence: uniqueSorted(missingEvidence) }; } if (operation === "base-envelope") { const baseEnvelope = copyBaseFacts(value); const { lineage, missingEvidence } = baseEnvelope; validateEnvelopeExtension(lineage, missingEvidence); delete baseEnvelope.lineage; delete baseEnvelope.missingEvidence; return baseEnvelope; } const attached = ownDataDescriptor(value, EXECUTION_IDENTITY_SPAWN_ADMISSION_FACTS)?.value; if (attached !== void 0) { if (typeof attached !== "string") throw new Error("execution identity spawn admission carrier is invalid"); return attached; } const missingEvidence = ownDataDescriptor(value, "missingEvidence")?.value; if (!Array.isArray(missingEvidence)) return; const lineage = ownDataDescriptor(value, "lineage")?.value; const extension = validateEnvelopeExtension(lineage, missingEvidence); return JSON.stringify([extension.lineage ?? null, extension.missingEvidence]); } //#endregion //#region src/audit/execution-identity-admission.ts /** Bounded execution-identity facts captured at authoritative run admission. */ const EXECUTION_IDENTITY_ADMISSION_MAX_BYTES = 16384; const EXECUTION_IDENTITY_ADMISSION_MAX_ITEMS = 16; const RAW_REF_MAX_LENGTH = 4096; const PROCESS_RUNTIME_INSTANCE_ID = randomUUID(); const log = createSubsystemLogger("audit/events"); const boundedRef = () => Type.String({ minLength: 1, maxLength: 256 }); const rawRef = () => Type.String({ minLength: 1, maxLength: RAW_REF_MAX_LENGTH }); const evidenceState = () => Type.Union([ Type.Literal("present"), Type.Literal("absent"), Type.Literal("unknown"), Type.Literal("unsupported") ]); const closedObject = (properties) => Type.Object(properties, { additionalProperties: false }); const ingressKind = () => Type.Union([ Type.Literal("local-cli"), Type.Literal("gateway-client"), Type.Literal("channel"), Type.Literal("api"), Type.Literal("schedule"), Type.Literal("webhook"), Type.Literal("task"), Type.Literal("subagent"), Type.Literal("acp"), Type.Literal("worker"), Type.Literal("plugin"), Type.Literal("recovery"), Type.Literal("system") ]); const runtimeKind = () => Type.Union([ Type.Literal("gateway"), Type.Literal("embedded"), Type.Literal("worker"), Type.Literal("plugin-harness"), Type.Literal("acp") ]); const admissionGrant = () => closedObject({ rawGrantRef: rawRef(), state: evidenceState() }); const admissionGrants = () => Type.Array(admissionGrant(), { maxItems: EXECUTION_IDENTITY_ADMISSION_MAX_ITEMS }); const admissionAssurance = () => Type.Array(closedObject({ kind: Type.Union([ Type.Literal("durable-profile"), Type.Literal("trusted-proxy"), Type.Literal("tailscale-whois"), Type.Literal("device-proof"), Type.Literal("channel-admission"), Type.Literal("local-process"), Type.Literal("spawn-lineage"), Type.Literal("worker-admission"), Type.Literal("runtime-binding"), Type.Literal("other") ]), rawEvidenceRef: rawRef(), strength: Type.Union([ Type.Literal("self-asserted"), Type.Literal("boundary-verified"), Type.Literal("cryptographic") ]) }), { maxItems: EXECUTION_IDENTITY_ADMISSION_MAX_ITEMS }); const ExecutionIdentityAdmissionInvokerSchema = Type.Union([closedObject({ state: Type.Literal("present"), kind: Type.Union([ Type.Literal("person"), Type.Literal("agent"), Type.Literal("service"), Type.Literal("schedule"), Type.Literal("webhook"), Type.Literal("system"), Type.Literal("local-account"), Type.Literal("runtime") ]), rawPrincipalRef: rawRef(), displayLabel: Type.Optional(Type.String({ maxLength: 128 })) }), closedObject({ state: Type.Literal("unknown") })]); const ExecutionIdentityAdmissionEnvelopeSchema = closedObject({ envelopeVersion: Type.Literal(1), contextId: boundedRef(), executionId: boundedRef(), runId: boundedRef(), createdAt: Type.Integer({ minimum: 0 }), runtimeInstanceId: rawRef(), agentId: boundedRef(), ingress: closedObject({ kind: ingressKind(), boundary: boundedRef(), state: evidenceState(), rawSourceRef: Type.Optional(rawRef()) }), runtime: closedObject({ kind: runtimeKind() }), invoker: Type.Optional(ExecutionIdentityAdmissionInvokerSchema), applicableGrants: admissionGrants(), assurance: admissionAssurance() }); const ExecutionIdentityAdmissionFactsSchema = closedObject({ runId: boundedRef(), agentId: boundedRef(), ingress: closedObject({ kind: ingressKind(), boundary: boundedRef(), state: Type.Optional(evidenceState()), rawSourceRef: Type.Optional(rawRef()) }), runtime: closedObject({ kind: runtimeKind() }), invoker: Type.Optional(ExecutionIdentityAdmissionInvokerSchema), applicableGrants: Type.Optional(admissionGrants()), assurance: Type.Optional(admissionAssurance()) }); const ExecutionIdentityAdmissionTokenSchema = closedObject({ tokenVersion: Type.Literal(1), contextId: boundedRef(), executionId: boundedRef(), runId: boundedRef(), createdAt: Type.Integer({ minimum: 0 }) }); let admissionSink; let admissionFailureWarned = false; function freezeEnvelope(value, seen = /* @__PURE__ */ new WeakSet()) { if (!value || typeof value !== "object" || seen.has(value)) return value; seen.add(value); for (const nested of Object.values(value)) freezeEnvelope(nested, seen); return Object.freeze(value); } function copyOwnedData(value, ancestors = /* @__PURE__ */ new WeakSet()) { if (value === null || [ "string", "number", "boolean" ].includes(typeof value)) return value; if (typeof value !== "object" || isProxy(value)) throw new Error("execution identity admission data must be clone-safe plain data"); if (ancestors.has(value)) throw new Error("execution identity admission data must be clone-safe plain data"); ancestors.add(value); try { const prototype = Object.getPrototypeOf(value); const keys = Reflect.ownKeys(value); const array = Array.isArray(value); if (Array.isArray(value)) { const lengthDescriptor = Object.getOwnPropertyDescriptor(value, "length"); if (prototype !== Array.prototype || !lengthDescriptor || !("value" in lengthDescriptor) || typeof lengthDescriptor.value !== "number" || keys.length !== lengthDescriptor.value + 1 || keys.at(-1) !== "length") throw new Error("execution identity admission data must be clone-safe plain data"); } else if (prototype !== Object.prototype && prototype !== null) throw new Error("execution identity admission data must be clone-safe plain data"); const copy = array ? [] : Object.create(null); for (const [index, key] of keys.entries()) { if (key === "length" && array) continue; if (typeof key !== "string" || array && key !== String(index)) throw new Error("execution identity admission data must be clone-safe plain data"); const descriptor = Object.getOwnPropertyDescriptor(value, key); if (!descriptor?.enumerable || !("value" in descriptor)) throw new Error("execution identity admission data must be clone-safe plain data"); Object.defineProperty(copy, key, { configurable: true, enumerable: true, value: copyOwnedData(descriptor.value, ancestors), writable: true }); } return copy; } finally { ancestors.delete(value); } } function validateEnvelope(value) { const owned = copyOwnedData(value); const baseEnvelope = executionIdentitySpawnAdmission({ operation: "base-envelope", value: owned }); if (!Value.Check(ExecutionIdentityAdmissionEnvelopeSchema, baseEnvelope) || typeof baseEnvelope.createdAt !== "number" || !Number.isSafeInteger(baseEnvelope.createdAt)) throw new Error("execution identity admission envelope violates its bounded contract"); if (!owned || typeof owned !== "object" || Array.isArray(owned)) throw new Error("execution identity admission envelope violates its bounded contract"); const encoded = JSON.stringify(owned); if (Buffer.byteLength(encoded, "utf8") > EXECUTION_IDENTITY_ADMISSION_MAX_BYTES) throw new Error("execution identity admission envelope exceeds 16 KiB"); return owned; } function validateFacts(value) { const owned = copyOwnedData(executionIdentitySpawnAdmission({ operation: "base-facts", value })); if (!Value.Check(ExecutionIdentityAdmissionFactsSchema, owned)) throw new Error("execution identity admission facts violate their bounded contract"); return executionIdentitySpawnAdmission({ operation: "attach", value: owned, extra: executionIdentitySpawnAdmission({ operation: "read", value }) }); } function validateToken(value) { const owned = copyOwnedData(value); if (!Value.Check(ExecutionIdentityAdmissionTokenSchema, owned) || !Number.isSafeInteger(owned.createdAt)) throw new Error("execution identity admission token violates its bounded contract"); return owned; } /** Allocate the immutable correlation owned by one outer admitted turn. */ function createExecutionIdentityAdmissionToken(runId, options = {}) { return freezeEnvelope(validateToken({ tokenVersion: 1, contextId: options.contextId ?? randomUUID(), executionId: options.executionId ?? randomUUID(), runId, createdAt: options.now ?? Date.now() })); } function parseExecutionIdentityAdmissionToken(value) { return freezeEnvelope(validateToken(value)); } function redactDisplayLabel(value) { return truncateUtf16Safe(redactSensitiveText(redactSensitiveText(value, { mode: "tools" }), { mode: "tools" }), 128); } /** Capture owned admission facts without touching filesystem or database state. */ function captureExecutionIdentityAdmissionEnvelope(facts, options) { const ownedToken = validateToken(options.token); if (ownedToken.runId !== facts.runId) throw new Error("execution identity admission token disagrees with the admitted run"); const runtimeInstanceId = options.runtimeInstanceId ?? PROCESS_RUNTIME_INSTANCE_ID; const assurance = facts.assurance ?? [{ kind: "runtime-binding", rawEvidenceRef: runtimeInstanceId, strength: "boundary-verified" }]; const serializedSpawnFacts = executionIdentitySpawnAdmission({ operation: "read", value: facts }); return freezeEnvelope(validateEnvelope(executionIdentitySpawnAdmission({ operation: "extend-envelope", value: { envelopeVersion: 1, contextId: ownedToken.contextId, executionId: ownedToken.executionId, runId: ownedToken.runId, createdAt: ownedToken.createdAt, runtimeInstanceId, agentId: facts.agentId, ingress: { ...facts.ingress, state: facts.ingress.state ?? "present" }, runtime: { ...facts.runtime }, ...facts.invoker?.state === "present" ? { invoker: { state: "present", kind: facts.invoker.kind, rawPrincipalRef: facts.invoker.rawPrincipalRef, ...facts.invoker.displayLabel !== void 0 ? { displayLabel: redactDisplayLabel(facts.invoker.displayLabel) } : {} } } : facts.invoker?.state === "unknown" ? { invoker: { state: "unknown" } } : {}, applicableGrants: sortUniqueExecutionIdentityEntries(facts.applicableGrants ?? [], (grant) => `${grant.rawGrantRef}\0${grant.state}`).map((grant) => ({ rawGrantRef: grant.rawGrantRef, state: grant.state })), assurance: sortUniqueExecutionIdentityEntries(assurance, (item) => `${item.kind}\0${item.rawEvidenceRef}\0${item.strength}`).map((item) => ({ kind: item.kind, rawEvidenceRef: item.rawEvidenceRef, strength: item.strength })) }, extra: serializedSpawnFacts }))); } /** Revalidate a structured-cloned queue payload before any persistence work. */ function parseExecutionIdentityAdmissionEnvelope(value) { const envelope = validateEnvelope(value); const spawnFacts = executionIdentitySpawnAdmission({ operation: "read", value: envelope }); if (!spawnFacts) throw new Error("execution identity admission envelope is missing spawn evidence state"); const parsed = captureExecutionIdentityAdmissionEnvelope(executionIdentitySpawnAdmission({ operation: "attach", value: envelope, extra: spawnFacts }), { token: createExecutionIdentityAdmissionToken(envelope.runId, { contextId: envelope.contextId, executionId: envelope.executionId, now: envelope.createdAt }), runtimeInstanceId: envelope.runtimeInstanceId }); if (JSON.stringify(parsed) !== JSON.stringify(envelope)) throw new Error("execution identity admission envelope is not canonical"); return parsed; } /** Revalidate either bounded queue payload before schema, key, or database work. */ function parseExecutionIdentityAdmissionWork(value) { const owned = copyOwnedData(value); if (!owned || typeof owned !== "object") throw new Error("execution identity admission work violates its bounded contract"); const work = owned; if (work.kind === "capture") return freezeEnvelope({ kind: "capture", envelope: parseExecutionIdentityAdmissionEnvelope(work.envelope) }); if (work.kind === "retry-reference") return freezeEnvelope({ kind: "retry-reference", token: parseExecutionIdentityAdmissionToken(work.token) }); throw new Error("execution identity admission work violates its bounded contract"); } /** Install the current process lifecycle's writer without creating a second queue. */ function configureExecutionIdentityAdmissionSink(sink) { admissionSink = sink; return () => { if (admissionSink === sink) admissionSink = void 0; }; } function hasExecutionIdentityAdmissionSink() { return admissionSink !== void 0; } /** * Capture and enqueue evidence. The returned ID is only a candidate until async persistence wins. */ function enqueueExecutionIdentityContextAtAdmission(facts, options) { if (!options.enabled) return; try { const ownedFacts = validateFacts(facts); const token = validateToken(options.token ?? createExecutionIdentityAdmissionToken(ownedFacts.runId, { contextId: options.contextId, executionId: options.executionId, now: options.now })); const work = options.retryOnly ? { kind: "retry-reference", token } : { kind: "capture", envelope: captureExecutionIdentityAdmissionEnvelope(ownedFacts, { token, runtimeInstanceId: options.runtimeInstanceId }) }; if (!admissionSink) throw new Error("audit writer unavailable"); return { candidateContextId: token.contextId, candidateExecutionId: token.executionId, accepted: admissionSink(work) }; } catch { if (!admissionFailureWarned) { admissionFailureWarned = true; log.warn("audit execution identity admission evidence was not queued; continuing without it"); } return; } } //#endregion export { parseExecutionIdentityAdmissionEnvelope as a, executionIdentitySpawnAdmission as c, hasExecutionIdentityAdmissionSink as i, sortUniqueExecutionIdentityEntries as l, createExecutionIdentityAdmissionToken as n, parseExecutionIdentityAdmissionToken as o, enqueueExecutionIdentityContextAtAdmission as r, parseExecutionIdentityAdmissionWork as s, configureExecutionIdentityAdmissionSink as t };