UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

133 lines (132 loc) 7.02 kB
import { i as registerSecretValueForRedaction } from "./secret-redaction-registry-BOLC6DkF.js"; import { n as sha256Base64Url } from "./crypto-digest-C4hqTb_e.js"; import { i as generateSecureToken } from "./secure-random-Ds4AFLgz.js"; import { n as WORKER_EXECUTION_CONTEXT_PROTOCOL_FEATURE } from "./worker-admission-C1l9t3yY.js"; import { t as safeEqualSecret } from "./secret-equal-DRsL8lKD.js"; import { n as sameWorkerProtocolFeatures, t as sameWorkerBuild } from "./worker-build-identity-D_c48Wx_.js"; import { x as serializeWorkerSessionTurnClaim } from "./placement-record-BOfTzlFO.js"; //#region src/gateway/worker-environments/credential.ts const WORKER_CREDENTIAL_TTL_MS = 6e5; const WORKER_CREDENTIAL_HASH_DOMAIN = "openclaw-worker-credential-v1\0"; const WORKER_TURN_CREDENTIAL_HASH_DOMAIN = "openclaw-worker-turn-credential-v1\0"; const WORKER_CREDENTIAL_BYTES = 32; /** Hash opaque worker credentials with their exact durable authority before persistence. */ function hashWorkerCredential(credential, claim) { if (!claim) return sha256Base64Url(`${WORKER_CREDENTIAL_HASH_DOMAIN}${credential}`); const binding = serializeWorkerSessionTurnClaim(claim); return sha256Base64Url(`${WORKER_TURN_CREDENTIAL_HASH_DOMAIN}${binding}\0${credential}`); } /** Generate one high-entropy credential. Plaintext is returned only to its delivery owner. */ function createWorkerCredentialMaterial(generateToken = generateSecureToken, claim) { const credential = generateToken(WORKER_CREDENTIAL_BYTES); registerSecretValueForRedaction(credential); return { credential, credentialHash: hashWorkerCredential(credential, claim) }; } //#endregion //#region src/gateway/worker-environments/admission.ts const STALE_WORKER_BUILD_REASON = "Worker build does not match the current Gateway build; redispatch the session so its worker can bootstrap the current build before retrying."; var StaleWorkerBuildError = class extends Error { constructor() { super(STALE_WORKER_BUILD_REASON); this.code = "invalid_state"; } }; /** True only for bundles that accept the exact admitted execution carrier. */ function supportsWorkerExecutionContextLaunch(handshake) { return handshake?.protocolFeatures.includes(WORKER_EXECUTION_CONTEXT_PROTOCOL_FEATURE) === true; } /** Admits only the exact build selected for this worker environment. */ function verifyWorkerAdmissionHandshake(handshake, expected) { return sameWorkerBuild(handshake, expected); } /** Validate an opaque credential and every server-owned worker admission binding. */ function admitWorkerConnection(params) { const { admission, store } = params; const turnClaim = params.turnClaim; const credentialHash = hashWorkerCredential(admission.credential, turnClaim); const credential = store.getCredential(admission.environmentId); if (!credential || !safeEqualSecret(credentialHash, credential.credentialHash)) return { ok: false, reason: store.findCredentialByHash(credentialHash) ? "environment-mismatch" : "invalid-credential" }; if (credential.environmentId !== admission.environmentId) return { ok: false, reason: "environment-mismatch" }; if (admission.sessionId !== null) { if (!turnClaim || turnClaim.owner.kind !== "worker" || turnClaim.sessionId !== admission.sessionId || turnClaim.runId !== admission.runId || turnClaim.owner.environmentId !== admission.environmentId || turnClaim.owner.ownerEpoch !== admission.ownerEpoch) return { ok: false, reason: "placement-mismatch" }; } else if (turnClaim || admission.runId !== null) return { ok: false, reason: "session-mismatch" }; if (params.nowMs >= credential.expiresAtMs && params.allowExpiredCredential !== true) return { ok: false, reason: "credential-expired" }; const environment = store.get(admission.environmentId); if (!environment || environment.state !== "ready" && environment.state !== "idle" && environment.state !== "attached" || environment.destroyRequestedAtMs !== null || !environment.bootstrapReceipt) return { ok: false, reason: "environment-unavailable" }; if (admission.handshake.bundleHash !== credential.bundleHash || admission.handshake.bundleHash !== environment.bootstrapReceipt.bundleHash || admission.handshake.bundleHash !== params.expectedBuild.bundleHash) return { ok: false, reason: "bundle-mismatch" }; if (admission.handshake.openclawVersion !== environment.bootstrapReceipt.openclawVersion || admission.handshake.openclawVersion !== params.expectedBuild.openclawVersion) return { ok: false, reason: "version-mismatch" }; if (admission.sessionId !== credential.sessionId) return { ok: false, reason: "session-mismatch" }; if (admission.sessionId === null !== (admission.runId === null)) return { ok: false, reason: "session-mismatch" }; if (admission.ownerEpoch !== credential.ownerEpoch || admission.ownerEpoch !== environment.ownerEpoch) return { ok: false, reason: "owner-epoch-mismatch" }; if (admission.rpcSetVersion !== credential.rpcSetVersion || credential.rpcSetVersion !== 1) return { ok: false, reason: "rpc-set-mismatch" }; if (!sameWorkerProtocolFeatures(admission.handshake.protocolFeatures, environment.bootstrapReceipt.protocolFeatures) || !sameWorkerProtocolFeatures(admission.handshake.protocolFeatures, params.expectedBuild.protocolFeatures)) return { ok: false, reason: "protocol-features-mismatch" }; return { ok: true, identity: { environmentId: environment.environmentId, credentialHash: credential.credentialHash, bundleHash: credential.bundleHash, sessionId: credential.sessionId, runId: admission.runId, turnClaim: turnClaim ?? null, ownerEpoch: credential.ownerEpoch, rpcSetVersion: credential.rpcSetVersion, protocolFeatures: [...environment.bootstrapReceipt.protocolFeatures], credentialExpiresAtMs: credential.expiresAtMs } }; } /** Revalidate live ownership on every worker RPC so rotation and expiry fence stale sockets. */ function validateWorkerConnectionIdentity(params) { const credential = params.store.getCredential(params.identity.environmentId); if (!credential || !safeEqualSecret(credential.credentialHash, params.identity.credentialHash)) return "credential-replaced"; if (params.nowMs >= credential.expiresAtMs) return "credential-expired"; const environment = params.store.get(params.identity.environmentId); if (!environment || environment.state !== "ready" && environment.state !== "idle" && environment.state !== "attached" || environment.destroyRequestedAtMs !== null) return "environment-unavailable"; if (environment.ownerEpoch !== params.identity.ownerEpoch || credential.ownerEpoch !== params.identity.ownerEpoch) return "owner-epoch-mismatch"; return null; } //#endregion export { validateWorkerConnectionIdentity as a, createWorkerCredentialMaterial as c, supportsWorkerExecutionContextLaunch as i, hashWorkerCredential as l, StaleWorkerBuildError as n, verifyWorkerAdmissionHandshake as o, admitWorkerConnection as r, WORKER_CREDENTIAL_TTL_MS as s, STALE_WORKER_BUILD_REASON as t };