UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

88 lines (87 loc) 3.33 kB
import { t as escapeRegExp } from "./regexp-BZyMFTlj.js"; import { t as pruneMapToMaxSize } from "./map-size-CNcWiFKu.js"; //#region src/logging/secret-redaction-registry.ts const MIN_SECRET_VALUE_LENGTH = 6; const MAX_SECRET_VALUES = 512; const registeredValues = /* @__PURE__ */ new Map(); let compiledMatcher; let firstChars; function invalidateMatcher() { firstChars = void 0; compiledMatcher = void 0; } function registerOneSecretValue(value) { if (registeredValues.delete(value)) { registeredValues.set(value, true); return; } registeredValues.set(value, true); pruneMapToMaxSize(registeredValues, MAX_SECRET_VALUES); invalidateMatcher(); } /** Registers one resolved secret for exact-value log redaction. */ function registerSecretValueForRedaction(value) { if (value.length < MIN_SECRET_VALUE_LENGTH) return; const encoded = encodeURIComponent(value); if (encoded !== value) registerOneSecretValue(encoded); const jsonEscaped = JSON.stringify(value).slice(1, -1); if (jsonEscaped !== value) registerOneSecretValue(jsonEscaped); registerOneSecretValue(value); } /** Returns whether a value has SecretRef provenance in the process registry. */ function isSecretValueRegisteredForRedaction(value) { return registeredValues.has(value); } function hasRegisteredSecretValuesForRedaction() { return registeredValues.size > 0; } /** Replaces registered exact values while preserving the caller's mask convention. */ function redactRegisteredSecretValues(text, mask) { if (!text || registeredValues.size === 0) return text; let couldMatch = false; firstChars ??= new Set([...registeredValues.keys()].map((value) => value.charAt(0))); for (const firstChar of firstChars) if (text.includes(firstChar)) { couldMatch = true; break; } if (!couldMatch) return text; if (!compiledMatcher) { const buckets = /* @__PURE__ */ new Map(); for (const value of [...registeredValues.keys()].toSorted((left, right) => right.length - left.length)) { const prefix = value.slice(0, MIN_SECRET_VALUE_LENGTH); const bucket = buckets.get(prefix); if (bucket) bucket.push(value); else buckets.set(prefix, [value]); } compiledMatcher = { prefixes: new RegExp([...buckets.keys()].map(escapeRegExp).join("|"), "g"), buckets }; } const { prefixes, buckets } = compiledMatcher; const matches = []; prefixes.lastIndex = 0; for (let match = prefixes.exec(text); match; match = prefixes.exec(text)) { const index = match.index; const value = buckets.get(match[0])?.find((candidate) => text.startsWith(candidate, index)); if (value !== void 0) matches.push({ index, value }); prefixes.lastIndex = index + (value?.length ?? 1); } let result = ""; let cursor = 0; for (const match of matches) { result += `${text.slice(cursor, match.index)}${mask(match.value)}`; cursor = match.index + match.value.length; } return result + text.slice(cursor); } function resetSecretRedactionRegistryForTest() { registeredValues.clear(); invalidateMatcher(); } if (process.env.VITEST || false) globalThis[Symbol.for("openclaw.secretRedactionRegistryTestApi")] = { resetSecretRedactionRegistryForTest }; //#endregion export { registerSecretValueForRedaction as i, isSecretValueRegisteredForRedaction as n, redactRegisteredSecretValues as r, hasRegisteredSecretValuesForRedaction as t };