UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

96 lines (95 loc) 4.32 kB
import { i as resolveGlobalSingleton } from "./global-singleton-Dc_stLtU.js"; import { i as registerSecretValueForRedaction } from "./secret-redaction-registry-BOLC6DkF.js"; import { createCipheriv, createDecipheriv, createHash, createHmac, randomBytes } from "node:crypto"; //#region src/secrets/sentinel.ts const SECRET_SENTINEL_PREFIX = "oc-sent-v2."; const SECRET_SENTINEL_SUFFIX = ".end"; const SECRET_SENTINEL_SOURCE = "oc-sent-v2\\.[A-Za-z0-9_-]+\\.end"; const SECRET_SENTINEL_CIPHER = "aes-256-gcm"; const SECRET_SENTINEL_NONCE_BYTES = 12; const SECRET_SENTINEL_SCOPE_BYTES = 8; const SECRET_SENTINEL_HEADER_BYTES = 36; const SECRET_SENTINEL_MAX_LENGTH = 11 + Math.ceil(262288 / 3) + 4; const SECRET_SENTINEL_PATTERN = new RegExp(SECRET_SENTINEL_SOURCE, "g"); const secretSentinelKeys = resolveGlobalSingleton(Symbol.for("openclaw.secretSentinel.keys"), () => ({ keys: randomBytes(64) })).keys; const secretSentinelCipherKey = secretSentinelKeys.subarray(0, 32); const secretSentinelNonceKey = secretSentinelKeys.subarray(32); function secretSentinelsEnabled(env = process.env) { const configured = env.OPENCLAW_SECRET_SENTINELS?.trim().toLowerCase(); return configured !== "off" && configured !== "0" && configured !== "false"; } function looksLikeSecretSentinel(value) { return new RegExp(`^${SECRET_SENTINEL_SOURCE}$`).test(value); } function containsSecretSentinel(value) { return value.includes(SECRET_SENTINEL_PREFIX); } function secretSentinelScope(label) { return createHash("sha256").update(label).digest().subarray(0, SECRET_SENTINEL_SCOPE_BYTES); } /** Masks provider auth unless its compatibility switch explicitly requests plaintext. */ function mintSecretSentinel(value, meta) { if (!secretSentinelsEnabled()) { registerSecretValueForRedaction(value); return value; } return sealSecretSentinel(value, meta); } /** Always seals protected egress values, independently of provider compatibility settings. */ function sealSecretSentinel(value, meta) { registerSecretValueForRedaction(value); const scope = secretSentinelScope(meta.label); const nonce = createHmac("sha256", secretSentinelNonceKey).update(scope).update(value).digest().subarray(0, SECRET_SENTINEL_NONCE_BYTES); const cipher = createCipheriv(SECRET_SENTINEL_CIPHER, secretSentinelCipherKey, nonce); cipher.setAAD(scope); const ciphertext = Buffer.concat([cipher.update(value, "utf8"), cipher.final()]); const sealed = Buffer.concat([ scope, nonce, cipher.getAuthTag(), ciphertext ]); return `${SECRET_SENTINEL_PREFIX}${sealed.toString("base64url")}${SECRET_SENTINEL_SUFFIX}`; } /** Opens a process-local sentinel and rejects malformed or tampered values. */ function resolveSecretSentinel(sentinel) { if (!looksLikeSecretSentinel(sentinel)) return; try { const encoded = sentinel.slice(11, -4); const sealed = Buffer.from(encoded, "base64url"); if (sealed.length < SECRET_SENTINEL_HEADER_BYTES) return; const scope = sealed.subarray(0, SECRET_SENTINEL_SCOPE_BYTES); const nonce = sealed.subarray(SECRET_SENTINEL_SCOPE_BYTES, 20); const tag = sealed.subarray(20, 36); const ciphertext = sealed.subarray(SECRET_SENTINEL_HEADER_BYTES); const decipher = createDecipheriv(SECRET_SENTINEL_CIPHER, secretSentinelCipherKey, nonce); decipher.setAAD(scope); decipher.setAuthTag(tag); const value = Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8"); registerSecretValueForRedaction(value); return value; } catch { return; } } /** Swaps every known sentinel substring and reports unknown sentinel-shaped values. */ function swapSecretSentinelsInText(text) { if (!containsSecretSentinel(text)) return { text, unknown: [] }; const unknown = /* @__PURE__ */ new Set(); return { text: text.replace(new RegExp(SECRET_SENTINEL_SOURCE, "g"), (sentinel) => { const value = resolveSecretSentinel(sentinel); if (value === void 0) { unknown.add(sentinel); return sentinel; } return value; }), unknown: [...unknown] }; } //#endregion export { containsSecretSentinel as a, resolveSecretSentinel as c, SECRET_SENTINEL_SUFFIX as i, sealSecretSentinel as l, SECRET_SENTINEL_PATTERN as n, looksLikeSecretSentinel as o, SECRET_SENTINEL_PREFIX as r, mintSecretSentinel as s, SECRET_SENTINEL_MAX_LENGTH as t, swapSecretSentinelsInText as u };