openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
5,116 lines • 215 kB
JavaScript
import { D as isRecord, S as resolveGlobalSingleton, g as normalizeLowercaseStringOrEmpty, u as parseStrictPositiveInteger, y as normalizeOptionalString } from "./number-coercion-CmpLVzZ6.mjs";
import { c as resolveHomeRelativePath, d as resolveUserPath, f as tryProcessCwd, l as resolveRequiredHomeDir } from "./utils-BfTq4rUg.mjs";
import { createRequire } from "node:module";
import fsSync from "node:fs";
import path from "node:path";
import os from "node:os";
import crypto from "node:crypto";
import { canUseRootFileOpen, matchRootFileOpenFailure, openRootFileSync, readFileDescriptorBounded, readFileDescriptorBoundedSync, resolvePathViaExistingAncestorSync, resolveRootPathSync } from "@openclaw/fs-safe/advanced";
import { FsSafeError } from "@openclaw/fs-safe/errors";
import { AsyncLocalStorage } from "node:async_hooks";
import { hasNodeErrorCode as hasErrnoCode, isNodeError as isErrno, isPathInside as isPathInside$1 } from "@openclaw/fs-safe/path";
import { channel } from "node:diagnostics_channel";
import { performance } from "node:perf_hooks";
import { isMainThread, threadId } from "node:worker_threads";
//#region packages/net-policy/src/redact-sensitive-url.ts
const SENSITIVE_URL_QUERY_PARAM_NAMES = /* @__PURE__ */ new Set([
"token",
"key",
"api_key",
"apikey",
"secret",
"access_token",
"auth_token",
"password",
"pass",
"passwd",
"auth",
"jwt",
"session",
"id_token",
"code",
"client_secret",
"app_secret",
"hook_token",
"refresh_token",
"signature",
"x_amz_signature",
"x_amz_security_token",
"private_key",
"credential",
"authorization",
"sig",
"x_api_key",
"x_access_token",
"x_auth_token"
]);
const URL_QUERY_NAME_SEPARATOR_RE = /[\p{C}\p{Z}\u115F\u1160\u3164\uFFA0+]/gu;
const SUFFIXED_OR_SCOPED_TOKEN_QUERY_PARAM_RE = /(?:^|_)token(?:_[a-f0-9]{16,})?$/u;
const TELEGRAM_BOT_TOKEN_PATH_RE = /\/bot\d{6,}(?::|%3[aA])[A-Za-z0-9_-]{20,}(?=\/|$)/giu;
const MAX_NESTED_URL_REDACTION_DEPTH = 8;
const URL_SCHEME_RE = /(?:^|[^a-z\d+.-])[a-z][a-z\d+.-]{0,31}:/iu;
const SPECIAL_SCHEME_AUTHORITY_RE = /\b(?:https?|wss?|ftp):[\\/]{0,2}[^\\/?#\s]*/giu;
const SPECIAL_SCHEME_SPILLED_USERINFO_RE = /\b(?:https?|wss?|ftp):[\\/]{0,2}[^\s]*@[^\\/?#\s]*/giu;
const PROTOCOL_RELATIVE_AUTHORITY_RE = /[\\/]{2,}[^\\/?#\s]*/gu;
function redactSensitiveUrlPath(value) {
return value.replace(TELEGRAM_BOT_TOKEN_PATH_RE, "/bot***");
}
function normalizeUrlQueryParamName(name) {
let current = name.replace(URL_QUERY_NAME_SEPARATOR_RE, "");
for (let depth = 0; depth <= MAX_NESTED_URL_REDACTION_DEPTH; depth += 1) {
let decoded;
try {
decoded = decodeURIComponent(current).replace(URL_QUERY_NAME_SEPARATOR_RE, "");
} catch {
break;
}
if (decoded === current) return {
value: normalizeLowercaseStringOrEmpty(current).replaceAll("-", "_"),
unresolvedEncoding: false
};
current = decoded;
}
return {
value: normalizeLowercaseStringOrEmpty(current).replaceAll("-", "_"),
unresolvedEncoding: current.includes("%")
};
}
function looksLikeNestedUrlValue(value) {
if (URL_SCHEME_RE.test(value)) return true;
const forwardAuthorityIndex = value.indexOf("//");
const backwardAuthorityIndex = value.indexOf("\\\\");
const authorityIndex = forwardAuthorityIndex < 0 ? backwardAuthorityIndex : backwardAuthorityIndex < 0 ? forwardAuthorityIndex : Math.min(forwardAuthorityIndex, backwardAuthorityIndex);
if (authorityIndex >= 0 && value.includes("@", authorityIndex + 2)) return true;
const queryIndex = value.search(/[?&]/u);
if (queryIndex >= 0 && value.includes("=", queryIndex + 1)) return true;
const fragmentIndex = value.indexOf("#");
if (fragmentIndex >= 0 && value.includes("=", fragmentIndex + 1)) return true;
return /%[\da-f]{2}/iu.test(value);
}
/** True for auth-like URL query parameter names that should be redacted. */
function isSensitiveUrlQueryParamName(name) {
const normalized = normalizeUrlQueryParamName(name);
return normalized.unresolvedEncoding || SENSITIVE_URL_QUERY_PARAM_NAMES.has(normalized.value) || SUFFIXED_OR_SCOPED_TOKEN_QUERY_PARAM_RE.test(normalized.value);
}
function redactDirectSensitiveUrl(value) {
try {
const parsed = new URL(value);
let mutated = false;
const redactedPath = redactSensitiveUrlPath(parsed.pathname);
if (redactedPath !== parsed.pathname) {
parsed.pathname = redactedPath;
mutated = true;
}
if (parsed.username || parsed.password) {
parsed.username = parsed.username ? "***" : "";
parsed.password = parsed.password ? "***" : "";
mutated = true;
}
for (const key of Array.from(parsed.searchParams.keys())) if (isSensitiveUrlQueryParamName(key)) {
parsed.searchParams.set(key, "***");
mutated = true;
}
return mutated ? parsed.toString() : value;
} catch {
return value;
}
}
function redactQueryString(value, depth) {
const params = new URLSearchParams(value);
const entries = Array.from(params.entries());
const redactedEntries = [];
const seenSensitiveKeys = /* @__PURE__ */ new Set();
let mutated = false;
for (const [key, entryValue] of entries) {
if (isSensitiveUrlQueryParamName(key)) {
mutated = true;
if (!seenSensitiveKeys.has(key)) {
seenSensitiveKeys.add(key);
redactedEntries.push([key, "***"]);
}
continue;
}
const redactedKey = redactNestedUrlValue(key, depth + 1);
const redactedValue = redactNestedUrlValue(entryValue, depth + 1);
if (redactedKey !== key || redactedValue !== entryValue) mutated = true;
redactedEntries.push([redactedKey, redactedValue]);
}
if (!mutated) return value;
return new URLSearchParams(redactedEntries).toString();
}
function redactUrlLikeFallback(value) {
return redactSensitiveUrlPath(redactEmbeddedUrlUserInfo(value).replace(/([?&])([^=&]+)=([^&]*)/g, (match, prefix, key) => isSensitiveUrlQueryParamName(key) ? `${prefix}${key}=***` : match));
}
function redactAuthorityUserInfo(candidate, authorityStart) {
const authority = candidate.slice(authorityStart);
const userInfoEnd = authority.lastIndexOf("@");
if (userInfoEnd < 0) return candidate;
return `${candidate.slice(0, authorityStart)}***:***@${authority.slice(userInfoEnd + 1)}`;
}
function skipAuthoritySeparators(candidate, start) {
let cursor = start;
while (candidate[cursor] === "/" || candidate[cursor] === "\\") cursor += 1;
return cursor;
}
function redactEmbeddedUrlUserInfo(value) {
return value.replace(SPECIAL_SCHEME_AUTHORITY_RE, (candidate) => redactAuthorityUserInfo(candidate, skipAuthoritySeparators(candidate, candidate.indexOf(":") + 1))).replace(SPECIAL_SCHEME_SPILLED_USERINFO_RE, (candidate) => {
const authorityStart = skipAuthoritySeparators(candidate, candidate.indexOf(":") + 1);
const userInfoEnd = candidate.lastIndexOf("@");
const firstReservedDelimiter = candidate.slice(authorityStart).search(/[\\/?#]/u);
if (userInfoEnd < 0 || firstReservedDelimiter < 0) return candidate;
const absoluteReservedDelimiter = authorityStart + firstReservedDelimiter;
if (absoluteReservedDelimiter >= userInfoEnd) return candidate;
const credentialSeparator = candidate.indexOf(":", authorityStart);
if (credentialSeparator < 0 || credentialSeparator > absoluteReservedDelimiter) return candidate;
const authorityPrefix = candidate.slice(authorityStart, absoluteReservedDelimiter);
const possiblePort = candidate.slice(credentialSeparator + 1, absoluteReservedDelimiter);
if (/^\d+$/u.test(possiblePort) || /^\[[^\]]+\](?::\d+)?$/u.test(authorityPrefix)) return candidate;
return `${candidate.slice(0, authorityStart)}***:***@${candidate.slice(userInfoEnd + 1)}`;
}).replace(PROTOCOL_RELATIVE_AUTHORITY_RE, (candidate) => redactAuthorityUserInfo(candidate, skipAuthoritySeparators(candidate, 0)));
}
function hasUnresolvedEmbeddedUrlUserInfo(value) {
for (const match of value.matchAll(/(?:\b(?:https?|wss?|ftp):[\\/]{0,2}|[\\/]{2,})/giu)) {
const remainder = value.slice((match.index ?? 0) + match[0].length);
const userInfoEnd = remainder.search(/(?<!\*\*\*:\*\*\*)@/u);
const authorityEnd = remainder.search(/[\\/?#]/u);
const pathBeforeAt = remainder.slice(authorityEnd + 1, userInfoEnd);
if (userInfoEnd >= 0 && (authorityEnd < 0 || userInfoEnd <= authorityEnd || remainder[authorityEnd] === "/" && (pathBeforeAt.includes(":") || /^[^/?#\s]+\.[^/?#\s]+(?:[/?#]|$)/u.test(remainder.slice(userInfoEnd + 1))))) return true;
}
return false;
}
function redactRelativeUrlFragment(value, depth) {
const fragmentIndex = value.indexOf("#");
if (fragmentIndex < 0) return value;
const fragment = value.slice(fragmentIndex + 1);
const redactedFragment = redactFragment(fragment, depth + 1);
return redactedFragment === fragment ? value : `${value.slice(0, fragmentIndex + 1)}${redactedFragment}`;
}
function redactFragment(value, depth) {
if (!value) return value;
if (depth > MAX_NESTED_URL_REDACTION_DEPTH && looksLikeNestedUrlValue(value)) return "***";
const wholeUrl = redactSensitiveUrlAtDepth(value, depth);
if (wholeUrl.parsedWholeUrl) return redactUrlLikeFallback(wholeUrl.value);
const firstQueryDelimiter = value.search(/[?&]/u);
const firstEquals = value.indexOf("=");
if (firstEquals >= 0 && (firstQueryDelimiter < 0 || firstEquals < firstQueryDelimiter)) return redactQueryString(value, depth);
const hashRouterQueryIndex = value.indexOf("?");
if (hashRouterQueryIndex >= 0) {
const redactedQuery = redactQueryString(value.slice(hashRouterQueryIndex + 1), depth);
return `${redactEncodedUrlLikeString(redactUrlLikeFallback(value.slice(0, hashRouterQueryIndex + 1)), depth + 1)}${redactedQuery}`;
}
const fallback = redactUrlLikeFallback(value);
if (!looksLikeNestedUrlValue(fallback)) return fallback;
let decoded;
try {
decoded = decodeURIComponent(fallback);
} catch {
return "***";
}
if (decoded === fallback) return fallback;
const redactedDecoded = redactFragment(decoded, depth + 1);
return redactedDecoded === decoded ? fallback : encodeURIComponent(redactedDecoded);
}
function redactEncodedNestedUrlPath(value, depth) {
if (!looksLikeNestedUrlValue(value)) return value;
if (depth > MAX_NESTED_URL_REDACTION_DEPTH) return "***";
let decoded;
try {
decoded = decodeURIComponent(value);
} catch {
return "***";
}
if (decoded === value) return value;
const direct = redactSensitiveUrlLikeStringAtDepth(decoded, depth);
if (direct.value !== decoded || hasUnresolvedEmbeddedUrlUserInfo(decoded)) return direct.value !== decoded ? direct.value : "***";
if (direct.parsedWholeUrl) return value;
const nested = redactEncodedNestedUrlPath(decoded, depth + 1);
return nested === decoded ? value : nested;
}
function redactSensitiveUrlAtDepth(value, depth) {
try {
const directRedaction = redactDirectSensitiveUrl(value);
const parsed = new URL(directRedaction);
if (depth > MAX_NESTED_URL_REDACTION_DEPTH) return {
value: "***",
parsedWholeUrl: true
};
let mutated = directRedaction !== value;
const redactedNestedPath = redactEmbeddedUrlUserInfo(redactEncodedNestedUrlPath(parsed.pathname, depth + 1));
if (redactedNestedPath !== parsed.pathname) {
const originalPath = parsed.pathname;
parsed.pathname = redactedNestedPath;
if (parsed.pathname === originalPath) return {
value: directRedaction,
parsedWholeUrl: false
};
mutated = true;
}
const redactedQuery = redactQueryString(parsed.search.slice(1), depth);
if (redactedQuery !== parsed.search.slice(1)) {
parsed.search = redactedQuery;
mutated = true;
}
const fragment = parsed.hash.slice(1);
const redactedHash = redactFragment(fragment, depth + 1);
if (redactedHash !== fragment) {
parsed.hash = redactedHash;
mutated = true;
}
return {
value: mutated ? parsed.toString() : value,
parsedWholeUrl: true
};
} catch {
return {
value,
parsedWholeUrl: false
};
}
}
function redactSensitiveUrlLikeStringAtDepth(value, depth) {
const redactedUrl = redactSensitiveUrlAtDepth(value, depth);
if (redactedUrl.parsedWholeUrl) return redactedUrl;
return {
value: redactEncodedUrlLikeString(redactRelativeUrlFragment(redactUrlLikeFallback(redactedUrl.value), depth), depth + 1),
parsedWholeUrl: false
};
}
function redactEncodedUrlLikeString(value, depth) {
if (!looksLikeNestedUrlValue(value)) return value;
if (depth > MAX_NESTED_URL_REDACTION_DEPTH) return "***";
let decoded;
try {
decoded = decodeURIComponent(value);
} catch {
return "***";
}
if (decoded === value) return value;
const redactedDecoded = redactSensitiveUrlLikeStringAtDepth(decoded, depth + 1);
if (redactedDecoded.value !== decoded || redactedDecoded.parsedWholeUrl) return redactedDecoded.value === decoded ? value : redactedDecoded.value;
return hasUnresolvedEmbeddedUrlUserInfo(decoded) ? "***" : value;
}
function redactNestedUrlValue(value, depth) {
if (!looksLikeNestedUrlValue(value)) return value;
if (depth > MAX_NESTED_URL_REDACTION_DEPTH) return "***";
const direct = redactSensitiveUrlLikeStringAtDepth(value, depth);
if (direct.value !== value) return direct.value;
if (hasUnresolvedEmbeddedUrlUserInfo(value)) return "***";
if (direct.parsedWholeUrl) return value;
let decoded;
try {
decoded = decodeURIComponent(value);
} catch {
return "***";
}
if (decoded === value || !looksLikeNestedUrlValue(decoded)) return value;
const redactedDecoded = redactNestedUrlValue(decoded, depth + 1);
return redactedDecoded === decoded ? value : encodeURIComponent(redactedDecoded);
}
/** Redacts sensitive URL-looking substrings even when the full value is not a valid URL. */
function redactSensitiveUrlLikeString(value) {
return redactSensitiveUrlLikeStringAtDepth(value, 0).value;
}
//#endregion
//#region packages/acp-core/src/structured-auth-redaction.ts
const HTTP_AUTH_SCHEME_PATTERN = "[A-Za-z0-9!#$%&'*+.^_`|~-]+";
const HTTP_AUTH_OPAQUE_CREDENTIAL_PATTERN = String.raw`(?:\[REDACTED\]|[^\s\\"',;&#?<>)}\]]+)`;
const HTTP_AUTH_SERIALIZED_TAB_PATTERN = String.raw`\\{1,64}t`;
const HTTP_AUTH_SERIALIZED_INDENT_PATTERN = String.raw`(?:[ \t]+|${HTTP_AUTH_SERIALIZED_TAB_PATTERN})`;
const HTTP_AUTH_OPTIONAL_VALUE_WHITESPACE_PATTERN = String.raw`(?:[ \t]*\r?\n${HTTP_AUTH_SERIALIZED_INDENT_PATTERN}|[ \t]*\\{1,64}r\\{1,64}n${HTTP_AUTH_SERIALIZED_INDENT_PATTERN}|[ \t]*\\{1,64}n${HTTP_AUTH_SERIALIZED_INDENT_PATTERN}|[ \t]*${HTTP_AUTH_SERIALIZED_TAB_PATTERN}[ \t]*|[ \t]*)`;
const HTTP_AUTH_REQUIRED_VALUE_WHITESPACE_PATTERN = String.raw`(?:[ \t]*\r?\n${HTTP_AUTH_SERIALIZED_INDENT_PATTERN}|[ \t]*\\{1,64}r\\{1,64}n${HTTP_AUTH_SERIALIZED_INDENT_PATTERN}|[ \t]*\\{1,64}n${HTTP_AUTH_SERIALIZED_INDENT_PATTERN}|[ \t]*${HTTP_AUTH_SERIALIZED_TAB_PATTERN}[ \t]*|[ \t]+)`;
const HTTP_AUTH_LEGACY_VALUE_WHITESPACE_PATTERN = String.raw`(?:[ \t\r\n]*|[ \t]*\\{1,64}r\\{1,64}n(?:[ \t]*|${HTTP_AUTH_SERIALIZED_TAB_PATTERN})|[ \t]*\\{1,64}n(?:[ \t]*|${HTTP_AUTH_SERIALIZED_TAB_PATTERN})|[ \t]*${HTTP_AUTH_SERIALIZED_TAB_PATTERN}[ \t]*)`;
const HTTP_AUTH_HEADER_BOUNDARY_PATTERN = String.raw`(^|[^A-Za-z0-9_-]|\\{1,64}[rn])`;
const HTTP_AUTH_SERIALIZED_QUOTE_PATTERN = String.raw`(?:\\{1,64}["']|["']|)`;
String.raw`${HTTP_AUTH_HEADER_BOUNDARY_PATTERN}(?:x-goog-api-key|api-key|apikey|x-api-token|x-access-token)${HTTP_AUTH_SERIALIZED_QUOTE_PATTERN}[ \t]*[:=]${HTTP_AUTH_OPTIONAL_VALUE_WHITESPACE_PATTERN}${HTTP_AUTH_SERIALIZED_QUOTE_PATTERN}([^\s\\"',;]+)`;
const STRUCTURED_AUTH_HEADER_RE = new RegExp(String.raw`${HTTP_AUTH_HEADER_BOUNDARY_PATTERN}(?:Proxy-)?Authorization${HTTP_AUTH_SERIALIZED_QUOTE_PATTERN}[ \t]*[:=]${HTTP_AUTH_OPTIONAL_VALUE_WHITESPACE_PATTERN}${HTTP_AUTH_SERIALIZED_QUOTE_PATTERN}(${HTTP_AUTH_SCHEME_PATTERN})${HTTP_AUTH_REQUIRED_VALUE_WHITESPACE_PATTERN}`, "giu");
const AUTH_PARAM_TOKEN_RE = /^[A-Za-z0-9!#$%&'*+.^_`|~-]+/u;
const AWS_SCOPE_VALUE_RE = /^[A-Za-z0-9!#$%&'*+.^_`|~:/-]+/u;
function skipHorizontalWhitespace(value, start) {
let cursor = start;
while (value[cursor] === " " || value[cursor] === " ") cursor += 1;
return cursor;
}
function skipSerializedBackslashes(value, start) {
let cursor = start;
while (cursor - start < 64 && value[cursor] === "\\") cursor += 1;
return cursor;
}
function readSerializedLineEnd(value, start) {
const cursor = skipSerializedBackslashes(value, start);
if (cursor === start) return null;
if (value[cursor] === "n") return cursor + 1;
if (value[cursor] !== "r") return null;
const lineEnd = skipSerializedBackslashes(value, cursor + 1);
return lineEnd > cursor + 1 && value[lineEnd] === "n" ? lineEnd + 1 : null;
}
function readSerializedTabEnd(value, start) {
const cursor = skipSerializedBackslashes(value, start);
return cursor > start && value[cursor] === "t" ? cursor + 1 : null;
}
function skipAuthWhitespace(value, start) {
let cursor = start;
for (;;) {
cursor = skipHorizontalWhitespace(value, cursor);
const tabEnd = readSerializedTabEnd(value, cursor);
if (tabEnd !== null) {
cursor = tabEnd;
continue;
}
const lineEnd = value[cursor] === "\r" && value[cursor + 1] === "\n" ? cursor + 2 : value[cursor] === "\n" ? cursor + 1 : readSerializedLineEnd(value, cursor);
if (lineEnd === null || value[lineEnd] !== " " && value[lineEnd] !== " " && readSerializedTabEnd(value, lineEnd) === null) return cursor;
cursor = lineEnd;
}
}
function readAuthParamName(value, start) {
const match = AUTH_PARAM_TOKEN_RE.exec(value.slice(start));
return match ? {
name: match[0].toLowerCase(),
end: start + match[0].length
} : null;
}
function isAuthHeaderStart(value, index) {
const previous = value[index - 1];
let serializedLineBoundary = false;
if (previous === "n" || previous === "r") {
let slashCursor = index - 2;
let slashCount = 0;
while (slashCount < 64 && value[slashCursor] === "\\") {
slashCount += 1;
slashCursor -= 1;
}
serializedLineBoundary = slashCount > 0;
}
if (!serializedLineBoundary && previous !== void 0 && /[A-Za-z0-9_-]/u.test(previous)) return false;
const proxyName = "proxy-authorization";
const directName = "authorization";
const candidate = value.slice(index, index + 19).toLowerCase();
const name = candidate === proxyName ? proxyName : candidate.startsWith(directName) ? directName : null;
if (!name) return false;
const quoteStart = index + name.length;
let cursor = skipSerializedBackslashes(value, quoteStart);
if (value[cursor] === "\"" || value[cursor] === "'") cursor += 1;
else if (cursor > quoteStart) return false;
cursor = skipHorizontalWhitespace(value, cursor);
return value[cursor] === ":" || value[cursor] === "=";
}
function findNextAuthParamStart(value, start) {
let cursor = start;
for (;;) {
cursor = skipAuthWhitespace(value, cursor);
if (cursor > start && isAuthHeaderStart(value, cursor)) return null;
if (cursor >= value.length || value[cursor] === "\r" || value[cursor] === "\n" || value[cursor] === ";") return null;
if (value[cursor] === ",") {
cursor += 1;
continue;
}
const param = readAuthParamName(value, cursor);
if (param) {
const equals = skipAuthWhitespace(value, param.end);
if (value[equals] === "=" && value[equals + 1] !== "=") return cursor;
}
while (cursor < value.length) {
const whitespaceEnd = skipAuthWhitespace(value, cursor);
if (whitespaceEnd > cursor) {
cursor = whitespaceEnd;
continue;
}
if (cursor > start && isAuthHeaderStart(value, cursor)) return null;
const char = value[cursor];
if (char === "\r" || char === "\n" || char === ";") return null;
cursor += 1;
if (char === ",") break;
}
}
}
function usesAuthParams(scheme) {
return scheme === "digest" || scheme === "hawk" || scheme.startsWith("aws4-");
}
function isAuthFieldEnd(char) {
return char === void 0 || char === "\r" || char === "\n" || char === ";" || char === "\\" || char === "\"" || char === "'" || char === "}" || char === "]";
}
function findAuthFieldEnd(value, start) {
let cursor = start;
while (cursor < value.length) {
const whitespaceEnd = skipAuthWhitespace(value, cursor);
if (whitespaceEnd > cursor) {
cursor = whitespaceEnd;
continue;
}
if (cursor > start && isAuthHeaderStart(value, cursor)) break;
if (isAuthFieldEnd(value[cursor])) break;
cursor += 1;
}
return cursor;
}
function readParamValue(value, start, options) {
let escapedQuoteSlashCount = 0;
while (value[start + escapedQuoteSlashCount] === "\\") escapedQuoteSlashCount += 1;
const escapedQuotes = escapedQuoteSlashCount > 0 && value[start + escapedQuoteSlashCount] === "\"";
const quote = value[start] === "\"" || value[start] === "'" ? value[start] : void 0;
if (quote || escapedQuotes) {
let cursor = start + (escapedQuotes ? escapedQuoteSlashCount + 1 : 1);
while (cursor < value.length) {
if (value[cursor] === "\r" || value[cursor] === "\n") {
const whitespaceEnd = skipAuthWhitespace(value, cursor);
if (whitespaceEnd === cursor) break;
cursor = whitespaceEnd;
continue;
}
if (escapedQuotes && value[cursor] === "\\") {
let slashEnd = cursor + 1;
while (value[slashEnd] === "\\") slashEnd += 1;
if (value[slashEnd] === "\"") {
if ((slashEnd - cursor) % (2 * (escapedQuoteSlashCount + 1)) === escapedQuoteSlashCount) return slashEnd + 1;
cursor = slashEnd + 1;
continue;
}
cursor = slashEnd;
continue;
}
if (!escapedQuotes && value[cursor] === "\\" && cursor + 1 < value.length) {
cursor += 2;
continue;
}
if (!escapedQuotes && value[cursor] === quote) return cursor + 1;
cursor += 1;
}
return cursor > start + 1 ? cursor : null;
}
if (options.signedHeaders) {
const match = /^:?[A-Za-z0-9!#$%&'*+.^_`|~-]+(?:;:?[A-Za-z0-9!#$%&'*+.^_`|~-]+)*/u.exec(value.slice(start));
if (!match) return null;
const end = start + match[0].length;
const next = value[end];
return next === void 0 || next === "," || next === " " || next === " " || next === "\r" || next === "\n" ? end : null;
}
const match = (options.awsScope ? AWS_SCOPE_VALUE_RE : AUTH_PARAM_TOKEN_RE).exec(value.slice(start));
return match ? start + match[0].length : null;
}
function findStructuredAuthParamRanges(value) {
const ranges = [];
for (const header of value.matchAll(STRUCTURED_AUTH_HEADER_RE)) {
const scheme = (header[2] ?? "").toLowerCase();
let cursor = (header.index ?? 0) + header[0].length;
const rangeStart = cursor;
let rangeEnd = cursor;
const directParam = readAuthParamName(value, cursor);
const directEquals = directParam ? skipAuthWhitespace(value, directParam.end) : void 0;
if (!directParam || directEquals === void 0 || value[directEquals] !== "=" || value[directEquals + 1] === "=") {
if (value[skipAuthWhitespace(value, cursor)] !== "," && !usesAuthParams(scheme)) continue;
const firstParamStart = findNextAuthParamStart(value, cursor);
if (firstParamStart === null) continue;
cursor = firstParamStart;
}
for (;;) {
const param = readAuthParamName(value, cursor);
if (!param) break;
cursor = skipAuthWhitespace(value, param.end);
if (value[cursor] !== "=") break;
cursor = skipAuthWhitespace(value, cursor + 1);
const valueEnd = readParamValue(value, cursor, {
awsScope: scheme.startsWith("aws4-") && param.name === "credential",
signedHeaders: param.name === "signedheaders"
});
if (valueEnd === null) {
const nextParamStart = findNextAuthParamStart(value, cursor);
if (nextParamStart !== null) {
cursor = nextParamStart;
continue;
}
rangeEnd = Math.max(rangeEnd, findAuthFieldEnd(value, cursor));
break;
}
rangeEnd = valueEnd;
const separator = skipAuthWhitespace(value, valueEnd);
if (value[separator] !== ",") {
if (!isAuthFieldEnd(value[separator])) {
const nextParamStart = findNextAuthParamStart(value, separator);
if (nextParamStart !== null) {
cursor = nextParamStart;
continue;
}
rangeEnd = Math.max(rangeEnd, findAuthFieldEnd(value, separator));
}
break;
}
const nextParamStart = findNextAuthParamStart(value, separator + 1);
if (nextParamStart === null) break;
cursor = nextParamStart;
}
if (rangeEnd > rangeStart) ranges.push({
start: rangeStart,
end: rangeEnd
});
}
return ranges;
}
function redactStructuredAuthHeaders(value, replacement) {
const parts = [];
let cursor = 0;
for (const range of findStructuredAuthParamRanges(value)) {
if (parts.length === 0 || range.start > cursor) parts.push(value.slice(cursor, range.start), replacement);
cursor = Math.max(cursor, range.end);
}
parts.push(value.slice(cursor));
return parts.join("");
}
//#endregion
//#region packages/normalization-core/src/cjk-chars.ts
const WEIGHTED_CJK_RANGES = [
[
[183, 183],
[12288, 12703],
[19968, 40869],
[44032, 55215],
[65281, 65376]
],
[
[4352, 4607],
[11904, 12287],
[12704, 19967],
[40870, 40959],
[40960, 42239],
[42752, 42759],
[43360, 43391],
[55216, 55295],
[63744, 64255]
],
[
[711, 711],
[713, 715],
[729, 729],
[746, 747],
[773, 773],
[803, 803],
[65040, 65103],
[65377, 65500],
[65504, 65510]
],
[[119648, 119665]],
[
[94176, 94207],
[110576, 110591],
[110592, 110959],
[127488, 127743],
[131072, 195103],
[196608, 210047]
]
];
new RegExp(`[${WEIGHTED_CJK_RANGES.flatMap((ranges) => ranges.map(([start, end]) => `\\u{${start.toString(16)}}-\\u{${end.toString(16)}}`)).join("")}]`, "u");
(() => {
const maxCodePoint = Math.max(...WEIGHTED_CJK_RANGES.flatMap((ranges) => ranges.map(([, end]) => end)));
const categories = new Uint8Array(maxCodePoint + 1);
for (const [bucket, ranges] of WEIGHTED_CJK_RANGES.entries()) for (const [start, end] of ranges) categories.fill(bucket + 1, start, end + 1);
return categories;
})();
//#endregion
//#region packages/normalization-core/src/expect.ts
/** Returns the value or throws with the named context; use for genuine invariants only. */
function expectDefined(value, context) {
if (value === null || value === void 0) throw new Error("expected " + context + " to be defined");
return value;
}
//#endregion
//#region packages/normalization-core/src/utf16-slice.ts
function isHighSurrogate(codeUnit) {
return codeUnit >= 55296 && codeUnit <= 56319;
}
function isLowSurrogate(codeUnit) {
return codeUnit >= 56320 && codeUnit <= 57343;
}
/** Slices a UTF-16 string without returning dangling surrogate halves at either edge. */
function sliceUtf16Safe(input, start, end) {
const len = input.length;
let from = start < 0 ? Math.max(len + start, 0) : Math.min(start, len);
let to = end === void 0 ? len : end < 0 ? Math.max(len + end, 0) : Math.min(end, len);
if (to <= from) return "";
if (from > 0 && from < len) {
if (isLowSurrogate(input.charCodeAt(from)) && isHighSurrogate(input.charCodeAt(from - 1))) from += 1;
}
if (to > 0 && to < len) {
if (isHighSurrogate(input.charCodeAt(to - 1)) && isLowSurrogate(input.charCodeAt(to))) to -= 1;
}
return input.slice(from, to);
}
/** Truncates a UTF-16 string without cutting a surrogate pair in half. */
function truncateUtf16Safe(input, maxLen) {
const limit = Math.max(0, Math.floor(maxLen));
if (input.length <= limit) return input;
return sliceUtf16Safe(input, 0, limit);
}
//#endregion
//#region src/infra/map-size.ts
/** Prunes a Map in insertion order until it fits the requested maximum size. */
function pruneMapToMaxSize(map, maxSize) {
if (Number.isNaN(maxSize) || maxSize === Number.POSITIVE_INFINITY) return;
const limit = Math.max(0, Math.floor(maxSize));
if (limit <= 0) {
map.clear();
return;
}
for (const key of map.keys()) {
if (map.size <= limit) break;
map.delete(key);
}
}
//#endregion
//#region src/security/safe-regex.ts
const SAFE_REGEX_CACHE_MAX = 256;
const safeRegexCache = /* @__PURE__ */ new Map();
function createParseFrame() {
return {
lastToken: null,
containsRepetition: false,
hasAlternation: false,
branchMinLength: 0,
branchMaxLength: 0,
altMinLength: null,
altMaxLength: null
};
}
function addLength(left, right) {
if (!Number.isFinite(left) || !Number.isFinite(right)) return Number.POSITIVE_INFINITY;
return left + right;
}
function multiplyLength(length, factor) {
if (!Number.isFinite(length)) return factor === 0 ? 0 : Number.POSITIVE_INFINITY;
return length * factor;
}
function recordAlternative(frame) {
if (frame.altMinLength === null || frame.altMaxLength === null) {
frame.altMinLength = frame.branchMinLength;
frame.altMaxLength = frame.branchMaxLength;
return;
}
frame.altMinLength = Math.min(frame.altMinLength, frame.branchMinLength);
frame.altMaxLength = Math.max(frame.altMaxLength, frame.branchMaxLength);
}
function readQuantifier(source, index) {
const ch = source[index];
const consumed = source[index + 1] === "?" ? 2 : 1;
if (ch === "*") return {
consumed,
minRepeat: 0,
maxRepeat: null
};
if (ch === "+") return {
consumed,
minRepeat: 1,
maxRepeat: null
};
if (ch === "?") return {
consumed,
minRepeat: 0,
maxRepeat: 1
};
if (ch !== "{") return null;
let i = index + 1;
while (i < source.length && /\d/.test(source.charAt(i))) i += 1;
if (i === index + 1) return null;
const minRepeat = Number.parseInt(source.slice(index + 1, i), 10);
let maxRepeat = minRepeat;
if (source[i] === ",") {
i += 1;
const maxStart = i;
while (i < source.length && /\d/.test(source.charAt(i))) i += 1;
maxRepeat = i === maxStart ? null : Number.parseInt(source.slice(maxStart, i), 10);
}
if (source[i] !== "}") return null;
i += 1;
if (source[i] === "?") i += 1;
if (maxRepeat !== null && maxRepeat < minRepeat) return null;
return {
consumed: i - index,
minRepeat,
maxRepeat
};
}
function tokenizePattern(source) {
const tokens = [];
let inCharClass = false;
for (let i = 0; i < source.length; i += 1) {
const ch = source[i];
if (inCharClass) {
if (ch === "\\") {
i += 1;
continue;
}
if (ch === "]") inCharClass = false;
continue;
}
if (ch === "\\") {
i += 1;
tokens.push({ kind: "simple-token" });
continue;
}
if (ch === "[") {
inCharClass = true;
tokens.push({ kind: "simple-token" });
continue;
}
if (ch === "(") {
tokens.push({ kind: "group-open" });
continue;
}
if (ch === ")") {
tokens.push({ kind: "group-close" });
continue;
}
if (ch === "|") {
tokens.push({ kind: "alternation" });
continue;
}
const quantifier = readQuantifier(source, i);
if (quantifier) {
tokens.push({
kind: "quantifier",
quantifier
});
i += quantifier.consumed - 1;
continue;
}
tokens.push({ kind: "simple-token" });
}
return tokens;
}
function analyzeTokensForNestedRepetition(tokens) {
const frames = [createParseFrame()];
const emitToken = (token) => {
const frame = expectDefined(frames[frames.length - 1], "frames entry at frames.length 1");
frame.lastToken = token;
if (token.containsRepetition) frame.containsRepetition = true;
frame.branchMinLength = addLength(frame.branchMinLength, token.minLength);
frame.branchMaxLength = addLength(frame.branchMaxLength, token.maxLength);
};
const emitSimpleToken = () => {
emitToken({
containsRepetition: false,
hasAmbiguousAlternation: false,
minLength: 1,
maxLength: 1
});
};
for (const token of tokens) {
if (token.kind === "simple-token") {
emitSimpleToken();
continue;
}
if (token.kind === "group-open") {
frames.push(createParseFrame());
continue;
}
if (token.kind === "group-close") {
if (frames.length > 1) {
const frame = frames.pop();
if (frame.hasAlternation) recordAlternative(frame);
const groupMinLength = frame.hasAlternation ? frame.altMinLength ?? 0 : frame.branchMinLength;
const groupMaxLength = frame.hasAlternation ? frame.altMaxLength ?? 0 : frame.branchMaxLength;
emitToken({
containsRepetition: frame.containsRepetition,
hasAmbiguousAlternation: frame.hasAlternation && frame.altMinLength !== null && frame.altMaxLength !== null && frame.altMinLength !== frame.altMaxLength,
minLength: groupMinLength,
maxLength: groupMaxLength
});
}
continue;
}
if (token.kind === "alternation") {
const frame = expectDefined(frames[frames.length - 1], "frames entry at frames.length 1");
frame.hasAlternation = true;
recordAlternative(frame);
frame.branchMinLength = 0;
frame.branchMaxLength = 0;
frame.lastToken = null;
continue;
}
const frame = expectDefined(frames[frames.length - 1], "frames entry at frames.length 1");
const previousToken = frame.lastToken;
if (!previousToken) continue;
if (previousToken.containsRepetition) return true;
if (previousToken.hasAmbiguousAlternation && token.quantifier.maxRepeat === null) return true;
const previousMinLength = previousToken.minLength;
const previousMaxLength = previousToken.maxLength;
previousToken.minLength = multiplyLength(previousToken.minLength, token.quantifier.minRepeat);
previousToken.maxLength = token.quantifier.maxRepeat === null ? Number.POSITIVE_INFINITY : multiplyLength(previousToken.maxLength, token.quantifier.maxRepeat);
previousToken.containsRepetition = true;
frame.containsRepetition = true;
frame.branchMinLength = frame.branchMinLength - previousMinLength + previousToken.minLength;
frame.branchMaxLength = addLength(Number.isFinite(frame.branchMaxLength) && Number.isFinite(previousMaxLength) ? frame.branchMaxLength - previousMaxLength : Number.POSITIVE_INFINITY, previousToken.maxLength);
}
return false;
}
function hasNestedRepetition(source) {
return analyzeTokensForNestedRepetition(tokenizePattern(source));
}
function compileSafeRegexDetailed(source, flags = "") {
const trimmed = source.trim();
if (!trimmed) return {
regex: null,
source: trimmed,
flags,
reason: "empty"
};
const cacheKey = `${flags}::${trimmed}`;
const cached = safeRegexCache.get(cacheKey);
if (cached) return cached;
let result;
if (hasNestedRepetition(trimmed)) result = {
regex: null,
source: trimmed,
flags,
reason: "unsafe-nested-repetition"
};
else try {
result = {
regex: new RegExp(trimmed, flags),
source: trimmed,
flags,
reason: null
};
} catch {
result = {
regex: null,
source: trimmed,
flags,
reason: "invalid-regex"
};
}
safeRegexCache.set(cacheKey, result);
pruneMapToMaxSize(safeRegexCache, SAFE_REGEX_CACHE_MAX);
return result;
}
//#endregion
//#region src/security/config-regex.ts
function normalizeRejectReason(result) {
if (result.reason === null || result.reason === "empty") return null;
return result.reason;
}
/**
* Compile a single user-configured regex with the shared safe-regex guardrails.
* Returns null for blank patterns so optional config entries can be skipped silently.
*/
function compileConfigRegex(pattern, flags = "") {
const result = compileSafeRegexDetailed(pattern, flags);
if (result.reason === "empty") return null;
return {
regex: result.regex,
pattern: result.source,
flags: result.flags,
reason: normalizeRejectReason(result)
};
}
//#endregion
//#region src/infra/plain-object.ts
/**
* Config merge/patch uses the object tag, not prototype identity: class instances
* and custom prototypes remain accepted, while Date/Map/Set values are excluded.
*/
function isPlainObject(value) {
return typeof value === "object" && value !== null && !Array.isArray(value) && Object.prototype.toString.call(value) === "[object Object]";
}
//#endregion
//#region src/infra/prototype-keys.ts
const BLOCKED_OBJECT_KEYS = /* @__PURE__ */ new Set([
"__proto__",
"prototype",
"constructor"
]);
/** Return true when assigning `key` could mutate an object prototype. */
function isBlockedObjectKey(key) {
return BLOCKED_OBJECT_KEYS.has(key);
}
//#endregion
//#region src/shared/path-array-index.ts
/** Upper bound for config path array indexes to reject impractical sparse writes. */
const MAX_CONFIG_PATH_ARRAY_INDEX = 1e5;
const CANONICAL_ARRAY_INDEX_SEGMENT = /^(0|[1-9]\d*)$/;
/** Parses a canonical non-negative array index segment used by config and JSON paths. */
function parseConfigPathArrayIndex(segment) {
if (!CANONICAL_ARRAY_INDEX_SEGMENT.test(segment)) return;
const index = Number(segment);
return Number.isSafeInteger(index) && index <= MAX_CONFIG_PATH_ARRAY_INDEX ? index : void 0;
}
//#endregion
//#region src/shared/dot-path.ts
function parseQuotedBracketPathSegment(value) {
const quote = value.startsWith("\"") ? "\"" : "'";
if (quote === "\"") try {
return JSON.parse(value);
} catch {}
if (!value.endsWith(quote)) throw new SyntaxError("Unterminated quoted path segment");
let normalized = "\"";
for (let index = 1; index < value.length - 1; index += 1) {
const character = value[index];
if (character === quote) throw new SyntaxError("Unexpected quote in path segment");
if (character === "\"") {
normalized += "\\\"";
continue;
}
if (character === "\\") {
const escaped = value[index + 1];
if (escaped === void 0 || index + 1 >= value.length - 1) throw new SyntaxError("Unterminated escape in path segment");
index += 1;
if (escaped === "\n" || escaped === "\u2028" || escaped === "\u2029") continue;
if (escaped === "\r") {
if (value[index + 1] === "\n") index += 1;
continue;
}
if (escaped === "x") {
const hex = value.slice(index + 1, index + 3);
if (!/^[\da-f]{2}$/i.test(hex)) throw new SyntaxError("Invalid hexadecimal escape in path segment");
normalized += `\\u00${hex}`;
index += 2;
continue;
}
if (escaped === "0") {
if (/\d/.test(value[index + 1] ?? "")) throw new SyntaxError("Invalid numeric escape in path segment");
normalized += "\\u0000";
continue;
}
if (escaped === "v") {
normalized += "\\u000b";
continue;
}
if (/[1-9]/.test(escaped)) throw new SyntaxError("Invalid numeric escape in path segment");
if (escaped === "'") normalized += "'";
else if ("\"\\/bfnrtu".includes(escaped)) normalized += `\\${escaped}`;
else normalized += escaped;
continue;
}
normalized += character;
}
return JSON.parse(`${normalized}"`);
}
function parseBracketPathSegment(raw, fullPath) {
const trimmed = raw.trim();
if (!trimmed) throw new Error(`Invalid path (empty "[]"): ${fullPath}`);
if (trimmed.startsWith("\"") || trimmed.startsWith("'")) {
try {
const parsed = parseQuotedBracketPathSegment(trimmed);
if (typeof parsed === "string" && parsed.trim()) return parsed;
} catch (err) {
throw new Error(`Invalid path bracket string (${trimmed}): ${fullPath}`, { cause: err });
}
throw new Error(`Invalid path bracket string (${trimmed}): ${fullPath}`);
}
return parseConfigPathArrayIndex(trimmed) ?? trimmed;
}
function assertNotWhitespaceSegment(current, raw) {
if (current.length > 0 && !current.trim()) throw new Error(`Invalid path (empty segment): ${raw}`);
}
function findBracketPathClose(path, open) {
let quote;
for (let index = open + 1; index < path.length; index += 1) {
const character = path[index];
if (quote) {
if (character === "\\") index += 1;
else if (character === quote) quote = void 0;
continue;
}
if (character === "]") return index;
if ((character === "\"" || character === "'") && !path.slice(open + 1, index).trim()) quote = character;
}
return -1;
}
/** Reads diagnostic path identity without applying object-mutation key restrictions. */
function tokenizeConcreteConfigPath(raw) {
const trimmed = raw.trim();
if (!trimmed) throw new Error("Path is empty.");
const parts = [];
const quotedNumericSegments = /* @__PURE__ */ new Set();
let current = "";
let segmentEmitted = false;
let index = 0;
while (index < trimmed.length) {
const character = trimmed[index];
if (character === "\\") {
const next = trimmed[index + 1];
if (next === void 0) throw new Error(`Invalid path (trailing escape): ${raw}`);
current += next;
index += 2;
continue;
}
if (character === ".") {
assertNotWhitespaceSegment(current, raw);
if (!segmentEmitted && !current.trim()) throw new Error(`Invalid path (empty segment): ${raw}`);
if (current) parts.push(current.trim());
current = "";
segmentEmitted = false;
index += 1;
continue;
}
if (character === "[") {
assertNotWhitespaceSegment(current, raw);
if (!current.trim() && !segmentEmitted && parts.length > 0) throw new Error(`Invalid path (empty segment): ${raw}`);
if (current) parts.push(current.trim());
current = "";
const close = findBracketPathClose(trimmed, index);
if (close === -1) throw new Error(`Invalid path (missing "]"): ${raw}`);
const inside = trimmed.slice(index + 1, close).trim();
const segment = parseBracketPathSegment(inside, raw);
if (typeof segment === "string" && (inside.startsWith("\"") || inside.startsWith("'")) && parseConfigPathArrayIndex(segment) !== void 0) quotedNumericSegments.add(parts.length);
parts.push(segment);
const next = trimmed[close + 1];
if (next !== void 0 && next !== "." && next !== "[") throw new Error(`Invalid path (missing separator after bracket): ${raw}`);
segmentEmitted = true;
index = close + 1;
continue;
}
current += character;
index += 1;
}
if (!segmentEmitted && !current.trim()) throw new Error(`Invalid path (empty segment): ${raw}`);
if (current) parts.push(current.trim());
return {
tokens: parts,
quotedNumericSegments
};
}
/** Appends one config path segment without confusing literal record keys with traversal. */
function appendConfigPathSegment(path, segment) {
if (typeof segment === "number") return `${path}[${segment}]`;
if (!/^[A-Za-z_$][A-Za-z0-9_$:-]*$/.test(segment)) return `${path}[${JSON.stringify(segment)}]`;
return path ? `${path}.${segment}` : segment;
}
/** Formats concrete tokens, recovering array indices from their actual source containers. */
function formatConcreteConfigPath(segments, source) {
let cursor = source;
return segments.reduce((path, segment) => {
const concreteSegment = typeof segment === "string" && Array.isArray(cursor) ? parseConfigPathArrayIndex(segment) ?? segment : segment;
cursor = cursor !== null && typeof cursor === "object" ? Reflect.get(cursor, String(segment)) : void 0;
return appendConfigPathSegment(path, concreteSegment);
}, "");
}
//#endregion
//#region src/secrets/ref-contract.ts
/** Shared SecretRef grammar and validation helpers for config, schema, SDK, and gateway parity. */
/** Provider alias used when a SecretRef omits a source-specific provider. */
const DEFAULT_SECRET_PROVIDER_ALIAS = "default";
/** Strict env-var id shape accepted for env-backed SecretRefs. */
const ENV_SECRET_REF_ID_RE = /^[A-Z][A-Z0-9_]{0,127}$/;
/** Return whether an env SecretRef id is a supported uppercase environment variable name. */
function isValidEnvSecretRefId(value) {
return ENV_SECRET_REF_ID_RE.test(value);
}
/** Narrow a value to the canonical SecretRef object shape. */
function isSecretRef(value) {
if (!isRecord(value)) return false;
if (Object.keys(value).length !== 3) return false;
return (value.source === "env" || value.source === "file" || value.source === "exec" || value.source === "store") && typeof value.provider === "string" && value.provider.trim().length > 0 && typeof value.id === "string" && value.id.trim().length > 0;
}
/**
* Runtime secret-reference grammar shared by config parsing, plugin SDK schemas,
* gateway parity checks, and resolver planning.
*/
const FILE_SECRET_REF_SEGMENT_PATTERN = /^(?:[^~]|~0|~1)*$/;
/** Shared alias grammar for env/file/exec/store secret provider names. */
const SECRET_PROVIDER_ALIAS_PATTERN = /^[a-z][a-z0-9_-]{0,63}$/;
const EXEC_SECRET_REF_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/#-]{0,255}$/;
/** Canonical id for file secret providers that expose exactly one value. */
const SINGLE_VALUE_FILE_REF_ID = "value";
/** Builds the stable map key used to cache or compare resolved secret refs. */
function secretRefKey(ref) {
return `${ref.source}:${ref.provider}:${ref.id}`;
}
/** Resolves the default provider alias for one source, falling back to the built-in alias. */
function resolveDefaultSecretProviderAlias(config, source, options) {
const configured = config.secrets?.defaults?.[source];
if (configured?.trim()) return configured.trim();
if (options?.preferFirstProviderForSource) {
const providers = config.secrets?.providers;
if (providers) {
for (const [providerName, provider] of Object.entries(providers)) if (provider?.source === source) return providerName;
}
}
return DEFAULT_SECRET_PROVIDER_ALIAS;
}
/** Whether a source-specific built-in provider owns this selected default alias. */
function isBuiltInDefaultSecretProviderRef(config, ref) {
return config.secrets?.providers?.[ref.provider]?.source !== ref.source && (ref.source === "env" || ref.source === "store") && ref.provider === resolveDefaultSecretProviderAlias(config, ref.source);
}
/** Returns the configured provider source when a SecretRef selects an impossible pairing. */
function resolveSecretRefProviderSourceMismatch(config, ref) {
const configuredSource = config.secrets?.providers?.[ref.provider]?.source;
if (!configuredSource || configuredSource === ref.source || isBuiltInDefaultSecretProviderRef(config, ref)) return null;
return configuredSource;
}
/** Validates file secret ref ids against the shared JSON-pointer-style contract. */
function isValidFileSecretRefId(value) {
if (value === "value") return true;
if (!value.startsWith("/")) return false;
return value.slice(1).split("/").every((segment) => FILE_SECRET_REF_SEGMENT_PATTERN.test(segment));
}
/** Validates a secret provider alias against the shared config/gateway grammar. */
function isValidSecretProviderAlias(value) {
return SECRET_PROVIDER_ALIAS_PATTERN.test(value);
}
/** Validates exec secret ref ids and reports why invalid ids failed. */
function validateExecSecretRefId(value) {
if (!EXEC_SECRET_REF_ID_PATTERN.test(value)) return {
ok: false,
reason: "pattern"
};
for (const segment of value.split("/")) if (segment === "." || segment === "..") return {
ok: false,
reason: "traversal-segment"
};
return { ok: true };
}
/** Boolean convenience wrapper for callers that only need accept/reject behavior. */
function isValidExecSecretRefId(value) {
return validateExecSecretRefId(value).ok;
}
/** Formats the user-facing validation message for rejected exec secret ref ids. */
function formatExecSecretRefIdValidationMessage() {
return [
"Exec secret reference id must match /^[A-Za-z0-9][A-Za-z0-9._:/#-]{0,255}$/",
"and must not include \".\" or \"..\" path segments",
"(example: \"vault/openai/api-key\" or \"aws/secret#json_key\")."
].join(" ");
}
function normalizeAndGroupSecretRefs(refs) {
if (refs.length === 0) return [];
const uniqueRefs = /* @__PURE__ */ new Map();
for (const ref of refs) {
const id = ref.id.trim();
if (!id) throw new Error("Secret reference id is empty.");
if (!isValidSecretProviderAlias(ref.provider)) throw new Error(`Secret reference provider must match /^[a-z][a-z0-9_-]{0,63}$/ (ref: ${ref.source}:${ref.provider}:${id}).`);
if (ref.source === "env" && !isValidEnvSecretRefId(id)) throw new Error(`Env secret reference id must match /^[A-Z][A-Z0-9_]{0,127}$/ (ref: ${ref.source}:${ref.provider}:${id}).`);
if (ref.source === "file" && !isValidFileSecretRefId(id)) throw new Error(`File secret reference id must be an absolute JSON pointer or "value" (ref: ${ref.source}:${ref.provider}:${id}).`);
if (ref.source === "store" && !isValidEnvSecretRefId(id)) throw new Error(`Store secret reference id must match /^[A-Z][A-Z0-9_]{0,127}$/ (ref: ${ref.source}:${ref.provider}:${id}).`);
if (ref.source === "exec" && !isValidExecSecretRefId(id)) throw new Error(`${formatExecSecretRefIdValidationMessage()} (ref: ${ref.source}:${ref.provider}:${id}).`);
uniqueRefs.set(secretRefKey(ref), {
...ref,
id
});
}
const grouped = /* @__PURE__ */ new Map();
for (const ref of uniqueRefs.values()) {
const key = `${ref.source}:${ref.provider}`;
const existing = grouped.get(key);
if (existing) {
existing.refs.push(ref);
continue;
}
grouped.set(key, {
source: ref.source,
providerName: ref.provider,
refs: [ref]
});
}
return [...grouped.values()];
}
//#endregion
//#region src/config/types.secrets.ts
const ENV_SECRET_TEMPLATE_RE = /^\$\{([A-Z][A-Z0-9_]{0,127})\}$/;
const ENV_SECRET_SHORTHAND_RE = /^\$([A-Z][A-Z0-9_]{0,127})$/;
function isLegacySecretRefWithoutProvider(value) {
if (!isRecord(value)) return false;
return (value.source === "env" || value.source === "file" || value.source === "exec" || value.source === "store") && typeof value.id === "string" && value.id.trim().length > 0 && value.provider === void 0;
}
/** Parse `$NAME` and `${NAME}` env-secret shorthand strings into env SecretRefs. */
function parseEnvTemplateSecretRef(value, provider = DEFAULT_SECRET_PROVIDER_ALIAS) {
if (typeof value !== "string") return null;
const trimmed = value.trim();
const match = ENV_SECRET_TEMPLATE_RE.exec(trimmed) ?? ENV_SECRET_SHORTHAND_RE.exec(trimmed);
if (!match) return null;
return {
source: "env",
provider: provider.trim() || "default",
id: expectDefined(match[1], "types.secrets regex capture 1")
};
}
/** Parse current structured refs and supported env shorthand without legacy normalization. */
function parseSecretRef(value, defaults) {
return isSecretRef(value) ? value : parseEnvTemplateSecretRef(value, defaults?.env);
}
/** Public SDK input adapter; persisted config and state are normalized by Doctor. */
function coerceSecretRef(value, defaults) {
if (isLegacySecretRefWithoutProvider(value)) {
const provider = defaults?.[value.source] ?? "default";
return {
source: value.source,
provider,
id: value.id
};
}
return parseSecretRef(value, defaults);
}
/** Return whether a value contains either a literal secret string or resolvable SecretRef shape. */
function hasConfiguredSecretInput(value, defaults) {
if (normalizeSecretInputString(value)) return true;
return coerceSecretRef(value, defaults) !== null;
}
/** Trim a literal secret input string while leaving non-string inputs unresolved. */
function normalizeSecretInputString(value) {
return normalizeOptionalString(value);
}
/** Resolve explicit `refValue` before inline secret references embedded in `value`. */
function resolveSecretInputRef(params) {
const explicitRef = coerceSecretRef(params.refValue, params.defaults);
const inlineRef = explicitRef ? null : coerceSecretRef(params.value, params.defaults);
return {
explicitRef,
inlineRef,
ref: explicitRef ?? inlineRef
};
}
//#endregion
//#region src/config/env-substitution.ts
const ENV_VAR_NAME_PATTERN = /^[A-Z_][A-Z0-9_]*$/;
const DEFAULT_VALUE_OPERATOR = ":-";
/** Error thrown when a config value references a missing or empty environment variable. */
var MissingEnvVarError = class extends Error {
constructor(varName, configPath) {
super(`Missing env var "${varName}" referenced at config path: ${configPath}`);
this.varName = varName;
this.configPath = configPath;
this.name = "MissingEnvVarError";
}
};
/** Rebuilds the authored placeholder text for a parsed token. */
function renderEnvTemplateToken(token) {
return token.defaultValue === void 0 ? `\${${token.name}}` : `\${${token.name}${DEFAULT_VALUE_OPERATOR}${token.defaultValue}}`;
}
function* iterateEnvTemplateTokens(value) {
for (let index = value.indexOf("$"); index !== -1; index = value.indexOf("$", index + 1)) {
const escaped = value[index + 1] === "$" && value[index + 2] === "{";
if (!escaped && value[index + 1] !== "{") continue;
const start = index + (escaped ? 3 : 2);
const end = value.indexOf("}", start);
if (end === -1) continue;
const body = value.slice(start, end);
const operatorIndex = body.indexOf(DEFAULT_VALUE_OPERATOR);
const name = operatorIndex === -1 ? body : body.slice(0, operatorIndex);
const defaultValue = operatorIndex === -1 ? void 0 : body.slice(operatorIndex + 2);
if (!ENV_VAR_NAME_PATTERN.test(name) || defaultValue?.includes("$") || defaultValue?.includes("{")) continue;
yield {
kind: escaped ? "escaped" : "substitution",
name,
defaultValue,
start: index,
end
};
index = end;
}
}
function substituteString(value, env, configPath, opts) {
if (!value.includes("$")) return value;
const authoredRef = parseEnvTemplateSecretRef(value);
if (authoredRef && !containsEnvVarReference(value)) opts?.onPendingEnvSecretRef?.(authoredRef.id, configPath);
const chunks = [];
let end = 0;
for (const token of iterateEnvTemplateTokens(value)) {
chunks.push(value.slice(end, token.start));
end = token.end + 1;
if (token.kind === "escaped") {
chunks.push(renderEnvTemplateToken(token));
continue;
}
const envValue = env[token.name];
if (envValue === void 0 || envValue === "") {
if (token.defaultValue !== void 0) {
chunks.push(token.defaultValue);
continue;
}
if (opts?.onMissing) {
opts.onMissing({
varName: token.name,
configPath
});
if (authoredRef?.id === token.name) opts.onPendingEnvSecretRef?.(token.name, configPath);
chunks.push(renderEnvTemplateToken(token));
continue;
}
throw new MissingEnvVarError(token.name, configPath);
}
if (authoredRef?.id === token.name) opts?.onResolvedEnvSecretRef?.(token.name, configPath);
chunks.push(envValue);
}
chunks.push(value.slice(end));
return chunks.join("");
}
/** Detects unescaped `${VAR}` references without treating escaped `$${VAR}` as references. */
function containsEnvVarReference(value) {
for (const token of iterateEnvTemplateTokens(value)) if (token.kind === "substitution") return true;
return false;
}
/** Resolve config string templates, preserving unresolved text when onMissing is supplied. */
function resolveConfigEnvVars(value, env = process.env, opts) {
const pending = [];
const visit = (current, currentPath) => {
if (typeof current === "string") return substituteString(current, env, currentPath, opts);
if (Array.isArray(current)) {
const length = current.length;
const result = [];
result.length = length;
let index = 0;
pending.push(() => {
while (index < length) {
const key = index++;
if (key in current) {
result[key] = visit(current[key], `${currentPath}[${key}]`);
return true;
}
}
return false;
});
return result;
}
if (isPlainObject(current)) {
const result = {};
const entries = Object.entries(current)[Symbol.iterator]();
pending.push(() => {
const entry = entries.next();
if (entry.done) return false;
const [key, child] = entry.value;
result[key] = visit(child, appendConfigPathSegment(currentPath, key));
return true;
});
return result;
}
return current;
};
const result = visit(value, "");
for (let next = pending.at(-1); next; next = pending.at(-1)) if (!next()) pending.pop();
return result;
}
//#endregion
//#region src/infra/boundary-file-read.ts
function preserveOpenClawOverflowError(error, maxBytes) {
if (error instanceof FsSafeError && error.code === "too-large") throw new RangeError(`File exceeds ${maxBytes} bytes`, { cause: error });
throw error;
}
/** Read a pinned descriptor without changing OpenClaw's user-facing overflow error. */
async function readFileDescriptorBounded$1(fd, maxBytes) {
try {
return await readFileDescriptorBounded(fd, maxBytes);
} catch (error) {
return preserveOpenClawOverflowError(error, maxBytes);
}
}
/** Synchronous variant for callers that own a pinned descriptor. */
function readFileDescriptorBoundedSync$1(fd, maxBytes) {
try {
return readFileDescriptorBoundedSync(fd, maxBytes);
} catch (error) {
return preserveOpenClawOverflowError(error, maxBytes);
}
}
//#endregion
//#region src/infra/boundary-path.ts
const runInBoundaryPathContext = AsyncLocalStorage.snapshot();
function resolvePathViaExistingAncestorSync$1(targetPath) {
return runInBoundaryPathContext(resolvePathViaExistingAncestorSync, targetPath);
}
function resolveIdentityPathViaExistingAncestorSync(targetPath) {
const fallback = path.resolve(targetPath);
const missingSegments = [];
let cursor = fallback;
while (true) try {
return path.join(fsSync.realpathSync.native(cursor), ...missingSegments.toReversed());
} catch {
const parent = path.dirname(cursor);
if (parent === cursor) return fallback;
missingSegments.push(path.basename(cursor));
cursor = parent;
}
}
//#endregion
//#region src/infra/deep-merge.ts
function sanitizePlainObject(value) {
const sanitized = {};
for (const [key, entry] of Object.entries(value)) {
if (isBlockedObjectKey(key)) continue;
sanitized[key] = isPlainObject(entry) ? sanitizePlainObject(entry) : entry;
}
return sanitized;
}
/** Merge plain objects while preserving OpenClaw's null, undefined, and array policies. */
function mergeDeep(base, override, options = {}) {
const arrays = options.arrays ?? "replace";
const undefinedValues = options.undefinedValues ?? "skip";
if (Array.isArray(base) && Array.isArray(override)) return arrays === "concat" ? [...base, ...override] : override;
if (!isPlainObject(base) || !isPlainObject(override)) return override === void 0 && undefinedValues === "skip" ? base : override;
const merged = sanitizePlainObject(base);
for (const [key, value] of Object.entries(override)) {
if (isBlockedObjectKey(key) || value === void 0 && undefinedValues === "skip") continue;
const current = merged[key];
if (isPlainObject(value)) merged[key] = isPlainObject(current) ? mergeDeep(current, value, options) : sanitizePlainObject(value);
else if (arrays === "concat" && Array.isArray(current) && Array.isArray(value)) merged[key] = [...current, ...value];
else merged[key] = value;
}
return merged;
}
//#endregion
//#region src/infra/errno.ts
/** Classifies missing filesystem paths across Node and fs-safe boundaries. */
function isMissingPathError(err) {
return hasErrnoCode(err, "ENOENT") || hasErrnoCode(err, "ENOTDIR") || hasErrnoCode(err, "not-found");
}
//#endregion
//#region src/shared/regexp.ts
/** Escape text so it can be embedded literally inside a RegExp pattern. */
function escapeRegExp(value) {
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function getSealedRuntimeSecureTempRoot() {}
//#endregion
//#region src/utils/parse-json-compat.ts
/**
* JSON parser compatibility helper for persisted config, manifests, and legacy stores.
* Strict JSON stays the fast path; JSON5 is only the authored/legacy fallback.
*/
let json5Runtime;
function isJson5Parser(value) {
return typeof value === "object" && value !== null && "parse" in value && typeof value.parse === "function";
}
function setJson5Runtime(runtime) {
const parser = isJson5Parser(runtime) ? runtime : typeof runtime === "object" && runtime !== null && "default" in runtime ? runtime.default : void 0;
if (!isJson5Parser(parser)) throw new Error("json5 parser unavailable");
json5Runtime = parser;
return parser;
}
function loadJson5Parser() {
if (json5Runtime) return json5Runtime;
if (typeof SEALED_RUNTIME_BUILD === "boolean" && SEALED_RUNTIME_BUILD) throw new Error("sealed JSON5 runtime was not registered before use");
return setJson5Runtime(createRequire(import.meta.url)("json5"));
}
/** Parses strict JSON first, then accepts JSON5 syntax such as comments and trailing commas. */
function parseJsonWithJson5Fallback(raw, json5) {
try {
return JSON.parse(raw);
} catch {
return (json5 ?? loadJson5Parser()).parse(raw);
}
}
//#endregion
//#region src/config/includes.ts
/**
* Config includes: $include directive for modular configs
*
* @example
* ```json5
* {
* "$include": "./base.json5", // single file
* "$include": ["./a.json5", "./b.json5"] // merge multiple
* }
* ```
*/
const INCLUDE_KEY = "$include";
const MAX_INCLUDE_FILE_BYTES = 2097152;
/** Maximum length for $include path and resolved path (CWE-22 hardening). */
const MAX_INCLUDE_PATH_LENGTH = 4096;
function hashConfigIncludeRaw(raw) {
const hash = crypto.createHash("sha256");
if (raw === null) hash.update("missing");
else {
hash.update("present\0");
hash.update(raw, "utf-8");
}
return hash.digest("hex");
}
/** Resolve an include write target through its current ancestors and allowed roots. */
function resolveConfigIncludeWritePath(params) {
const resolvedPath = path.normalize(path.resolve(params.includePath));
const roots = [path.dirname(params.configPath), ...params.allowedRoots ?? []].filter((root) => path.isAbsolute(root)).map((root) => path.normalize(root));
if (!roots.some((root) => isPathInside$1(root, resolvedPath))) throw new ConfigIncludeError(`Include write path escapes config directory: ${params.includePath}`, params.includePath);
const canonicalPath = path.normalize(resolvePathViaExistingAncestorSync$1(resolvedPath));
if (!roots.map((root) => path.normalize(safeRealpath(root))).some((root) => isPathInside$1(root, canonicalPath))) throw new ConfigIncludeError(`Include write path resolves outside config directory (symlink): ${params.includePath}`, params.includePath);
return canonicalPath;
}
function isConfigContainer(value) {
return Array.isArray(value) || isPlainObject(value);
}
function includeLogicalPath(link) {
const parts = [];
for (let current = link; current; current = current.parent) parts.push(current.key);
return parts.toReversed();
}
var ConfigIncludeError = class extends Error {
constructor(message, includePath, cause) {
super(message);
this.includePath = includePath;
this.cause = cause;
this.name = "ConfigIncludeError";
}
};
/** File access failed; the included configuration has not been validated. */
var ConfigIncludeReadError = class extends ConfigIncludeError {
constructor(..._args) {
super(..._args);
this.name = "ConfigIncludeReadError";
}
};
var CircularIncludeError = class extends ConfigIncludeError {
constructor(chain) {
super(`Circular include detected: ${chain.join(" -> ")}`, expectDefined(chain[chain.length - 1], "chain entry at chain.length 1"));
this.chain = chain;
this.name = "CircularIncludeError";
}
};
/** Deep merge: arrays concatenate, objects merge recursively, primitives: source wins */
function deepMerge(target, source) {
return mergeDeep(target, source, {
arrays: "concat",
undefinedValues: "replace"
});
}
var IncludeProcessor = class {
constructor(basePath, resolver, boundary, rootProjectionKeys) {
this.basePath = basePath;
this.resolver = resolver;
this.boundary = boundary;
this.rootProjectionKeys = rootProjectionKeys;
}
get rootDir() {
return this.boundary.configRoot.rootDir;
}
process(obj) {
const stack = [];
let current = this.traverseValue(obj, null, false, {
basePath: this.basePath,
visited: /* @__PURE__ */ new Set([path.normalize(this.basePath)]),
depth: 0
});
let delivered = void 0;
for (;;) {
const step = current.next(delivered);
if (step.done) {
const parent = stack.pop();
if (!parent) return step.value;
delivered = step.value;
current = parent;
continue;
}
stack.push(current);
current = this.traverseValue(step.value.value, step.value.pathLink, step.value.hasArrayAncestor, step.value.context);
}
}
*traverseValue(value, pathLink, hasArrayAncestor, context) {
if (Array.isArray(value)) {
const result = [];
for (const [index, item] of value.entries()) result.push(isConfigContainer(item) ? yield {
value: item,
pathLink: {
parent: pathLink,
key: String(index)
},
hasArrayAncestor: true,
context
} : item);
return result;
}
if (!isPlainObject(value)) return value;
if (!("$include" in value)) {
const isRoot = pathLink === null;
const result = {};
for (const [key, entry] of Object.entries(value)) {
if (isRoot && this.rootProjectionKeys && !this.rootProjectionKeys.has(key)) continue;
result[key] = isConfigContainer(entry) ? yield {
value: entry,
pathLink: {
parent: pathLink,
key
},
hasArrayAncestor,
context
} : entry;
}
return result;
}
return yield* this.traverseInclude(value, pathLink, hasArrayAncestor, context);
}
*traverseInclude(obj, pathLink, hasArrayAncestor, context) {
const includeValue = obj[INCLUDE_KEY];
const isRoot = pathLink === null;
const siblingKeys = Object.keys(obj).filter((key) => key !== "$include" && (!isRoot || !this.rootProjectionKeys || this.rootProjectionKeys.has(key)));
const multiple = Array.isArray(includeValue);
if (!multiple && typeof includeValue !== "string") throw new ConfigIncludeError(`Invalid $include value: expected string or array of strings, got ${typeof includeValue}`, String(includeValue));
const entries = [];
for (const item of multiple ? includeValue : [includeValue]) {
if (typeof item !== "string") throw new ConfigIncludeError(`Invalid $include array item: expected string, got ${typeof item}`, String(item));
const loaded = this.loadFileSync(item, context);
entries.push({
value: yield {
value: loaded.parsed,
pathLink,
hasArrayAncestor,
context: loaded.nestedContext
},
targetPath: loaded.resolvedPath
});
}
const included = multiple ? entries.reduce((current, entry) => deepMerge(current, entry.value), {}) : entries[0].value;
this.resolver.onIncludeResolved?.({
path: includeLogicalPath(pathLink),
value: included,
kind: multiple ? "multiple" : "single",
hasSiblingOverrides: siblingKeys.length > 0,
hasArrayAncestor,
...multiple ? { targetPaths: entries.map((entry) => entry.targetPath) } : { targetPath: entries[0].targetPath }
});
if (siblingKeys.length === 0) return included;
if (!isPlainObject(included)) throw new ConfigIncludeError("Sibling keys require included content to be an object", typeof includeValue === "string" ? includeValue : INCLUDE_KEY);
const rest = {};
for (const key of siblingKeys) {
const sibling = obj[key];
rest[key] = isConfigContainer(sibling) ? yield {
value: sibling,
pathLink: {
parent: pathLink,
key
},
hasArrayAncestor,
context
} : sibling;
}
return deepMerge(included, rest);
}
loadFileSync(includePath, context) {
const { resolvedPath, root } = this.resolvePath(includePath, context);
if (context.visited.has(resolvedPath)) throw new CircularIncludeError([...context.visited, resolvedPath]);
if (context.depth >= 10) throw new ConfigIncludeError(`Maximum include depth (10) exceeded at: ${includePath}`, includePath);
const raw = this.readFile(includePath, resolvedPath, root);
return {
parsed: this.parseFile(includePath, resolvedPath, raw),
resolvedPath,
nestedContext: {
basePath: resolvedPath,
visited: /* @__PURE__ */ new Set([...context.visited, resolvedPath]),
depth: context.depth + 1
}
};
}
resolvePath(includePath, context) {
if (includePath.includes("\0")) throw new ConfigIncludeError("Include path must not contain null bytes", includePath);
if (includePath.length >= MAX_INCLUDE_PATH_LENGTH) throw new ConfigIncludeError(`Include path exceeds maximum length (${MAX_INCLUDE_PATH_LENGTH} characters)`, includePath);
const configDir = path.dirname(context.basePath);
const resolved = path.isAbsolute(includePath) ? includePath : path.resolve(configDir, includePath);
const normalized = path.normalize(resolved);
if (normalized.length >= MAX_INCLUDE_PATH_LENGTH) throw new ConfigIncludeError(`Resolved include path exceeds maximum length (${MAX_INCLUDE_PATH_LENGTH} characters)`, includePath);
const lexicalMatch = this.findContainingRoot(normalized, "rootDir");
if (!lexicalMatch) throw new ConfigIncludeError(`Include path escapes config directory: ${includePath} (root: ${this.rootDir})`, includePath);
this.resolver.onLexicalPath?.(normalized);
try {
const real = fsSync.realpathSync(normalized);
const realMatch = this.findContainingRoot(real, "rootRealDir");
if (!realMatch) throw new ConfigIncludeError(`Include path resolves outside config directory (symlink): ${includePath} (root: ${this.rootDir})`, includePath);
return {
resolvedPath: normalized,
root: realMatch
};
} catch (err) {
if (err instanceof ConfigIncludeError) throw err;
if (isMissingPathError(err)) return {
resolvedPath: normalized,
root: lexicalMatch
};
throw new ConfigIncludeReadError(`Failed to resolve include file realpath: ${includePath} (resolved: ${normalized})`, includePath, err instanceof Error ? err : void 0);
}
}
findContainingRoot(candidate, field) {
if (isPathInside$1(this.boundary.configRoot[field], candidate)) return this.boundary.configRoot;
for (const root of this.boundary.allowedRoots) if (isPathInside$1(root[field], candidate)) return root;
return null;
}
readFile(includePath, resolvedPath, root) {
try {
if (this.resolver.readFileWithGuards) return this.resolver.readFileWithGuards({
includePath,
resolvedPath,
rootRealDir: root.rootRealDir
});
return this.resolver.readFile(resolvedPath);
} catch (err) {
if (err instanceof ConfigIncludeError) throw err;
throw new ConfigIncludeReadError(`Failed to read include file: ${includePath} (resolved: ${resolvedPath})`, includePath, err instanceof Error ? err : void 0);
}
}
parseFile(includePath, resolvedPath, raw) {
try {
return this.resolver.parseJson(raw);
} catch (err) {
throw new ConfigIncludeError(`Failed to parse include file: ${includePath} (resolved: ${resolvedPath})`, includePath, err instanceof Error ? err : void 0);
}
}
};
function safeRealpath(target) {
try {
return fsSync.realpathSync(target);
} catch {
return target;
}
}
/** Capture the lexical and canonical include roots once for a resolver traversal. */
function createConfigIncludeBoundary(configPath, allowedRoots = []) {
const configRootDir = path.normalize(path.dirname(configPath));
return {
configRoot: {
rootDir: configRootDir,
rootRealDir: path.normalize(safeRealpath(configRootDir))
},
allowedRoots: allowedRoots.filter((entry) => typeof entry === "string" && entry.length > 0 && path.isAbsolute(entry)).map((entry) => {
const rootDir = path.normalize(entry);
return {
rootDir,
rootRealDir: path.normalize(safeRealpath(rootDir))
};
})
};
}
function readConfigIncludeFileWithGuards(params) {
const ioFs = params.ioFs ?? fsSync;
const maxBytes = params.maxBytes ?? MAX_INCLUDE_FILE_BYTES;
if (!canUseRootFileOpen(ioFs)) {
const raw = ioFs.readFileSync(params.resolvedPath, "utf-8");
try {
params.onResolvedPath?.(path.normalize(ioFs.realpathSync(params.resolvedPath)));
} catch {}
return raw;
}
const opened = openRootFileSync({
absolutePath: params.resolvedPath,
rootPath: params.rootRealDir,
rootRealPath: params.rootRealDir,
boundaryLabel: "config directory",
skipLexicalRootCheck: true,
rejectSymlinks: false,
maxBytes,
ioFs
});
if (!opened.ok) {
if (opened.reason === "validation") throw new ConfigIncludeError(`Include file failed security checks (regular file, max ${maxBytes} bytes, no hardlinks): ${params.includePath}`, params.includePath);
throw new ConfigIncludeReadError(`Failed to read include file: ${params.includePath} (resolved: ${params.resolvedPath})`, params.includePath, opened.error instanceof Error ? opened.error : void 0);
}
try {
const raw = ioFs.readFileSync(opened.fd, "utf-8");
params.onResolvedPath?.(path.normalize(opened.path));
return raw;
} finally {
ioFs.closeSync(opened.fd);
}
}
const defaultResolver = {
readFile: (p) => fsSync.readFileSync(p, "utf-8"),
readFileWithGuards: readConfigIncludeFileWithGuards,
parseJson: parseJsonWithJson5Fallback
};
/**
* Resolves all $include directives in a parsed config object.
*/
function resolveConfigIncludes(obj, configPath, resolver = defaultResolver, options = {}) {
return new IncludeProcessor(configPath, resolver, createConfigIncludeBoundary(configPath, options.allowedRoots ?? [])).process(obj);
}
/**
* Resolves one top-level config field through the canonical include graph while
* leaving unrelated top-level branches untouched. Early bootstrap readers use
* this when a malformed sibling must not hide an independently valid setting.
*/
function resolveConfigIncludesForTopLevelKey(obj, configPath, key, resolver = defaultResolver, options = {}) {
return new IncludeProcessor(configPath, resolver, createConfigIncludeBoundary(configPath, options.allowedRoots ?? []), /* @__PURE__ */ new Set([key])).process(obj);
}
//#endregion
//#region src/cli/profile-utils.ts
const PROFILE_NAME_RE = /^[a-z0-9][a-z0-9_-]{0,63}$/i;
function isValidProfileName(value) {
return PROFILE_NAME_RE.test(value);
}
function normalizeProfileName(raw) {
const profile = raw?.trim();
if (!profile) return null;
if (normalizeLowercaseStringOrEmpty(profile) === "default") return null;
if (!isValidProfileName(profile)) return null;
return profile;
}
/** Resolve the canonical home-scoped state root for a validated CLI profile. */
function resolveProfileStateDir(profile, env, homedir) {
const trimmed = profile.trim();
if (!isValidProfileName(trimmed)) throw new Error(`Invalid profile name: ${JSON.stringify(profile)}`);
const suffix = normalizeLowercaseStringOrEmpty(trimmed) === "default" ? "" : `-${trimmed}`;
return path.join(resolveRequiredHomeDir(env, homedir), `.openclaw${suffix}`);
}
/** Parse a positive TCP port or return null for absent/invalid input. */
function parseTcpPort(raw) {
const parsed = parseStrictPositiveInteger(raw);
if (parsed === void 0 || parsed > 65535) return null;
return parsed;
}
//#endregion
//#region src/infra/test-runtime-env.ts
/** Detects Vitest/test execution from the env shape used by local and worker processes. */
function isVitestRuntimeEnv(env = process.env) {
const vitest = env.VITEST;
return vitest === "true" || vitest === "1" || env.VITEST_POOL_ID !== void 0 || env.VITEST_WORKER_ID !== void 0 || env.NODE_ENV === "test";
}
/** Enables the shared fast-test shortcuts only inside a detected test runtime. */
function isFastTestRuntimeEnv(env = process.env) {
return env.OPENCLAW_TEST_FAST === "1" && (isVitestRuntimeEnv(env) || env !== process.env && isVitestRuntimeEnv(process.env));
}
//#endregion
//#region src/config/state-dir.ts
function resolveLegacyStateDirs(homedir = resolveRequiredHomeDir) {
return [path.join(homedir(), ".clawdbot")];
}
function resolveNewStateDir(homedir = resolveRequiredHomeDir) {
return path.join(homedir(), ".openclaw");
}
/**
* State directory for mutable data (sessions, logs, caches).
* Can be overridden via OPENCLAW_STATE_DIR.
* Default: ~/.openclaw
*/
function resolveStateDir(env = process.env, homedir = () => resolveRequiredHomeDir(env, os.homedir)) {
const effectiveHomedir = () => resolveRequiredHomeDir(env, homedir);
const override = env.OPENCLAW_STATE_DIR?.trim();
if (override) return resolveHomeRelativePath(override, {
env,
homedir: effectiveHomedir
});
return resolveStateDirFromHome(env, effectiveHomedir);
}
/** Select a default state directory from the caller's already resolved home. */
function resolveStateDirFromHome(env, effectiveHomedir) {
const newDir = resolveNewStateDir(effectiveHomedir);
if (isFastTestRuntimeEnv(env) || fsSync.existsSync(newDir)) return newDir;
return resolveLegacyStateDirs(effectiveHomedir).find((dir) => fsSync.existsSync(dir)) ?? newDir;
}
//#endregion
//#region src/config/paths.ts
/**
* Nix mode detection: When OPENCLAW_NIX_MODE=1, the gateway is running under Nix.
* In this mode:
* - No auto-install flows should be attempted
* - Missing dependencies should produce actionable Nix-specific error messages
* - Config is managed externally (read-only from Nix perspective)
*/
function resolveIsNixMode(env = process.env) {
return env.OPENCLAW_NIX_MODE === "1";
}
resolveIsNixMode();
const CONFIG_FILENAME = "openclaw.json";
const LEGACY_CONFIG_FILENAMES = ["clawdbot.json"];
function configPathsInStateDir(stateDir) {
return [CONFIG_FILENAME, ...LEGACY_CONFIG_FILENAMES].map((name) => path.join(stateDir, name));
}
function findExistingConfigPath(candidates) {
return candidates.find((candidate) => fsSync.existsSync(candidate));
}
/** Build a homedir thunk that respects OPENCLAW_HOME for the given env. */
function envHomedir(env) {
return () => resolveRequiredHomeDir(env, os.homedir);
}
function normalizePathForComparison(candidate) {
const resolved = path.resolve(candidate);
try {
return fsSync.realpathSync.native(resolved);
} catch {
return resolved;
}
}
/**
* Optional allowlist of directories that `$include` directives may resolve
* outside the config directory. Set via `OPENCLAW_INCLUDE_ROOTS` as a
* platform-delimited path list (`:` on POSIX, `;` on Windows).
*
* Each entry is tilde-expanded and resolved to an absolute path. Entries that
* cannot be resolved or that are not absolute after expansion are dropped.
*
* Returns an empty array when the var is unset or contains no usable entries,
* preserving the historical behavior where `$include` is confined to the
* directory containing `openclaw.json`.
*/
function resolveIncludeRoots(env = process.env, homedir = envHomedir(env)) {
const raw = env.OPENCLAW_INCLUDE_ROOTS?.trim();
if (!raw) return [];
const effectiveHomedir = () => resolveRequiredHomeDir(env, homedir);
const seen = /* @__PURE__ */ new Set();
const roots = [];
for (const entry of raw.split(path.delimiter)) {
const trimmed = entry.trim();
if (!trimmed) continue;
const resolved = path.resolve(resolveHomeRelativePath(trimmed, {
env,
homedir: effectiveHomedir
}));
if (seen.has(resolved)) continue;
seen.add(resolved);
roots.push(resolved);
}
return roots;
}
resolveStateDir();
/**
* Config file path (JSON or JSON5).
* Can be overridden via OPENCLAW_CONFIG_PATH.
* Default: ~/.openclaw/openclaw.json (or $OPENCLAW_STATE_DIR/openclaw.json)
*/
function resolveCanonicalConfigPath(env = process.env, stateDir) {
const override = env.OPENCLAW_CONFIG_PATH?.trim();
if (override) return resolveUserPath(override, env, envHomedir(env));
return path.join(stateDir ?? resolveStateDir(env, envHomedir(env)), CONFIG_FILENAME);
}
/**
* Resolve the active config path by preferring existing config candidates
* before falling back to the canonical path.
*/
function resolveConfigPathCandidate(env = process.env, homedir = envHomedir(env)) {
const override = env.OPENCLAW_CONFIG_PATH?.trim();
if (override) return resolveUserPath(override, env, homedir);
if (isFastTestRuntimeEnv(env)) return resolveCanonicalConfigPath(env, resolveStateDir(env, homedir));
return findExistingConfigPath(resolveDefaultConfigCandidates(env, homedir)) ?? resolveCanonicalConfigPath(env, resolveStateDir(env, homedir));
}
/**
* Active config path (prefers existing config files).
*/
function resolveConfigPath(env = process.env, stateDir, homedir = envHomedir(env)) {
const override = env.OPENCLAW_CONFIG_PATH?.trim();
if (override) return resolveUserPath(override, env, homedir);
const selectedStateDir = stateDir ?? resolveStateDir(env, envHomedir(env));
if (isFastTestRuntimeEnv(env)) return path.join(selectedStateDir, CONFIG_FILENAME);
const stateOverride = env.OPENCLAW_STATE_DIR?.trim();
const existing = findExistingConfigPath(configPathsInStateDir(selectedStateDir));
if (existing) return existing;
if (stateOverride) return path.join(selectedStateDir, CONFIG_FILENAME);
const defaultStateDir = resolveStateDir(env, homedir);
if (path.resolve(selectedStateDir) === path.resolve(defaultStateDir)) return resolveConfigPathCandidate(env, homedir);
return path.join(selectedStateDir, CONFIG_FILENAME);
}
resolveConfigPathCandidate();
/**
* Resolve default config path candidates across default locations.
* Order: explicit config path → state-dir-derived paths → new default.
*/
function resolveDefaultConfigCandidates(env = process.env, homedir = envHomedir(env)) {
const effectiveHomedir = () => resolveRequiredHomeDir(env, homedir);
const explicit = env.OPENCLAW_CONFIG_PATH?.trim();
if (explicit) return [resolveUserPath(explicit, env, effectiveHomedir)];
const openclawStateDir = env.OPENCLAW_STATE_DIR?.trim();
return [
...openclawStateDir ? [resolveUserPath(openclawStateDir, env, effectiveHomedir)] : [],
resolveNewStateDir(effectiveHomedir),
...resolveLegacyStateDirs(effectiveHomedir)
].flatMap(configPathsInStateDir);
}
const DEFAULT_GATEWAY_PORT = 18789;
/**
* Gateway lock directory inside the selected state tree.
* Default: $OPENCLAW_STATE_DIR/tmp/openclaw-<uid> (uid suffix when available).
*/
function resolveGatewayLockDir(stateDir = resolveStateDir(), uid = typeof process.getuid === "function" ? process.getuid() : void 0) {
return resolveGatewayLockDirForCanonicalStateDir(normalizePathForComparison(stateDir), uid);
}
/** Append the lock layout when the caller has already resolved the state directory. */
function resolveGatewayLockDirForCanonicalStateDir(stateDir, uid = typeof process.getuid === "function" ? process.getuid() : void 0) {
const suffix = uid != null ? `openclaw-${uid}` : "openclaw";
return path.join(stateDir, "tmp", suffix);
}
function parseGatewayPortEnvValue(raw) {
const trimmed = raw?.trim();
if (!trimmed) return null;
if (/^\d+$/.test(trimmed)) return parseTcpPort(trimmed);
const bracketedIpv6Match = trimmed.match(/^\[[^\]]+\]:(\d+)$/);
if (bracketedIpv6Match?.[1]) return parseTcpPort(bracketedIpv6Match[1]);
const firstColon = trimmed.indexOf(":");
const lastColon = trimmed.lastIndexOf(":");
if (firstColon <= 0 || firstColon !== lastColon) return null;
const suffix = trimmed.slice(firstColon + 1);
if (!/^\d+$/.test(suffix)) return null;
return parseTcpPort(suffix);
}
function resolveGatewayPort(cfg, env = process.env) {
const envRaw = env.OPENCLAW_GATEWAY_PORT?.trim();
const envPort = parseGatewayPortEnvValue(envRaw);
if (envPort !== null) return envPort;
const configPort = cfg?.gateway?.port;
if (typeof configPort === "number" && Number.isFinite(configPort) && configPort > 0) return configPort;
const profile = normalizeProfileName(env.OPENCLAW_PROFILE);
if (!profile) return DEFAULT_GATEWAY_PORT;
let hash = 2166136261;
for (const byte of Buffer.from(profile, "utf8")) hash = Math.imul(hash ^ byte, 16777619) >>> 0;
return 2e4 + hash % 4e4;
}
//#endregion
//#region src/logging/state.ts
const LOGGING_STATE_KEY = Symbol.for("openclaw.loggingState");
const APPLIED_LOGGING_CONFIG_UNOWNED = "unowned";
function createUnownedAppliedLoggingConfig() {
return APPLIED_LOGGING_CONFIG_UNOWNED;
}
function createLoggingState() {
return {
appliedConfig: createUnownedAppliedLoggingConfig(),
cachedLogger: null,
cachedSettings: null,
cachedConsoleSettings: null,
overrideSettings: null,
invalidEnvLogLevelValue: null,
consolePatched: false,
forceConsoleToStderr: false,
earlyConsoleRoutingRestore: null,
consoleTimestampPrefix: false,
consoleSubsystemFilter: null,
streamErrorHandlersInstalled: false,
rawConsole: null
};
}
const globalStore = globalThis;
const loggingState = globalStore[LOGGING_STATE_KEY] ?? createLoggingState();
if (!Object.hasOwn(loggingState, "appliedConfig")) loggingState.appliedConfig = APPLIED_LOGGING_CONFIG_UNOWNED;
globalStore[LOGGING_STATE_KEY] = loggingState;
//#endregion
//#region src/logging/config.ts
let cachedLoggingConfig;
function resolveLoggingConfigSelector() {
const env = process.env;
return [
env.OPENCLAW_CONFIG_PATH,
env.OPENCLAW_STATE_DIR,
env.OPENCLAW_HOME,
env.OPENCLAW_PROFILE,
env.HOME,
env.USERPROFILE,
env.HOMEDRIVE,
env.HOMEPATH,
env.PREFIX,
env.ANDROID_DATA,
env.OPENCLAW_TEST_FAST,
tryProcessCwd() ?? ""
].map((value) => value ?? "").join("\0");
}
function resolvePartialDiagnosticLoggingConfig(logging) {
if (!isRecord(logging)) return;
const partial = {};
if (typeof logging.consoleStyle === "string") try {
const resolved = resolveConfigEnvVars({ consoleStyle: logging.consoleStyle });
if (isRecord(resolved) && (resolved.consoleStyle === "pretty" || resolved.consoleStyle === "compact" || resolved.consoleStyle === "json")) partial.consoleStyle = resolved.consoleStyle;
} catch {}
if (Array.isArray(logging.redactPatterns)) try {
const resolved = resolveConfigEnvVars({ redactPatterns: logging.redactPatterns });
if (isRecord(resolved) && Array.isArray(resolved.redactPatterns) && resolved.redactPatterns.every((entry) => typeof entry === "string")) partial.redactPatterns = resolved.redactPatterns;
} catch {}
return Object.keys(partial).length > 0 ? partial : void 0;
}
/** Reads the logging block from config, caching by resolved config path. */
function readLoggingConfig() {
try {
if (loggingState.appliedConfig !== "unowned") return loggingState.appliedConfig;
const selector = resolveLoggingConfigSelector();
if (cachedLoggingConfig?.selector === selector) return cachedLoggingConfig.logging;
const configPath = resolveConfigPath();
if (!fsSync.existsSync(configPath)) {
cachedLoggingConfig = {
selector,
logging: void 0
};
return;
}
const parsed = parseJsonWithJson5Fallback(fsSync.readFileSync(configPath, "utf8"));
const allowedRoots = resolveIncludeRoots();
let includedConfig;
try {
includedConfig = resolveConfigIncludesForTopLevelKey(parsed, configPath, "logging", void 0, { allowedRoots });
} catch {
const directLogging = isRecord(parsed) ? parsed.logging : void 0;
if (directLogging === void 0) return;
try {
includedConfig = resolveConfigIncludes({ logging: directLogging }, configPath, void 0, { allowedRoots });
} catch {
return resolvePartialDiagnosticLoggingConfig(directLogging);
}
}
let resolvedConfig;
try {
resolvedConfig = resolveConfigEnvVars(includedConfig);
} catch {
return resolvePartialDiagnosticLoggingConfig(isRecord(includedConfig) ? includedConfig.logging : void 0);
}
const logging = isRecord(resolvedConfig) ? resolvedConfig.logging : void 0;
const resolvedLogging = isRecord(logging) ? logging : void 0;
cachedLoggingConfig = {
selector,
logging: resolvedLogging
};
return resolvedLogging;
} catch {
return;
}
}
//#endregion
//#region src/logging/redact-bounded.ts
const REDACT_REGEX_CHUNK_THRESHOLD = 32768;
const REDACT_REGEX_CHUNK_SIZE = 16384;
/** Applies a regex replacement in chunks once input crosses the redaction size threshold. */
function replacePatternBounded(text, pattern, replacer) {
if (text.length <= REDACT_REGEX_CHUNK_THRESHOLD) return text.replace(pattern, replacer);
let output;
for (let index = 0; index < text.length; index += REDACT_REGEX_CHUNK_SIZE) {
const chunk = text.slice(index, index + REDACT_REGEX_CHUNK_SIZE);
const replaced = chunk.replace(pattern, replacer);
if (output !== void 0) output += replaced;
else if (replaced !== chunk) output = text.slice(0, index) + replaced;
}
return output ?? text;
}
//#endregion
//#region src/logging/redact-edit-composition.ts
/** Compose sorted, disjoint current-value edits while retaining original source spans. */
function composeRedactionEdits(length, previous, edits) {
if (previous.length === 0) return edits;
const pieces = [];
let source = 0;
for (const edit of previous) {
if (source < edit.start) pieces.push({
start: source,
end: edit.start
});
pieces.push(edit);
source = edit.end;
}
if (source < length) pieces.push({
start: source,
end: length
});
const output = [];
let index = 0;
let offset = 0;
let position = 0;
const consume = (end, keep) => {
if (end === position) {
const piece = pieces[index];
if (!piece) return {
start: length,
end: length
};
const start = piece.replacement === void 0 ? piece.start + offset : piece.start;
return {
start,
end: piece.replacement !== void 0 && offset > 0 ? piece.end : start
};
}
let sourceStart;
let sourceEnd = 0;
while (position < end) {
const piece = expectDefined(pieces[index], "current redaction piece");
const size = piece.replacement?.length ?? piece.end - piece.start;
const count = Math.min(size - offset, end - position);
const start = piece.replacement === void 0 ? piece.start + offset : piece.start;
const finish = piece.replacement === void 0 ? start + count : piece.end;
sourceStart ??= start;
sourceEnd = finish;
if (keep && count > 0) output.push({
start,
end: finish,
replacement: piece.replacement?.slice(offset, offset + count)
});
position += count;
offset += count;
if (offset === size) {
index += 1;
offset = 0;
}
}
return {
start: sourceStart ?? sourceEnd,
end: sourceEnd
};
};
for (const edit of edits) {
consume(edit.start, true);
output.push({
...consume(edit.end, false),
replacement: edit.replacement
});
}
while (index < pieces.length) {
const piece = expectDefined(pieces[index], "remaining redaction piece");
consume(position + (piece.replacement?.length ?? piece.end - piece.start) - offset, true);
}
const composed = [];
let fragments = [];
let pending;
const flush = () => {
if (pending) {
pending.replacement = fragments.join("");
composed.push(pending);
pending = void 0;
fragments = [];
}
};
for (const piece of output) {
if (piece.replacement === void 0) {
flush();
continue;
}
const sameEmptySpan = pending && pending.start === piece.start && pending.end === piece.end && piece.start === piece.end;
if (!pending || piece.start >= pending.end && !sameEmptySpan) {
flush();
pending = {
start: piece.start,
end: piece.end,
replacement: ""
};
} else pending.end = Math.max(pending.end, piece.end);
fragments.push(piece.replacement);
}
flush();
return composed;
}
/** Project source-span replacements onto a value that already contains replacements. */
function rebaseRedactionEdits(previous, edits) {
let shift = 0;
const spans = previous.map((edit) => {
const start = edit.start + shift;
shift += edit.replacement.length - (edit.end - edit.start);
return {
...edit,
currentStart: start,
currentEnd: edit.end + shift
};
});
const boundary = (position, end) => {
let low = 0;
let high = spans.length;
while (low < high) {
const middle = low + high >>> 1;
const span = expectDefined(spans[middle], "source redaction span");
if (span.end < position || span.end === position && span.start !== span.end) low = middle + 1;
else high = middle;
}
const span = spans[low];
if (span && (position > span.start || position === span.start && span.start === span.end)) return end ? span.currentEnd : span.currentStart;
const before = spans[low - 1];
return position + (before ? before.currentEnd - before.end : 0);
};
return edits.map((edit) => ({
start: boundary(edit.start, false),
end: boundary(edit.end, true),
replacement: edit.replacement
}));
}
function mergeRedactionEdits(edits) {
edits.sort((left, right) => left.start - right.start || left.end - right.end);
const merged = [];
for (const edit of edits) {
const previous = merged.at(-1);
if (!previous || edit.start >= previous.end) merged.push({ ...edit });
else if (edit.start !== previous.start || edit.end !== previous.end || edit.replacement !== previous.replacement) {
previous.end = Math.max(previous.end, edit.end);
previous.replacement = "***";
}
}
return merged;
}
function applyRedactionEdits(value, edits) {
const parts = [];
let cursor = 0;
for (const edit of mergeRedactionEdits(edits)) {
parts.push(value.slice(cursor, edit.start), edit.replacement);
cursor = edit.end;
}
return parts.join("") + value.slice(cursor);
}
//#endregion
//#region src/logging/secret-redaction-registry.ts
const MIN_SECRET_VALUE_LENGTH = 6;
const MAX_SECRET_VALUES = 512;
const state = resolveGlobalSingleton(Symbol.for("openclaw.secretRedactionRegistry"), () => ({
registeredValues: /* @__PURE__ */ new Map(),
registryRevision: 0,
registeredValueRedactor: void 0
}));
const registrySnapshots = resolveGlobalSingleton(Symbol.for("openclaw.secretRedactionRegistrySnapshots"), () => new AsyncLocalStorage());
function invalidateMatcher() {
state.registryRevision += 1;
state.registeredValueRedactor = void 0;
}
function registerOneSecretValue(value) {
if (state.registeredValues.delete(value)) {
state.registeredValues.set(value, true);
return;
}
state.registeredValues.set(value, true);
pruneMapToMaxSize(state.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);
}
function hasRegisteredSecretValuesForRedaction() {
return (registrySnapshots.getStore()?.values ?? state.registeredValues).size > 0;
}
/** Replaces registered exact values while preserving the caller's mask convention. */
function redactRegisteredSecretValues(text, mask) {
const scoped = registrySnapshots.getStore();
if (scoped) {
scoped.redactor ??= createSecretValueRedactor(scoped.snapshot.values);
return scoped.redactor(text, mask);
}
if (!text || state.registeredValues.size === 0) return text;
state.registeredValueRedactor ??= createSecretValueRedactor([...state.registeredValues.keys()]);
return state.registeredValueRedactor(text, mask);
}
function createSecretValueRedactor(values) {
let compiledMatcher;
let firstChars;
return (text, mask) => {
if (!text || values.length === 0) return text;
let couldMatch = false;
firstChars ??= new Set(values.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 values.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, match.index)}`;
cursor = match.index + match.value.length;
}
return result + text.slice(cursor);
};
}
function resetSecretRedactionRegistryForTest() {
state.registeredValues.clear();
invalidateMatcher();
}
if (process.env.VITEST || false) globalThis[Symbol.for("openclaw.secretRedactionRegistryTestApi")] = { resetSecretRedactionRegistryForTest };
//#endregion
//#region src/logging/redact-internal-state.ts
const fullContextToolPayloadRedaction = Symbol("full-context-tool-payload-redaction");
const fullContextToolPayloadRedactionState = {
mark(loggingConfig) {
return {
...loggingConfig,
[fullContextToolPayloadRedaction]: true
};
},
isMarked(loggingConfig) {
return Boolean(loggingConfig?.[fullContextToolPayloadRedaction]);
}
};
//#endregion
//#region src/logging/redact-internal.ts
function isFullContextToolPayloadRedaction(loggingConfig) {
return fullContextToolPayloadRedactionState.isMarked(loggingConfig);
}
//#endregion
//#region src/logging/redact-json-tokens.ts
const JSON_TOKEN_RE = /"(?:[^"\\]|\\.)*"|-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?|true|false|null|[{}[\]]/g;
function skipWhitespace(text, start) {
let offset = start;
while (text[offset] === " " || text[offset] === " " || text[offset] === "\r" || text[offset] === "\n") offset += 1;
return offset;
}
function readScalarTokens(text, origins) {
const tokens = [];
const containers = [];
const root = {
key: "",
path: [],
origins,
origin: origins.value,
objectPath: true
};
const valueContext = (parent) => !parent ? root : parent.array ? {
...parent.context,
origin: parent.context.origins?.value ?? parent.context.origin,
origins: parent.context.origins?.children.get(String(parent.nextIndex++))
} : expectDefined(parent.field, "JSON object field context");
for (const match of text.matchAll(JSON_TOKEN_RE)) {
const raw = match[0];
const parent = containers.at(-1);
if (raw === "{" || raw === "[") {
const context = valueContext(parent);
const array = raw === "[";
containers.push({
array,
nextIndex: 0,
context: array ? {
...context,
objectPath: false
} : context,
start: match.index,
tokenStart: tokens.length
});
continue;
}
if (raw === "}" || raw === "]") {
const container = expectDefined(containers.pop(), "JSON container");
if (container.tokenStart === tokens.length) {
const end = match.index + 1;
const value = text.slice(container.start, end);
tokens.push({
...container.context,
isKey: false,
string: false,
value,
start: container.start,
end,
escaped: false,
edits: [],
projectedEdits: [],
currentValue: value,
currentStart: container.start,
currentEnd: end
});
}
continue;
}
const start = match.index;
const end = start + raw.length;
const string = raw.startsWith("\"");
const value = string ? JSON.parse(raw) : raw;
const next = skipWhitespace(text, end);
const isKey = string && text[next] === ":";
let context;
if (isKey) {
const container = expectDefined(parent, "JSON property container");
const inherited = container.context;
context = {
key: "",
path: [],
origin: inherited.origin,
objectPath: false,
rootKey: inherited.rootKey,
rootValueStart: inherited.rootValueStart
};
const valueStart = skipWhitespace(text, next + 1);
container.field = {
key: value,
path: [...inherited.path, value],
origins: inherited.origins?.children.get(value),
origin: inherited.origins?.value ?? inherited.origin,
objectPath: inherited.objectPath,
rootKey: containers.length === 1 ? value : inherited.rootKey,
rootValueStart: containers.length === 1 ? valueStart : inherited.rootValueStart
};
} else context = valueContext(parent);
const origin = isKey ? {
structured: false,
primitiveMask: false
} : context.origins?.value ?? context.origin;
tokens.push({
...context,
origin,
start,
end,
isKey,
string,
value,
escaped: string && raw.includes("\\"),
edits: [],
projectedEdits: [],
currentValue: value,
currentStart: start,
currentEnd: end
});
}
return tokens;
}
function readBatchTokens(input, origins, preserveLines = false) {
const tokens = [];
let offset = 0;
for (const line of input.split("\n")) {
let json = false;
try {
JSON.parse(line);
json = true;
} catch {}
if (json) for (const token of readScalarTokens(line, origins)) {
token.deferEncoding = !token.string;
token.start += offset;
token.end += offset;
token.currentStart += offset;
token.currentEnd += offset;
tokens.push(token);
}
else {
const previous = tokens.at(-1);
if (!preserveLines && previous?.raw && previous.end + 1 === offset) {
previous.value += `\n${line}`;
previous.currentValue = previous.value;
previous.end = previous.currentEnd = offset + line.length;
} else tokens.push({
raw: true,
origin: origins.value,
key: "",
path: [],
objectPath: false,
isKey: false,
string: false,
value: line,
escaped: false,
start: offset,
end: offset + line.length,
currentStart: offset,
currentEnd: offset + line.length,
currentValue: line,
edits: [],
projectedEdits: []
});
}
offset += line.length + 1;
}
return tokens;
}
//#endregion
//#region src/logging/redact-pattern-runtime.ts
function parseRedactPatternSource(raw) {
const literal = raw.match(/^\/(.+)\/([gimsuy]*)$/);
if (!literal) return [raw, "gi"];
const source = literal[1] ?? "";
const flags = literal[2] ?? "";
return [source, flags.includes("g") ? flags : `${flags}g`];
}
const OPEN_REPEAT_RE = /^\{(\d+),\}/;
const BOUNDED_REPEAT_RE = /^\{(?:\d+(?:,\d*)?|,\d+)\}/;
const HEX_DIGIT_RE = /[0-9A-Fa-f]/;
const ASSERTION_ESCAPE_CHARS = /* @__PURE__ */ new Set(["b", "B"]);
const escapeHexValue = (source, at) => {
let value = 0;
for (let index = 0; index < 4; index += 1) {
const digit = source[at + index];
if (digit === void 0 || !HEX_DIGIT_RE.test(digit)) return null;
value = value * 16 + Number.parseInt(digit, 16);
}
return value;
};
/** True when the escape at `i` is a `\uXXXX` high-surrogate escape. */
const isHighSurrogateEscape = (source, i) => {
const value = escapeHexValue(source, i + 2);
return value !== null && value >= 55296 && value <= 56319;
};
/** True when the escape at `i` is a `\uXXXX` low-surrogate escape. */
const isLowSurrogateEscape = (source, i) => {
const value = escapeHexValue(source, i + 2);
return value !== null && value >= 56320 && value <= 57343;
};
/** End index (exclusive) of the single atom introduced by the escape at `i` (a backslash), or -1. */
function escapeAtomEnd(source, i) {
const kind = source[i + 1];
if (kind === void 0) return -1;
if (ASSERTION_ESCAPE_CHARS.has(kind)) return i + 2;
if (kind === "x") return HEX_DIGIT_RE.test(source[i + 2] ?? "") && HEX_DIGIT_RE.test(source[i + 3] ?? "") ? i + 4 : i + 2;
if ((kind === "u" || kind === "p" || kind === "P") && source[i + 2] === "{") {
const close = source.indexOf("}", i + 3);
return close === -1 ? i + 2 : close + 1;
}
if (kind === "u") {
let end = i + 2;
while (end < i + 6 && HEX_DIGIT_RE.test(source[end] ?? "")) end += 1;
return end;
}
if (kind === "c") return i + 3;
if (kind === "k" && source[i + 2] === "<") {
const close = source.indexOf(">", i + 3);
return close === -1 ? i + 2 : close + 1;
}
if (kind >= "0" && kind <= "9") {
let end = i + 2;
for (let cursor = end; cursor < source.length; cursor += 1) {
const digit = source[cursor];
if (digit === void 0 || digit < "0" || digit > "9") break;
end = cursor + 1;
}
return end;
}
return i + 2;
}
/** End index (exclusive) of the character class starting at `i` (an unescaped bracket). */
function classAtomEnd(source, i) {
let cursor = i + 1;
if (source[cursor] === "^") cursor += 1;
if (source[cursor] === "]") return -1;
while (cursor < source.length) {
const char = source[cursor];
if (char === "\\") {
cursor += 2;
continue;
}
if (char === "]") return cursor + 1;
cursor += 1;
}
return source.length;
}
/**
* Rewrites open-ended repeats `X{n,}` into `X{n}X*` for flat single-character atoms `X`
* (one literal, one escaped atom, or one bracket class). Greedy `X{n,}` and `X{n}X*`
* accept the same strings in the same backtracking order, but the latter never grows one
* backtrack stack entry per repetition, so multi-megabyte runs no longer overflow.
* Bounded repeats, quantifiers after groups, and quantified atoms are left untouched.
* `flags` decides atom boundaries for literal astral characters: under `u` a surrogate pair
* is one atom; without `u` JavaScript quantifies only the trailing code unit, so the pair
* must keep its per-unit handling and the original language.
*/
function rewriteOpenEndedRepeats(source, flags = "") {
if (!source.includes("{")) return source;
let out = "";
let atomStart = -1;
let atomEnd = -1;
let i = 0;
while (i < source.length) {
const char = source[i];
if (char === "\\") {
const end = escapeAtomEnd(source, i);
if (end < 0) {
out += char;
i += 1;
atomStart = -1;
continue;
}
if (flags.includes("u") && isHighSurrogateEscape(source, i) && source[end] === "\\" && isLowSurrogateEscape(source, end)) {
const pairEnd = escapeAtomEnd(source, end);
out += source.slice(i, pairEnd);
atomStart = i;
atomEnd = pairEnd;
i = pairEnd;
continue;
}
out += source.slice(i, end);
if (ASSERTION_ESCAPE_CHARS.has(source[i + 1])) atomStart = -1;
else {
atomStart = i;
atomEnd = end;
}
i = end;
continue;
}
if (char === "[") {
const end = classAtomEnd(source, i);
if (end < 0) return source;
out += source.slice(i, end);
atomStart = i;
atomEnd = end;
i = end;
continue;
}
if (char === "{") {
const open = OPEN_REPEAT_RE.exec(source.slice(i));
if (open && atomStart >= 0) {
const atom = source.slice(atomStart, atomEnd);
out += `{${open[1]}}${atom}*`;
i += open[0].length;
if (source[i] === "?") {
out += "?";
i += 1;
}
atomStart = -1;
continue;
}
const bounded = BOUNDED_REPEAT_RE.exec(source.slice(i));
if (bounded) {
out += bounded[0];
i += bounded[0].length;
if (source[i] === "?") {
out += "?";
i += 1;
}
atomStart = -1;
continue;
}
out += char;
atomStart = i;
atomEnd = i + 1;
i += 1;
continue;
}
out += char;
if (flags.includes("u") && char >= "\ud800" && char <= "\udbff" && i + 1 < source.length && source[i + 1] >= "\udc00" && source[i + 1] <= "\udfff") {
out += source[i + 1];
atomStart = i;
atomEnd = i + 2;
i += 2;
continue;
}
if (char === "*" || char === "+" || char === "?" || char === "(" || char === ")" || char === "|" || char === "^" || char === "$") atomStart = -1;
else {
atomStart = i;
atomEnd = i + 1;
}
i += 1;
}
return out;
}
function readRedactMatch(args) {
const inputIndex = args.length > 0 && typeof args[args.length - 1] === "object" && args[args.length - 1] !== null ? args.length - 2 : args.length - 1;
const offsetIndex = inputIndex - 1;
const match = typeof args[0] === "string" ? args[0] : "";
const groups = args.slice(1, offsetIndex).map((value) => typeof value === "string" ? value : "");
const offset = typeof args[offsetIndex] === "number" ? args[offsetIndex] : -1;
return {
match,
groups,
input: typeof args[inputIndex] === "string" ? args[inputIndex] : "",
offset
};
}
const patternPrefilters = /* @__PURE__ */ new WeakMap();
function setRedactPatternPrefilter(pattern, probe) {
if (patternPrefilters.has(pattern)) return;
const source = pattern.source;
const exec = pattern.exec;
const flags = pattern instanceof RegExp ? pattern.flags : void 0;
const replace = pattern instanceof RegExp ? pattern[Symbol.replace] : void 0;
patternPrefilters.set(pattern, (text) => pattern.source !== source || pattern.exec !== exec || pattern instanceof RegExp && (pattern.flags !== flags || pattern[Symbol.replace] !== replace) || probe(text));
}
const indexedPatterns = /* @__PURE__ */ new WeakMap();
function getIndexedCaptureStart(pattern, input, match, matchOffset, captureIndex) {
if (!(pattern instanceof RegExp) || matchOffset < 0 || !input) return null;
let indexedPattern = indexedPatterns.get(pattern);
if (!indexedPattern) {
indexedPattern = new RegExp(pattern.source, `${pattern.flags.replace("d", "").replace("g", "")}dg`);
indexedPatterns.set(pattern, indexedPattern);
}
indexedPattern.lastIndex = matchOffset;
const indexedMatch = indexedPattern.exec(input);
const captureIndices = indexedMatch?.indices?.[captureIndex + 1];
return indexedMatch?.index === matchOffset && indexedMatch[0] === match && captureIndices ? captureIndices[0] - matchOffset : null;
}
function selectSecretCapture(match, groups) {
const selected = {
index: -1,
value: match
};
for (let index = 0; index < groups.length; index++) {
const value = groups[index];
if (typeof value === "string" && value.length > 0) {
selected.index = index;
selected.value = value;
}
}
return selected;
}
function getSecretCaptureStart(pattern, input, match, matchOffset, selected) {
return getIndexedCaptureStart(pattern, input, match, matchOffset, selected.index) ?? match.lastIndexOf(selected.value);
}
const globalPatterns = /* @__PURE__ */ new WeakMap();
function visitRedactMatches(text, pattern, visit) {
if (patternPrefilters.get(pattern)?.(text) === false) return;
if (!(pattern instanceof RegExp)) {
for (const match of pattern.exec(text)) visit(match, pattern);
return;
}
let regex = pattern.global ? pattern : globalPatterns.get(pattern);
if (!regex) {
regex = new RegExp(pattern.source, `${pattern.flags}g`);
globalPatterns.set(pattern, regex);
}
const unicode = regex.unicode || regex.flags.includes("v");
let cursor = 0;
while (cursor <= text.length) {
const previousIndex = regex.lastIndex;
let match;
try {
regex.lastIndex = cursor;
match = regex.exec(text);
} finally {
regex.lastIndex = previousIndex;
}
if (!match) return;
cursor = match.index + match[0].length;
if (!match[0]) {
const codePoint = text.codePointAt(cursor);
cursor += unicode && codePoint !== void 0 && codePoint > 65535 ? 2 : 1;
}
visit({
match: match[0],
groups: match.slice(1).map((group) => group ?? ""),
input: text,
offset: match.index
}, pattern);
}
}
function replaceRedactPattern(text, pattern, replace, replaceRegex) {
if (pattern instanceof RegExp) {
if (patternPrefilters.get(pattern)?.(text) === false) {
pattern.lastIndex = 0;
return text;
}
return text.replace(pattern, replaceRegex ?? ((...args) => replace(readRedactMatch(args))));
}
const parts = [];
let end = 0;
visitRedactMatches(text, pattern, (match) => {
parts.push(text.slice(end, match.offset), replace(match));
end = match.offset + match.match.length;
});
return parts.length ? parts.join("") + text.slice(end) : text;
}
function redactPemBlock(block, marker) {
const lines = block.split(/\r?\n/).filter(Boolean);
if (lines.length < 2) return "***";
return `${lines[0]}\n${marker}\n${lines[lines.length - 1]}`;
}
//#endregion
//#region src/logging/redact-json.ts
function getPatternRedactionEdits(value, pattern, getCapture) {
const edits = [];
visitRedactMatches(value, pattern, (match) => {
const capture = getCapture(match, pattern);
const edit = capture?.redact(capture);
if (edit && edit.end >= edit.start) edits.push(edit);
});
return edits;
}
function stringBoundaries(text, start, end) {
const boundaries = /* @__PURE__ */ new Map();
let decoded = 0;
for (let offset = start; offset < end; decoded += 1) {
boundaries.set(offset, decoded);
offset += text[offset] === "\\" ? text[offset + 1] === "u" ? 6 : 2 : 1;
}
boundaries.set(end, decoded);
return boundaries;
}
function decodedBoundary(text, token, offset) {
if (!token.escaped) return offset - token.start - 1;
token.boundaries ??= stringBoundaries(text, token.start + 1, token.end - 1);
return token.boundaries.get(offset);
}
function encodedBoundary(text, token, offset) {
if (!token.escaped) return token.start + 1 + offset;
if (!token.encodedBoundaries) {
token.boundaries ??= stringBoundaries(text, token.start + 1, token.end - 1);
token.encodedBoundaries = [];
for (const [encoded, decoded] of token.boundaries) token.encodedBoundaries[decoded] = encoded;
}
return expectDefined(token.encodedBoundaries[offset], "decoded JSON edit boundary");
}
function projectMessageEdits(input, tokens, message, getEdits) {
const parts = new Map(message.parts.map((part) => [part.key, part]));
const projected = [];
for (const token of tokens) {
if (!token.isKey && token.path.length === 1 && token.key === "message") continue;
const part = token.rootKey === void 0 ? void 0 : parts.get(token.rootKey);
if (!part) continue;
if (!part.json && (token.isKey || (part.messageField ? token.path.length !== 2 || token.key !== "message" : token.path.length !== 1))) continue;
const edits = getEdits(token);
for (const edit of edits) {
let start;
let end;
let replacement = edit.replacement;
if (part.json) {
const base = part.start - expectDefined(token.rootValueStart, "displayed JSON argument");
if (token.string) {
start = base + encodedBoundary(input, token, edit.start);
end = base + encodedBoundary(input, token, edit.end);
replacement = JSON.stringify(replacement).slice(1, -1);
} else {
start = base + token.start;
end = base + token.end;
replacement = JSON.stringify(replacement);
}
} else {
start = part.start + edit.start;
end = part.start + (part.primitiveLength ?? edit.end);
}
if (start < message.contentLength) projected.push({
start,
end: Math.min(end, message.contentLength),
replacement,
scalar: part.json && !token.string
});
}
}
return projected.toSorted((left, right) => left.start - right.start || left.end - right.end);
}
function firstIntersectingToken(tokens, start) {
let low = 0;
let high = tokens.length;
while (low < high) {
const middle = low + high >>> 1;
if (expectDefined(tokens[middle], "bounded JSON token search").currentEnd <= start) low = middle + 1;
else high = middle;
}
return low;
}
function updateCurrentToken(input, token) {
if (token.raw || token.deferEncoding) {
token.currentRaw = token.currentValue;
return;
}
const parts = [];
const encodedEdits = [];
let cursor = token.start + (token.string ? 1 : 0);
let encodedLength = 0;
let decodedShift = 0;
for (const edit of token.edits) {
const start = token.string ? encodedBoundary(input, token, edit.start) : token.start + edit.start;
const end = token.string ? encodedBoundary(input, token, edit.end) : token.start + edit.end;
const replacement = JSON.stringify(edit.replacement).slice(1, -1);
const encodedStart = encodedLength + start - cursor;
const encodedEnd = encodedStart + replacement.length;
encodedEdits.push({
start: encodedStart,
end: encodedEnd,
decodedStart: edit.start + decodedShift,
decodedEnd: edit.start + decodedShift + edit.replacement.length,
sourceEnd: end,
sourceDecodedEnd: edit.end,
replacement
});
parts.push(input.slice(cursor, start), replacement);
encodedLength = encodedEnd;
decodedShift += edit.replacement.length - (edit.end - edit.start);
cursor = end;
}
parts.push(input.slice(cursor, token.end - (token.string ? 1 : 0)));
token.currentRaw = `"${parts.join("")}"`;
token.encodedEdits = encodedEdits;
}
function currentDecodedBoundary(input, token, position, replacements) {
const offset = position - token.currentStart - 1;
const edits = token.encodedEdits;
if (!edits) return decodedBoundary(input, token, token.start + 1 + offset);
let low = 0;
let high = edits.length;
while (low < high) {
const middle = low + high >>> 1;
if (expectDefined(edits[middle], "current encoded edit").end < offset) low = middle + 1;
else high = middle;
}
const edit = edits[low];
if (edit && offset >= edit.start) {
const within = offset - edit.start;
if (!edit.replacement.includes("\\")) return edit.decodedStart + within;
let boundaries = replacements.get(edit.replacement);
if (!boundaries) {
boundaries = stringBoundaries(edit.replacement, 0, edit.replacement.length);
replacements.set(edit.replacement, boundaries);
}
const decoded = boundaries.get(within);
return decoded === void 0 ? void 0 : edit.decodedStart + decoded;
}
const previous = edits[low - 1];
const decoded = decodedBoundary(input, token, offset + (previous ? previous.sourceEnd - previous.end : token.start + 1));
return decoded === void 0 ? void 0 : decoded + (previous ? previous.decodedEnd - previous.sourceDecodedEnd : 0);
}
function commitPatternEdits(token) {
const pending = token.pending;
token.pending = void 0;
if (!pending) return false;
const edits = mergeRedactionEdits(pending);
const value = applyRedactionEdits(token.currentValue, edits);
if (value === token.currentValue) return false;
token.edits = composeRedactionEdits(token.value.length, token.edits, edits);
token.currentValue = value;
return true;
}
function changedRedactionEdits(previous, current) {
let index = 0;
return current.filter((edit) => {
while (previous[index] && expectDefined(previous[index], "previous configured edit").start < edit.start) index += 1;
const before = previous[index];
return !before || before.start !== edit.start || before.end !== edit.end || before.replacement !== edit.replacement;
});
}
function commitOriginalEdits(token, edits) {
if (edits.length === 0) return false;
const combined = mergeRedactionEdits([...token.edits, ...edits]);
const value = applyRedactionEdits(token.value, combined);
if (value === token.currentValue) return false;
token.edits = combined;
token.currentValue = value;
return true;
}
function updateCurrentRecord(input, current, tokens, changed) {
const parts = [];
let cursor = 0;
let shift = 0;
for (const token of tokens) {
const start = token.currentStart;
const end = token.currentEnd;
if (changed.has(token)) {
updateCurrentToken(input, token);
const raw = expectDefined(token.currentRaw, "changed JSON token");
parts.push(current.slice(cursor, start), raw);
cursor = end;
token.currentStart = start + shift;
shift += raw.length - (end - start);
} else token.currentStart = start + shift;
token.currentEnd = end + shift;
}
parts.push(current.slice(cursor));
return parts.join("");
}
function projectedStringEnd(input, tokens, message, token, start, end) {
const jsonParts = new Set(message.parts.filter((part) => part.json).map((part) => part.key));
const spans = projectMessageEdits(input, tokens, message, (source) => source.string && !source.isKey && source.rootKey !== void 0 && jsonParts.has(source.rootKey) ? [{
start: 0,
end: source.value.length,
replacement: ""
}] : []).filter((span) => span.end < message.contentLength);
const containing = rebaseRedactionEdits(token.edits, spans).filter((span) => span.start <= start && start < span.end && end <= span.end);
return containing.length === 1 ? expectDefined(containing[0], "displayed source string").end : token.currentValue.length;
}
function redactJsonRecord(input, origins, patternPhases, getCapture, legacyFieldEdits, fieldEdits, prepEdits, skipDecodedPatterns, message, batch) {
let tokens = batch ? [] : readScalarTokens(input, origins);
const messageToken = message ? tokens.find((token) => !token.isKey && token.path.length === 1 && token.key === "message") : void 0;
const replacementBoundaries = /* @__PURE__ */ new Map();
let projectedMessageEdits = [];
let projectedMessageValue = messageToken?.value ?? "";
let current = input;
const prepared = /* @__PURE__ */ new Set();
for (const token of tokens) if (commitOriginalEdits(token, prepEdits(token))) prepared.add(token);
if (prepared.size > 0) current = updateCurrentRecord(input, current, tokens, prepared);
const decodedTokens = tokens.filter((token) => !token.isKey && token.string && !skipDecodedPatterns(token, token.currentValue));
const projectMessage = () => {
if (!messageToken || !message) return false;
const projected = projectMessageEdits(input, tokens, message, (token) => {
const edits = changedRedactionEdits(token.projectedEdits, token.edits);
token.projectedEdits = token.edits;
return edits;
});
if (projected.length === 0) return false;
const sourceEdits = rebaseRedactionEdits(projectedMessageEdits, projected);
const messageEdits = rebaseRedactionEdits(messageToken.edits, projected);
let generatedIndex = 0;
for (let index = 0; index < messageEdits.length; index += 1) {
const edit = expectDefined(messageEdits[index], "projected message edit");
const sourceEdit = expectDefined(sourceEdits[index], "projected source edit");
const projection = expectDefined(projected[index], "source projection");
while (messageToken.edits[generatedIndex] && expectDefined(messageToken.edits[generatedIndex], "generated message span").start < projection.start) generatedIndex += 1;
const generated = messageToken.edits[generatedIndex];
const scalarPromotion = projection.scalar && generated?.start === projection.start && generated.end === projection.end && edit.replacement === JSON.stringify(generated.replacement);
const before = messageToken.currentValue.slice(edit.start, edit.end);
if (!scalarPromotion && before !== edit.replacement && before !== projectedMessageValue.slice(sourceEdit.start, sourceEdit.end)) edit.replacement = projection.scalar ? JSON.stringify("***") : "***";
}
messageToken.pending = messageEdits;
projectedMessageValue = applyRedactionEdits(projectedMessageValue, sourceEdits);
projectedMessageEdits = composeRedactionEdits(messageToken.value.length, projectedMessageEdits, sourceEdits);
return commitPatternEdits(messageToken);
};
for (const [phase, groups] of patternPhases.entries()) {
const changed = /* @__PURE__ */ new Set();
const pending = /* @__PURE__ */ new Set();
const add = (token, edit) => {
(token.pending ??= []).push(edit);
pending.add(token);
};
let decodedToken;
const visitDecodedMatch = (match, pattern) => {
const capture = getCapture(match, pattern);
const edit = capture?.redact(capture);
if (edit && edit.end >= edit.start) add(decodedToken, edit);
};
for (const group of groups) {
if (group.couldMatch && !group.couldMatch(current)) continue;
for (const pattern of group.patterns) {
if (phase === 0) for (decodedToken of decodedTokens) visitRedactMatches(decodedToken.currentValue, pattern, visitDecodedMatch);
else visitRedactMatches(current, pattern, (match) => {
const capture = getCapture(match, pattern);
if (!capture || capture.end < capture.start) return;
if (batch && tokens.length === 0) tokens = readBatchTokens(input, origins, batch.preserveLines);
for (let index = firstIntersectingToken(tokens, capture.start); index < tokens.length; index += 1) {
const token = expectDefined(tokens[index], "serialized capture token");
if (token.currentStart >= capture.end) break;
const unquoted = token.raw || token.deferEncoding;
const padding = unquoted ? 0 : 1;
const captureInsideToken = capture.start >= token.currentStart + padding && capture.end <= token.currentEnd - padding;
if (batch && token.isKey && !captureInsideToken) continue;
const value = token.currentValue;
if (!token.raw && !token.string && token.edits.length === 0) {
add(token, {
start: 0,
end: value.length,
replacement: "***"
});
continue;
}
let startPosition = Math.max(capture.start, token.currentStart + padding);
let start = unquoted ? startPosition - token.currentStart : currentDecodedBoundary(input, token, startPosition, replacementBoundaries);
let endPosition = Math.min(capture.end, token.currentEnd - padding);
let end = unquoted ? endPosition - token.currentStart : currentDecodedBoundary(input, token, endPosition, replacementBoundaries);
if (start === void 0 || end === void 0) {
while (start === void 0) start = currentDecodedBoundary(input, token, --startPosition, replacementBoundaries);
while (end === void 0) end = currentDecodedBoundary(input, token, ++endPosition, replacementBoundaries);
const maskEnd = token === messageToken && message ? projectedStringEnd(input, tokens, message, token, start, end) : value.length;
add(token, {
start,
end: maskEnd,
replacement: "***"
});
continue;
}
if (end < start || batch && end === start) continue;
const { start: captureStart, end: captureEnd } = capture;
const edit = capture.redact({
start,
end,
value: current.slice(Math.max(captureStart, token.currentStart + padding), Math.min(captureEnd, token.currentEnd - padding))
});
if (!edit) continue;
let replacement = "***";
if (captureInsideToken) try {
replacement = unquoted ? edit.replacement : JSON.parse(`"${edit.replacement}"`);
} catch {}
add(token, {
...edit,
replacement
});
}
});
if (pending.size === 0) continue;
for (const token of pending) if (!commitPatternEdits(token)) pending.delete(token);
else if (phase === 0) changed.add(token);
if (phase !== 0 && pending.size > 0) current = updateCurrentRecord(input, current, tokens, pending);
pending.clear();
}
}
for (const token of tokens) if (phase === 0) {
token.pending = legacyFieldEdits(token, token.currentValue);
if (commitPatternEdits(token)) changed.add(token);
} else if (commitOriginalEdits(token, fieldEdits(token))) changed.add(token);
if ((phase !== 0 || prepared.size > 0 || changed.size > 0) && projectMessage() && messageToken) changed.add(messageToken);
if (changed.size > 0) current = updateCurrentRecord(input, current, tokens, changed);
}
if (messageToken && message) {
const finished = message.finish(messageToken.currentValue);
if (finished !== messageToken.currentValue) return applyRedactionEdits(current, [{
start: messageToken.currentStart,
end: messageToken.currentEnd,
replacement: JSON.stringify(finished)
}]);
}
return applyRedactionEdits(current, tokens.flatMap((token) => token.deferEncoding && token.edits.length > 0 ? [{
start: token.currentStart,
end: token.currentEnd,
replacement: JSON.stringify(token.currentValue)
}] : []));
}
//#endregion
//#region src/logging/redact-pem.ts
const PEM_REDACT_PATTERN_SOURCE = String.raw`-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]+?-----END [A-Z ]*PRIVATE KEY-----`;
function createState() {
return {
scanner: {
offset: 0,
phase: "search",
dashes: 0,
keyword: "BEGIN ",
keywordOffset: 0,
start: 0,
suffix: ""
},
matchEnd: 0
};
}
function resetScanner(scanner, char) {
scanner.phase = "search";
scanner.dashes = char === "-" ? 1 : 0;
scanner.suffix = "";
}
function* readDelimiters(text, scanner) {
for (let index = 0; index < text.length; index += 1) {
if (scanner.phase === "search" && scanner.dashes === 0) {
const nextDash = text.indexOf("-", index);
if (nextDash === -1) {
scanner.offset += text.length - index;
return;
}
scanner.offset += nextDash - index;
index = nextDash;
}
const code = text.charCodeAt(index);
const char = code >= 97 && code <= 122 ? String.fromCharCode(code - 32) : text.charAt(index);
const offset = scanner.offset++;
switch (scanner.phase) {
case "search":
if (char === "-") scanner.dashes = Math.min(5, scanner.dashes + 1);
else if (scanner.dashes === 5 && (char === "B" || char === "E")) {
scanner.phase = "keyword";
scanner.keyword = char === "B" ? "BEGIN " : "END ";
scanner.keywordOffset = 1;
scanner.start = offset - 5;
} else scanner.dashes = 0;
break;
case "keyword":
if (char !== scanner.keyword.charAt(scanner.keywordOffset)) resetScanner(scanner, char);
else if (++scanner.keywordOffset === scanner.keyword.length) {
scanner.phase = "label";
scanner.suffix = "";
}
break;
case "label":
if (char >= "A" && char <= "Z" || char === " ") scanner.suffix = (scanner.suffix + char).slice(-11);
else if (char === "-" && scanner.suffix === "PRIVATE KEY") {
scanner.phase = "close";
scanner.dashes = 1;
} else resetScanner(scanner, char);
break;
case "close": if (char !== "-") resetScanner(scanner, char);
else if (++scanner.dashes === 5) {
scanner.phase = "search";
scanner.suffix = "";
yield {
kind: scanner.keyword === "BEGIN " ? "begin" : "end",
start: scanner.start,
end: offset + 1
};
}
}
}
}
function consumeDelimiter(state, delimiter) {
if (!state.open) {
if (delimiter.kind === "begin" && delimiter.start >= state.matchEnd) state.open = {
start: delimiter.start,
end: delimiter.end
};
return;
}
if (delimiter.kind !== "end" || delimiter.start <= state.open.end) return;
const block = {
start: state.open.start,
end: delimiter.end,
bodyStart: state.open.end,
bodyEnd: delimiter.start
};
state.open = void 0;
state.matchEnd = delimiter.end;
return block;
}
function* matchPem(text, context, incomplete) {
const state = {
...context,
scanner: { ...context.scanner },
open: context.open && { ...context.open }
};
const origin = state.scanner.offset;
for (const delimiter of readDelimiters(text, state.scanner)) {
const block = consumeDelimiter(state, delimiter);
if (!block) continue;
const start = Math.max(0, (incomplete ? block.bodyStart : block.start) - origin);
const end = (incomplete ? block.bodyEnd : block.end) - origin;
if (end <= start) continue;
yield {
match: text.slice(start, end),
groups: [],
input: text,
offset: start,
...incomplete ? { replacement: "…redacted…" } : {}
};
}
if (incomplete && state.open) {
const start = Math.max(0, state.open.end - origin);
if (start < text.length) yield {
match: text.slice(start),
groups: [],
input: text,
offset: start,
replacement: "…redacted…"
};
}
}
const PEM_REDACT_MATCHER = {
source: PEM_REDACT_PATTERN_SOURCE,
exec(text) {
return matchPem(text, createState(), false);
},
createContext() {
let whenClosed = false;
let whenOpen = true;
return {
prepend() {
const closed = createState();
const open = createState();
open.open = {
start: -1,
end: -1
};
return {
consume(text) {
for (const delimiter of readDelimiters(text, closed.scanner)) {
consumeDelimiter(closed, delimiter);
consumeDelimiter(open, delimiter);
}
},
finish() {
const nextClosed = closed.open ? whenOpen : whenClosed;
const nextOpen = open.open ? whenOpen : whenClosed;
whenClosed = nextClosed;
whenOpen = nextOpen;
return whenClosed === whenOpen;
}
};
},
pattern: {
source: PEM_REDACT_PATTERN_SOURCE,
exec(text) {
const state = createState();
if (whenClosed) state.open = {
start: -1,
end: -1
};
return matchPem(text, state, true);
}
}
};
}
};
//#endregion
//#region src/logging/redact-patterns.ts
const PAYMENT_CREDENTIAL_ENV_KEYS = String.raw`CARD[_-]?NUMBER|CARD[_-]?CVC|CARD[_-]?CVV|CVC|CVV|SECURITY[_-]?CODE|PAYMENT[_-]?CREDENTIAL|SHARED[_-]?PAYMENT[_-]?TOKEN`;
const PAYMENT_CREDENTIAL_QUERY_KEYS = String.raw`card[-_]?number|card[-_]?cvc|card[-_]?cvv|cvc|cvv|security[-_]?code|payment[-_]?credential|shared[-_]?payment[-_]?token`;
const PAYMENT_CREDENTIAL_JSON_KEYS = String.raw`cardNumber|card_number|cardCvc|card_cvc|cardCvv|card_cvv|cvc|cvv|securityCode|security_code|paymentCredential|payment_credential|sharedPaymentToken|shared_payment_token`;
const AWS_SECRET_ACCESS_KEY_FIELD_KEYS = String.raw`aws[-_]?secret[-_]?access[-_]?key|awsSecretAccessKey|SecretAccessKey`;
const AUTH_QUERY_KEYS = String.raw`access[-_]?token|auth[-_]?token|hook[-_]?token|refresh[-_]?token|id[-_]?token|api[-_]?key|apikey|client[-_]?secret|app[-_]?secret|private[-_]?key|${AWS_SECRET_ACCESS_KEY_FIELD_KEYS}|credential|authorization|token|key|secret|password|pass|passwd|auth|jwt|session|code|signature|x[-_]?amz[-_]?(?:signature|security[-_]?token)`;
const FORM_BODY_FIRST_PAIR_KEYS = String.raw`${AUTH_QUERY_KEYS}|app[-_]?secret|credential|${PAYMENT_CREDENTIAL_QUERY_KEYS}`;
const STANDALONE_ASSIGNMENT_SECRET_KEYS = String.raw`access_token|refresh_token|id_token|auth[-_]?token|hook[-_]?token|api[-_]?key|client[-_]?secret|app[-_]?secret|private[-_]?key|authorization|jwt|token|secret|password|pass|passwd|credential|${PAYMENT_CREDENTIAL_QUERY_KEYS}`;
const CONFIG_ASSIGNMENT_SECRET_KEYS = String.raw`access[-_]?token|refresh[-_]?token|id[-_]?token|auth[-_]?token|hook[-_]?token|api[-_]?(?:key|secret)|client[-_]?secret|app[-_]?secret|private[-_]?key|secret[-_]?key|key[-_]?material|authorization|jwt|token|secret|password|passphrase|pass|passwd|credential|${PAYMENT_CREDENTIAL_QUERY_KEYS}`;
const CONFIG_COLON_ASSIGNMENT_SECRET_KEYS = String.raw`access[-_]?token|refresh[-_]?token|id[-_]?token|auth[-_]?token|hook[-_]?token|api[-_]?(?:key|secret)|client[-_]?secret|app[-_]?secret|private[-_]?key|secret[-_]?key|key[-_]?material|authorization|jwt|token|secret|password|passphrase|passwd|credential|${PAYMENT_CREDENTIAL_QUERY_KEYS}`;
const CONFIG_DIRECT_ASSIGNMENT_SECRET_KEYS = String.raw`access-token|refresh-token|id-token|auth-token|hook-token|api[-_]?(?:key|secret)|secret[-_]?key|key[-_]?material|passphrase`;
const CONFIG_PREFIXED_PASSWORD_ASSIGNMENT_SECRET_KEYS = String.raw`password|passphrase|pass|passwd`;
const CLI_SECRET_FLAG_KEYS = String.raw`${AWS_SECRET_ACCESS_KEY_FIELD_KEYS}|api[-_]?key|hook[-_]?token|access[-_]?token|refresh[-_]?token|id[-_]?token|token|secret|password|passwd|credential|private[-_]?key|client[-_]?secret|${PAYMENT_CREDENTIAL_QUERY_KEYS}`;
const BODY_SECRET_KEYS = /* @__PURE__ */ new Set([
"access_token",
"auth_token",
"awssecretaccesskey",
"aws_secret_access_key",
"hook_token",
"refresh_token",
"id_token",
"token",
"api_key",
"apikey",
"client_secret",
"app_secret",
"password",
"pass",
"passwd",
"auth",
"jwt",
"session",
"code",
"signature",
"x_amz_signature",
"x_amz_security_token",
"secret",
"secretaccesskey",
"credential",
"private_key",
"authorization",
"key",
"card_number",
"card_cvc",
"card_cvv",
"cvc",
"cvv",
"security_code",
"payment_credential",
"shared_payment_token"
]);
const FORM_BODY_KEY_INVISIBLE_CHARS = String.raw`\p{C}\u00A0\u1680\u2000-\u200A\u202F\u205F\u3000\u115F\u1160\u3164\uFFA0`;
const ENV_ASSIGNMENT_REDACT_PATTERN = String.raw`/\b[A-Z0-9_]*(?:KEY|TOKEN|SECRET|PASSWORD|PASSWD|${PAYMENT_CREDENTIAL_ENV_KEYS})\b\s*[=:]\s*(["']?)([^\s"'\\]+)\1/g`;
const ESCAPED_ENV_ASSIGNMENT_REDACT_PATTERN = String.raw`/\b[A-Z0-9_]*(?:KEY|TOKEN|SECRET|PASSWORD|PASSWD|${PAYMENT_CREDENTIAL_ENV_KEYS})\b\s*[=:]\s*\\+(["'])([^\s"'\\]+)\\+\1/g`;
const STANDALONE_ASSIGNMENT_QUOTED_REDACT_PATTERN = String.raw`(^|[\s,;({\["])(?:${STANDALONE_ASSIGNMENT_SECRET_KEYS})=(["'\x60])((?:(?!\2)[^\r\n])+)\2`;
const STANDALONE_ASSIGNMENT_REDACT_PATTERN = String.raw`(^|[\s,;({\["])(?:${STANDALONE_ASSIGNMENT_SECRET_KEYS})=(["'\x60]?[^\s&#"'\x60<>]+)`;
const CONFIG_QUOTED_ASSIGNMENT_SECRET_KEYS = String.raw`access[-_]?token|refresh[-_]?token|id[-_]?token|auth[-_]?token|hook[-_]?token|api[-_]?(?:key|secret)|secret[-_]?key|key[-_]?material|authorization|jwt|token|secret|password|passphrase|pass|passwd|${PAYMENT_CREDENTIAL_QUERY_KEYS}`;
const CONFIG_QUOTED_ASSIGNMENT_REDACT_PATTERN = String.raw`/(^|[\s,{])(?:(?:${CONFIG_QUOTED_ASSIGNMENT_SECRET_KEYS})(?:\s*:\s*|\s+=\s*|=\s*)|[a-z0-9][a-z0-9._-]{0,79}[-_](?:${CONFIG_PREFIXED_PASSWORD_ASSIGNMENT_SECRET_KEYS})\s*[:=]\s*|[a-z0-9_.-]{1,80}\.(?:${CONFIG_ASSIGNMENT_SECRET_KEYS})\s*[:=]\s*)(["'\x60])((?:(?!\2)[^\r\n])+)\2/g`;
const CONFIG_ASSIGNMENT_REDACT_PATTERN = String.raw`/(^|[\s,{])(?:(?:${CONFIG_COLON_ASSIGNMENT_SECRET_KEYS})\s*:\s*|(?:${CONFIG_ASSIGNMENT_SECRET_KEYS})(?:\s+=\s*|=\s+))([^\s#"'\x60<>]+)/g`;
const CONFIG_DIRECT_ASSIGNMENT_REDACT_PATTERN = String.raw`/(^|[\s,{])(?:${CONFIG_DIRECT_ASSIGNMENT_SECRET_KEYS})=([^\s#"'\x60<>]+)/g`;
const CONFIG_PREFIXED_PASSWORD_ASSIGNMENT_REDACT_PATTERN = String.raw`/(^|[\s,{])[a-z0-9][a-z0-9._-]{0,79}[-_](?:${CONFIG_PREFIXED_PASSWORD_ASSIGNMENT_SECRET_KEYS})\s*[:=]\s*([^\s#"'\x60<>]+)/g`;
const CONFIG_NAMESPACED_ASSIGNMENT_REDACT_PATTERN = String.raw`/(^|[\s,{])[a-z0-9_.-]{1,80}\.(?:${CONFIG_ASSIGNMENT_SECRET_KEYS})\s*[:=]\s*([^\s#"'\x60<>]+)/g`;
const STRUCTURED_JSON_SECRET_REDACT_PATTERN = String.raw`"(?:apiKey|api_key|apiToken|api_token|bearerToken|bearer_token|token|secret|password|passwd|${AWS_SECRET_ACCESS_KEY_FIELD_KEYS}|credential|authorization|proxy-authorization|cookie|set-cookie|x-api-key|x-auth-token|accessToken|access_token|refreshToken|refresh_token|idToken|id_token|authToken|auth_token|clientSecret|client_secret|privateKey|private_key|secret_value|raw_secret|secret_input|key_material)"\s*:\s*"([^"]+)"`;
const STRUCTURED_JSON_PAYMENT_REDACT_PATTERN = String.raw`"(?:${PAYMENT_CREDENTIAL_JSON_KEYS})"\s*:\s*"([^"]+)"`;
const AMBIGUOUS_QUOTED_SECRET_FIELD_REDACT_PATTERN = String.raw`(^|[\s,{])["']?(?:api[-_]key|access[-_]token|refresh[-_]token|id[-_]token|authToken|auth[-_]token|clientSecret|client[-_]secret|appSecret|app[-_]secret|private[-_]key|credential|authorization|secret[-_]value|raw[-_]secret|secret[-_]input|key[-_]material)["']?\s*[:=]\s*(["'])([^"'\r\n]+)\2`;
const AMBIGUOUS_QUOTED_AUTH_FIELD_REDACT_PATTERN = String.raw`(^|[\s,{])["']?(?:authorization|proxy-authorization|cookie|set-cookie|x-api-key|x-auth-token)["']?\s*[:=]\s*(["'])([^"'\r\n]+)\2`;
const BASE64_SAFE_TOKEN_BOUNDARY = String.raw`(^|[^A-Za-z0-9])`;
const base64SafeToken = (token) => String.raw`${BASE64_SAFE_TOKEN_BOUNDARY}(?=${token})(?<!;base64,[A-Za-z0-9+/=]*)(${token})`;
const IDENTIFIER_SAFE_TOKEN_BOUNDARY = String.raw`(^|[^A-Za-z0-9_])`;
function isAwsValueCharacter(char) {
const code = char.charCodeAt(0);
return code >= 65 && code <= 90 || code >= 97 && code <= 122 || code >= 48 && code <= 57 || char === "/" || char === "+" || char === "=";
}
const AWS_SECRET_ACCESS_KEY_VALUE_RE = /(?=[A-Za-z0-9/+=]{0,39}[A-Z])(?=[A-Za-z0-9/+=]{0,39}[a-z])(?=[A-Za-z0-9/+=]{0,39}[0-9/+=])(?=[A-Za-z0-9/+=]{0,39}[G-Zg-z/+=])[A-Za-z0-9/+=]{40}/u;
function couldMatchAwsSecretAccessKey(text) {
for (let end = 39; end < text.length;) {
let cursor = end;
while (cursor > end - 40 && isAwsValueCharacter(text[cursor])) cursor--;
if (cursor === end - 40) return AWS_SECRET_ACCESS_KEY_VALUE_RE.test(text);
end = cursor + 40;
}
return false;
}
const AWS_VALUE_WHITESPACE_RE = /\s/;
const AWS_URL_SCHEME_CHARACTER_RE = /[A-Za-z0-9+.-]/;
const AWS_URL_SCHEME_START_RE = /[A-Za-z]/;
function* matchAwsSecretAccessKeys(text) {
if (!couldMatchAwsSecretAccessKey(text)) return;
const closingDelimiters = {
"\"": "\"",
"'": "'",
"`": "`",
"<": ">",
"(": ")",
"[": "]",
"{": "}"
};
let runStart = -1;
let schemeStart = -1;
let url;
let backslashes = 0;
const runs = [];
for (let index = 0; index <= text.length; index++) {
const char = text[index] ?? "";
const whitespace = index === text.length || AWS_VALUE_WHITESPACE_RE.test(char);
const escapeDepth = backslashes;
backslashes = char === "\\" ? backslashes + 1 : 0;
if (char && isAwsValueCharacter(char)) {
if (runStart === -1) runStart = index;
} else if (runStart !== -1) {
if (index - runStart === 40) runs.push({
start: runStart,
end: index,
origin: runStart,
url
});
else if (index - runStart > 40 && char === "@" && text[index - 41] === "/") runs.push({
start: index - 40,
end: index,
origin: runStart,
url
});
runStart = -1;
}
if (whitespace) {
if (url) url.end = Math.min(url.end, index);
for (const run of runs) {
const context = run.url;
const publicUrl = context && !context.hasAt && context.portValid && (context.portStart === -1 || (context.authorityEnd === -1 ? context.end : context.authorityEnd) > context.portStart + 1);
const before = text[run.start - 1] ?? "";
const after = text[run.end] ?? "";
const slashCredential = run.start !== run.origin;
const portCredential = context && context.portStart !== -1 && run.origin === context.portStart + 1 && context.authorityEnd > run.origin && run.end > context.authorityEnd && after === "@";
if (!slashCredential && (before === "_" || isAwsValueCharacter(before)) || after === "_" || text.slice(run.origin - 8, run.origin) === ";base64," || publicUrl && !portCredential && run.start >= context.start && run.end <= context.end) continue;
const match = text.slice(run.start, run.end);
if (AWS_SECRET_ACCESS_KEY_VALUE_RE.test(match)) yield {
match,
groups: [match],
input: text,
offset: run.start
};
}
runs.length = 0;
schemeStart = -1;
url = void 0;
continue;
}
if (url && index >= url.start) {
if (char === url.closing && escapeDepth === url.closingEscapeDepth) {
url.end = Math.min(url.end, index);
url = void 0;
} else {
if ("?#\"'<>`|()[]{}".includes(char)) url.end = Math.min(url.end, index);
url.queryOrFragment ||= char === "?" || char === "#";
if (url.authorityEnd === -1) {
url.hasAt ||= char === "@";
if ("/?#".includes(char)) url.authorityEnd = index;
else if (index < url.end) {
if (char === ":") {
url.portValid &&= url.portStart === -1;
url.portStart = index;
} else if (url.portStart !== -1 && (char < "0" || char > "9")) url.portValid = false;
}
}
}
}
if (AWS_URL_SCHEME_CHARACTER_RE.test(char)) {
if (schemeStart === -1) schemeStart = index;
} else {
if ((!url || index >= url.end && !url.queryOrFragment) && char === ":" && text.startsWith("//", index + 1) && schemeStart !== -1 && AWS_URL_SCHEME_START_RE.test(text[schemeStart])) {
const opening = text[schemeStart - 1];
let closingEscapeDepth = 0;
for (let offset = schemeStart - 2; offset >= 0 && text[offset] === "\\"; offset--) closingEscapeDepth++;
url = {
start: index + 3,
end: text.length,
authorityEnd: -1,
portStart: -1,
portValid: true,
hasAt: false,
queryOrFragment: false,
closing: opening ? closingDelimiters[opening] : void 0,
closingEscapeDepth
};
}
schemeStart = -1;
}
}
}
const AWS_SECRET_ACCESS_KEY_MATCHER = Object.freeze({
source: "aws-secret-access-key",
exec: matchAwsSecretAccessKeys,
couldMatch: couldMatchAwsSecretAccessKey
});
const BARE_PASS_KEY_RE = new RegExp(String.raw`(?<![A-Za-z0-9])(pass|${CONFIG_COLON_ASSIGNMENT_SECRET_KEYS})\s*:\s*`, "gi");
const BARE_PASS_VALUE_RE = /[^\s#"'\x60<>]+/y;
const ASCII_WORD_CHAR_RE = /[A-Za-z0-9]/;
const INLINE_WHITESPACE_RE = /[ \t\r\n]/;
function* matchBarePassAssignments(text) {
if (!/pass\s*:/.test(text)) return;
const keys = [...text.matchAll(BARE_PASS_KEY_RE)];
if (keys.length === 0) return;
let next = 0;
let assignmentSeen = false;
for (let index = 0; index < text.length; index++) {
const key = keys[next];
if (!key) return;
if (index === key.index) {
next++;
const before = text[index - 1] ?? "";
const owned = key[1] !== "pass" || before === "." || before === "-" || before === "_";
if (!(!owned && !assignmentSeen && before === " " && ASCII_WORD_CHAR_RE.test(text[index - 2] ?? ""))) {
assignmentSeen = true;
let end = index + key[0].length;
if (!owned) {
BARE_PASS_VALUE_RE.lastIndex = end;
const value = BARE_PASS_VALUE_RE.exec(text)?.[0];
if (value) {
end += value.length;
yield {
match: text.slice(index, end),
groups: [value],
input: text,
offset: index
};
}
}
let following = keys[next];
while (following && following.index < end) {
next++;
following = keys[next];
}
index = end - 1;
continue;
}
}
const char = text[index];
if (char === "\n" || char === "\r") assignmentSeen = false;
else if (char === "=") assignmentSeen = true;
else if (char === ":" && !INLINE_WHITESPACE_RE.test(text[index + 1] ?? " ")) assignmentSeen = true;
}
}
const BARE_PASS_ASSIGNMENT_MATCHER = Object.freeze({
source: "bare-pass-assignment",
exec: matchBarePassAssignments
});
const TELEGRAM_BOT_TOKEN_REDACT_PATTERN = String.raw`\bbot(\d{6,}:[A-Za-z0-9_-]{20,})\b`;
const TELEGRAM_TOKEN_REDACT_PATTERN = String.raw`\b(\d{6,}:[A-Za-z0-9_-]{20,})\b`;
const CREDENTIAL_STYLE_HEADER_KEYS = "x-goog-api-key|api-key|apikey|x-api-token|x-access-token";
const GATEWAY_SECURITY_HEADER_KEYS = "X-OpenClaw-Token|x-pomerium-jwt-assertion|X-Api-Key|X-Auth-Token";
const CREDENTIAL_HEADER_FIELD_RE = new RegExp(`^(?:${CREDENTIAL_STYLE_HEADER_KEYS}|${GATEWAY_SECURITY_HEADER_KEYS})$`, "i");
const LOG_HEADER_BOUNDARY_PATTERN = String.raw`(^|[^A-Za-z0-9_?&-]|\\{1,64}[rn])`;
const CREDENTIAL_STYLE_COLON_HEADER_REDACT_PATTERN = String.raw`${LOG_HEADER_BOUNDARY_PATTERN}(?:${CREDENTIAL_STYLE_HEADER_KEYS})${HTTP_AUTH_SERIALIZED_QUOTE_PATTERN}[ \t]*:${HTTP_AUTH_OPTIONAL_VALUE_WHITESPACE_PATTERN}${HTTP_AUTH_SERIALIZED_QUOTE_PATTERN}([^\s\\"',;]+)`;
const CREDENTIAL_STYLE_EQUALS_ASSIGNMENT_REDACT_PATTERN = String.raw`${LOG_HEADER_BOUNDARY_PATTERN}(?:${CREDENTIAL_STYLE_HEADER_KEYS})${HTTP_AUTH_SERIALIZED_QUOTE_PATTERN}[ \t]*=${HTTP_AUTH_OPTIONAL_VALUE_WHITESPACE_PATTERN}${HTTP_AUTH_SERIALIZED_QUOTE_PATTERN}([^\s\\"',;]+)`;
const GATEWAY_SECURITY_COLON_HEADER_REDACT_PATTERN = String.raw`${LOG_HEADER_BOUNDARY_PATTERN}(?:${GATEWAY_SECURITY_HEADER_KEYS})\s*:\s*([^\s"',;]+)`;
const GATEWAY_SECURITY_EQUALS_ASSIGNMENT_REDACT_PATTERN = String.raw`${LOG_HEADER_BOUNDARY_PATTERN}(?:${GATEWAY_SECURITY_HEADER_KEYS})\s*=\s*([^\s"',;]+)`;
const FORM_AWARE_EQUALS_ASSIGNMENT_PATTERN_SOURCES = /* @__PURE__ */ new Set([CREDENTIAL_STYLE_EQUALS_ASSIGNMENT_REDACT_PATTERN, GATEWAY_SECURITY_EQUALS_ASSIGNMENT_REDACT_PATTERN]);
const HTTP_AUTH_HEADER_REDACT_PATTERNS = [
String.raw`${HTTP_AUTH_HEADER_BOUNDARY_PATTERN}Proxy-Authorization${HTTP_AUTH_SERIALIZED_QUOTE_PATTERN}[ \t]*[:=]${HTTP_AUTH_OPTIONAL_VALUE_WHITESPACE_PATTERN}${HTTP_AUTH_SERIALIZED_QUOTE_PATTERN}${HTTP_AUTH_SCHEME_PATTERN}${HTTP_AUTH_REQUIRED_VALUE_WHITESPACE_PATTERN}(${HTTP_AUTH_OPAQUE_CREDENTIAL_PATTERN})`,
String.raw`${HTTP_AUTH_HEADER_BOUNDARY_PATTERN}Proxy-Authorization${HTTP_AUTH_SERIALIZED_QUOTE_PATTERN}[ \t]*[:=]${HTTP_AUTH_OPTIONAL_VALUE_WHITESPACE_PATTERN}${HTTP_AUTH_SERIALIZED_QUOTE_PATTERN}(${HTTP_AUTH_OPAQUE_CREDENTIAL_PATTERN})[ \t]*(?=${HTTP_AUTH_SERIALIZED_QUOTE_PATTERN}(?:$|[,;)}\]]|\r?\n(?![ \t])))`,
String.raw`${HTTP_AUTH_HEADER_BOUNDARY_PATTERN}Authorization${HTTP_AUTH_SERIALIZED_QUOTE_PATTERN}[ \t]*[:=]${HTTP_AUTH_OPTIONAL_VALUE_WHITESPACE_PATTERN}${HTTP_AUTH_SERIALIZED_QUOTE_PATTERN}(?!(?:Bearer|Basic|Bot)(?=${HTTP_AUTH_REQUIRED_VALUE_WHITESPACE_PATTERN}))${HTTP_AUTH_SCHEME_PATTERN}${HTTP_AUTH_REQUIRED_VALUE_WHITESPACE_PATTERN}(${HTTP_AUTH_OPAQUE_CREDENTIAL_PATTERN})`,
String.raw`${HTTP_AUTH_HEADER_BOUNDARY_PATTERN}Authorization${HTTP_AUTH_SERIALIZED_QUOTE_PATTERN}[ \t]*[:=]${HTTP_AUTH_OPTIONAL_VALUE_WHITESPACE_PATTERN}${HTTP_AUTH_SERIALIZED_QUOTE_PATTERN}(?!(?:Bearer|Basic|Bot)(?=${HTTP_AUTH_REQUIRED_VALUE_WHITESPACE_PATTERN}))(${HTTP_AUTH_OPAQUE_CREDENTIAL_PATTERN})[ \t]*(?=${HTTP_AUTH_SERIALIZED_QUOTE_PATTERN}(?:$|[,;)}\]]|\r?\n(?![ \t])))`,
CREDENTIAL_STYLE_COLON_HEADER_REDACT_PATTERN,
CREDENTIAL_STYLE_EQUALS_ASSIGNMENT_REDACT_PATTERN
];
const AUTHORIZATION_BEARER_REDACT_PATTERN = String.raw`Authorization${HTTP_AUTH_SERIALIZED_QUOTE_PATTERN}[ \t]*[:=]${HTTP_AUTH_LEGACY_VALUE_WHITESPACE_PATTERN}${HTTP_AUTH_SERIALIZED_QUOTE_PATTERN}Bearer${HTTP_AUTH_REQUIRED_VALUE_WHITESPACE_PATTERN}(${HTTP_AUTH_OPAQUE_CREDENTIAL_PATTERN})`;
const AUTHORIZATION_BASIC_REDACT_PATTERN = String.raw`Authorization${HTTP_AUTH_SERIALIZED_QUOTE_PATTERN}[ \t]*[:=]${HTTP_AUTH_LEGACY_VALUE_WHITESPACE_PATTERN}${HTTP_AUTH_SERIALIZED_QUOTE_PATTERN}Basic${HTTP_AUTH_REQUIRED_VALUE_WHITESPACE_PATTERN}(${HTTP_AUTH_OPAQUE_CREDENTIAL_PATTERN})`;
const AUTHORIZATION_BOT_REDACT_PATTERN = String.raw`Authorization${HTTP_AUTH_SERIALIZED_QUOTE_PATTERN}[ \t]*[:=]${HTTP_AUTH_LEGACY_VALUE_WHITESPACE_PATTERN}${HTTP_AUTH_SERIALIZED_QUOTE_PATTERN}Bot${HTTP_AUTH_REQUIRED_VALUE_WHITESPACE_PATTERN}(${HTTP_AUTH_OPAQUE_CREDENTIAL_PATTERN})`;
const STANDALONE_BEARER_REDACT_PATTERN = String.raw`\bBearer\s+([-A-Za-z0-9._~+/=]{18,})(?![-A-Za-z0-9._~+/=])`;
const SHELL_REFERENCE_PRESERVING_PATTERN_SOURCES = /* @__PURE__ */ new Set([
ENV_ASSIGNMENT_REDACT_PATTERN,
ESCAPED_ENV_ASSIGNMENT_REDACT_PATTERN,
STANDALONE_ASSIGNMENT_QUOTED_REDACT_PATTERN,
STANDALONE_ASSIGNMENT_REDACT_PATTERN
]);
const DEFAULT_REDACT_FIELD_PATTERNS = [
ENV_ASSIGNMENT_REDACT_PATTERN,
ESCAPED_ENV_ASSIGNMENT_REDACT_PATTERN,
STRUCTURED_JSON_SECRET_REDACT_PATTERN,
STRUCTURED_JSON_PAYMENT_REDACT_PATTERN,
AMBIGUOUS_QUOTED_SECRET_FIELD_REDACT_PATTERN,
AMBIGUOUS_QUOTED_AUTH_FIELD_REDACT_PATTERN,
String.raw`--(?:${CLI_SECRET_FLAG_KEYS})=([^\s"']+)`,
String.raw`--(?:${CLI_SECRET_FLAG_KEYS})\s+(?!(?:or|and)\b(?=\s+--))(["']?)([^\s"']+)\1`,
AUTHORIZATION_BEARER_REDACT_PATTERN,
AUTHORIZATION_BASIC_REDACT_PATTERN,
AUTHORIZATION_BOT_REDACT_PATTERN,
...HTTP_AUTH_HEADER_REDACT_PATTERNS,
GATEWAY_SECURITY_COLON_HEADER_REDACT_PATTERN,
GATEWAY_SECURITY_EQUALS_ASSIGNMENT_REDACT_PATTERN,
STANDALONE_BEARER_REDACT_PATTERN,
String.raw`\b(?:https?|wss?|ftp):\/\/[^\/\s:@]*:([^\/\s@]+)@`,
String.raw`\b(?:postgres(?:ql)?|mysql|mongodb(?:\+srv)?|rediss?|amqps?):\/\/[^:\s/@]*:([^@\s]+)@`,
String.raw`(^|[\s,;])(?:${FORM_BODY_FIRST_PAIR_KEYS})=([^&\s]+)(?=&[A-Za-z_][A-Za-z0-9_.-]*=)`,
STANDALONE_ASSIGNMENT_QUOTED_REDACT_PATTERN,
STANDALONE_ASSIGNMENT_REDACT_PATTERN,
CONFIG_QUOTED_ASSIGNMENT_REDACT_PATTERN,
CONFIG_ASSIGNMENT_REDACT_PATTERN,
BARE_PASS_ASSIGNMENT_MATCHER,
CONFIG_DIRECT_ASSIGNMENT_REDACT_PATTERN,
CONFIG_PREFIXED_PASSWORD_ASSIGNMENT_REDACT_PATTERN,
CONFIG_NAMESPACED_ASSIGNMENT_REDACT_PATTERN,
PEM_REDACT_PATTERN_SOURCE,
String.raw`(^|[\s,{])["']?(?:${AWS_SECRET_ACCESS_KEY_FIELD_KEYS})["']?\s*[:=]\s*(["']?)([A-Za-z0-9/+=]{40})(?![A-Za-z0-9/+=])\2`
];
const CONFIG_KEY_PROBE = new RegExp(String.raw`[:=](?<=(?:${CONFIG_ASSIGNMENT_SECRET_KEYS})\s*[:=])`);
const CONFIG_QUOTED_PROBE = new RegExp(String.raw`[:=](?<=(?:${CONFIG_ASSIGNMENT_SECRET_KEYS})\s*[:=])\s*["'\x60]`);
const CONFIG_NAMESPACED_PROBE = new RegExp(String.raw`[:=](?<=\.(?:${CONFIG_ASSIGNMENT_SECRET_KEYS})\s*[:=])`);
const STANDALONE_ASSIGNMENT_PROBE = new RegExp(String.raw`=(?<=(?:^|[\s,;({\["])(?:${STANDALONE_ASSIGNMENT_SECRET_KEYS})=)`, "i");
const REDACT_PATTERN_PREFILTERS = new Map([
[ENV_ASSIGNMENT_REDACT_PATTERN, (text) => /KEY|TOKEN|SECRET|PASSWORD|PASSWD|CARD|CVC|CVV|SECURITY|PAYMENT/.test(text)],
[ESCAPED_ENV_ASSIGNMENT_REDACT_PATTERN, (text) => text.includes("\\")],
[STANDALONE_ASSIGNMENT_QUOTED_REDACT_PATTERN, (text) => /[=]["'`]/.test(text)],
[STANDALONE_ASSIGNMENT_REDACT_PATTERN, (text) => STANDALONE_ASSIGNMENT_PROBE.test(text)],
[CONFIG_QUOTED_ASSIGNMENT_REDACT_PATTERN, (text) => /["'`]/.test(text) && CONFIG_QUOTED_PROBE.test(text)],
[CONFIG_ASSIGNMENT_REDACT_PATTERN, (text) => (text.includes(":") || text.includes("=")) && CONFIG_KEY_PROBE.test(text)],
[CONFIG_DIRECT_ASSIGNMENT_REDACT_PATTERN, (text) => text.includes("=")],
[CONFIG_PREFIXED_PASSWORD_ASSIGNMENT_REDACT_PATTERN, (text) => /[-_](?:password|passphrase|pass|passwd)\s*[:=]/.test(text)],
[CONFIG_NAMESPACED_ASSIGNMENT_REDACT_PATTERN, (text) => CONFIG_NAMESPACED_PROBE.test(text)],
[AMBIGUOUS_QUOTED_SECRET_FIELD_REDACT_PATTERN, (text) => /["']/.test(text)],
[AMBIGUOUS_QUOTED_AUTH_FIELD_REDACT_PATTERN, (text) => /["']/.test(text)],
...HTTP_AUTH_HEADER_REDACT_PATTERNS.slice(0, 4).map((raw) => [raw, (text) => /authorization/i.test(text)]),
[CREDENTIAL_STYLE_COLON_HEADER_REDACT_PATTERN, (text) => /api-key|apikey|api-token|access-token/i.test(text)],
[CREDENTIAL_STYLE_EQUALS_ASSIGNMENT_REDACT_PATTERN, (text) => /api-key|apikey|api-token|access-token/i.test(text)],
[STRUCTURED_JSON_SECRET_REDACT_PATTERN, (text) => text.includes("\"")],
[STRUCTURED_JSON_PAYMENT_REDACT_PATTERN, (text) => text.includes("\"")],
[GATEWAY_SECURITY_COLON_HEADER_REDACT_PATTERN, (text) => /x-openclaw-token|x-pomerium-jwt-assertion|x-api-key|x-auth-token/i.test(text)],
[GATEWAY_SECURITY_EQUALS_ASSIGNMENT_REDACT_PATTERN, (text) => /x-openclaw-token|x-pomerium-jwt-assertion|x-api-key|x-auth-token/i.test(text)]
]);
const VENDOR_TOKEN_REDACT_PATTERNS = [
String.raw`\b(sk-[A-Za-z0-9_-]{8,})\b`,
String.raw`(ghp_[A-Za-z0-9]{10,})`,
String.raw`(github_pat_[A-Za-z0-9_]{10,})`,
String.raw`(gho_[A-Za-z0-9]{10,})`,
String.raw`(ghu_[A-Za-z0-9]{10,})`,
String.raw`(ghs_[A-Za-z0-9]{10,})`,
String.raw`(ghr_[A-Za-z0-9]{10,})`,
String.raw`(glpat-[A-Za-z0-9._=\-]{20,})`,
String.raw`(gloas-(?:[A-Fa-f0-9]{65,}|[A-Za-z0-9_-]{64}|[A-Fa-f0-9]{32,}))`,
String.raw`(gldt-[A-Za-z0-9_-]{20,})`,
String.raw`(glcbt-[A-Za-z0-9]{1,5}_[A-Za-z0-9_-]{20,})`,
String.raw`(glptt-[A-Za-z0-9_-]{40,})`,
String.raw`(glft-(?:[A-Za-z0-9_-]{20,}|[a-h0-9]+-[0-9]+_))`,
String.raw`(glimt-[A-Za-z0-9_-]{25,})`,
String.raw`(glagent-[A-Za-z0-9_-]{50,})`,
String.raw`(glwt-[A-Za-z0-9_-]{20,})`,
String.raw`(glsoat-[A-Za-z0-9_-]{20,})`,
String.raw`(glffct-[A-Za-z0-9_-]{20,})`,
String.raw`(glrt-[A-Za-z0-9._-]{20,})`,
String.raw`(glrtr?-[A-Za-z0-9_-]{27,300}\.[0-9a-z]{2}\.[0-9a-z]{9})`,
String.raw`(GR1348941[A-Za-z0-9_-]{20,})`,
String.raw`(_gitlab_session=[A-Za-z0-9%._-]{20,})`,
String.raw`(xox[baprs]-[A-Za-z0-9-]{10,})`,
String.raw`(xapp-[A-Za-z0-9-]{10,})`,
String.raw`(https:\/\/hooks\.slack\.com\/(?:services\/T[A-Z0-9]+\/B[A-Z0-9]+|workflows\/T[A-Z0-9]+\/A[A-Z0-9]+\/[0-9]{17,19})\/[A-Za-z0-9]{20,})`,
String.raw`(https:\/\/discord(?:app)?\.com\/api\/webhooks\/[0-9]{17,20}\/[A-Za-z0-9_-]{60,})`,
String.raw`discord(?:.|\n|\r){0,40}?\b([A-Za-z0-9_-]{24}\.[A-Za-z0-9_-]{6}\.[A-Za-z0-9_-]{27})\b`,
String.raw`(gsk_[A-Za-z0-9_-]{10,})`,
String.raw`(AIza[0-9A-Za-z\-_]{20,})`,
String.raw`(ya29\.[0-9A-Za-z_\-./+=]{10,})`,
String.raw`(1//0[0-9A-Za-z_\-./+=]{10,})`,
String.raw`(eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,})`,
String.raw`(pplx-[A-Za-z0-9_-]{10,})`,
String.raw`(fal_[A-Za-z0-9_-]{10,})`,
String.raw`${IDENTIFIER_SAFE_TOKEN_BOUNDARY}(fc-[A-Za-z0-9]{10,})`,
String.raw`(bb_live_[A-Za-z0-9_-]{10,})`,
base64SafeToken(String.raw`gAAAA[A-Za-z0-9_=-]{20,}`),
String.raw`(sk_live_[A-Za-z0-9]{10,})`,
String.raw`(sk_test_[A-Za-z0-9]{10,})`,
String.raw`(rk_live_[A-Za-z0-9]{10,})`,
String.raw`(SG\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,})`,
String.raw`(npm_[A-Za-z0-9]{10,})`,
String.raw`(pypi-[A-Za-z0-9_-]{10,})`,
String.raw`(dop_v1_[A-Za-z0-9]{10,})`,
String.raw`(doo_v1_[A-Za-z0-9]{10,})`,
String.raw`(dor_v1_[A-Za-z0-9]{10,})`,
String.raw`(dp\.(?:ct|pt|sa|scim|audit)\.[A-Za-z0-9]{40,44})`,
String.raw`(dp\.st\.[A-Za-z0-9]{40,44})`,
String.raw`(dp\.st\.[a-z0-9_-]{2,35}\.[A-Za-z0-9]{40,44})`,
String.raw`(dckr_(?:pat|oat)_[A-Za-z0-9_-]{27,32})`,
String.raw`(bkua_[a-z0-9]{40})`,
String.raw`(CCIPAT_[A-Za-z0-9]{22}_[A-Fa-f0-9]{40})`,
String.raw`(sbp_[a-z0-9]{40})`,
base64SafeToken(String.raw`dapi[0-9a-f]{32}(?:-\d)?`),
String.raw`(dd[pw]_[A-Za-z0-9]{36})`,
String.raw`(glsa_[A-Za-z0-9_]{41})`,
String.raw`(glc_eyJ[A-Za-z0-9+/=]{60,160})`,
String.raw`(nfp_[A-Za-z0-9_]{36})`,
String.raw`(CFPAT-[A-Za-z0-9_\-]{40,})`,
base64SafeToken(String.raw`ATCTT3xFfG[A-Za-z0-9+/=_-]+=[A-Za-z0-9]{8}`),
base64SafeToken(String.raw`ATATT[A-Za-z0-9+/=_-]+=[A-Za-z0-9]{8}`),
base64SafeToken(String.raw`ATBB[A-Za-z0-9_=.-]{16,}`),
String.raw`(BBDC-[A-Za-z0-9+/@_-]{40,50})`,
String.raw`(HRKU-AA[A-Za-z0-9_-]{20,})`,
String.raw`(pat-(?:eu|na)1-[A-Za-z0-9]{8}\-[A-Za-z0-9]{4}\-[A-Za-z0-9]{4}\-[A-Za-z0-9]{4}\-[A-Za-z0-9]{12})`,
String.raw`(apify_api_[A-Za-z0-9\-]{20,})`,
String.raw`(FlyV1 fm\d+_[A-Za-z0-9+/=,_-]{100,})`,
String.raw`(fio-u-[A-Za-z0-9_-]{40,})`,
String.raw`(^|[^A-Za-z0-9_])(am_[A-Za-z0-9_-]{10,})`,
String.raw`(^|[^A-Za-z0-9_])(sk_[A-Za-z0-9_]{10,})`,
String.raw`(tvly-[A-Za-z0-9]{10,})`,
String.raw`(exa_[A-Za-z0-9]{10,})`,
String.raw`(syt_[A-Za-z0-9]{10,})`,
String.raw`(retaindb_[A-Za-z0-9]{10,})`,
String.raw`(hsk-[A-Za-z0-9]{10,})`,
String.raw`(mem0_[A-Za-z0-9]{10,})`,
String.raw`(brv_[A-Za-z0-9]{10,})`,
String.raw`(xai-[A-Za-z0-9]{30,})`,
String.raw`${IDENTIFIER_SAFE_TOKEN_BOUNDARY}(fw-[A-Za-z0-9]{30,})`,
String.raw`${IDENTIFIER_SAFE_TOKEN_BOUNDARY}(fw_[A-Za-z0-9]{30,})`,
String.raw`${IDENTIFIER_SAFE_TOKEN_BOUNDARY}(fpk_[A-Za-z0-9]{30,})`,
base64SafeToken(String.raw`AKIA[A-Z0-9]{16}`),
base64SafeToken(String.raw`ASIA[A-Z0-9]{16}`),
String.raw`(AKID[A-Za-z0-9]{10,})`,
String.raw`(LTAI[A-Za-z0-9]{10,})`,
String.raw`(hf_[A-Za-z0-9]{10,})`,
String.raw`(api_org_[A-Za-z0-9]{20,})`,
String.raw`(r8_[A-Za-z0-9]{10,})`,
TELEGRAM_BOT_TOKEN_REDACT_PATTERN,
TELEGRAM_TOKEN_REDACT_PATTERN
];
const CHUNK_UNSAFE_PATTERN_SOURCES = /* @__PURE__ */ new Set([
TELEGRAM_BOT_TOKEN_REDACT_PATTERN,
TELEGRAM_TOKEN_REDACT_PATTERN,
AUTHORIZATION_BEARER_REDACT_PATTERN,
AUTHORIZATION_BASIC_REDACT_PATTERN,
AUTHORIZATION_BOT_REDACT_PATTERN,
STANDALONE_BEARER_REDACT_PATTERN,
...HTTP_AUTH_HEADER_REDACT_PATTERNS,
...VENDOR_TOKEN_REDACT_PATTERNS.filter((source) => source.startsWith(BASE64_SAFE_TOKEN_BOUNDARY))
]);
const DEFAULT_REDACT_PATTERNS = [
...DEFAULT_REDACT_FIELD_PATTERNS,
...VENDOR_TOKEN_REDACT_PATTERNS,
AWS_SECRET_ACCESS_KEY_MATCHER
];
const TOOL_PAYLOAD_AMBIGUOUS_ASSIGNMENT_PATTERNS = /* @__PURE__ */ new Set([
ENV_ASSIGNMENT_REDACT_PATTERN,
ESCAPED_ENV_ASSIGNMENT_REDACT_PATTERN,
STRUCTURED_JSON_SECRET_REDACT_PATTERN,
AMBIGUOUS_QUOTED_SECRET_FIELD_REDACT_PATTERN,
AMBIGUOUS_QUOTED_AUTH_FIELD_REDACT_PATTERN,
STANDALONE_ASSIGNMENT_QUOTED_REDACT_PATTERN,
STANDALONE_ASSIGNMENT_REDACT_PATTERN,
CONFIG_QUOTED_ASSIGNMENT_REDACT_PATTERN,
CONFIG_ASSIGNMENT_REDACT_PATTERN,
CONFIG_DIRECT_ASSIGNMENT_REDACT_PATTERN,
CONFIG_PREFIXED_PASSWORD_ASSIGNMENT_REDACT_PATTERN,
CONFIG_NAMESPACED_ASSIGNMENT_REDACT_PATTERN
]);
/** Programmatic counterparts of the ambiguous assignment rules: kept out of tool payloads, preserved in source. */
const AMBIGUOUS_ASSIGNMENT_MATCHERS = /* @__PURE__ */ new Set([BARE_PASS_ASSIGNMENT_MATCHER]);
const TOOL_PAYLOAD_REDACT_PATTERNS = DEFAULT_REDACT_PATTERNS.filter((pattern) => typeof pattern === "string" ? !TOOL_PAYLOAD_AMBIGUOUS_ASSIGNMENT_PATTERNS.has(pattern) : !AMBIGUOUS_ASSIGNMENT_MATCHERS.has(pattern));
//#endregion
//#region src/logging/redact-linear-matchers.ts
const JS_WHITESPACE_CODES = /* @__PURE__ */ new Set([
9,
10,
11,
12,
13,
32,
160,
5760,
8232,
8233,
8239,
8287,
12288,
65279
]);
function isJsWhitespaceAt(text, index) {
if (index >= text.length) return false;
const code = text.charCodeAt(index);
return JS_WHITESPACE_CODES.has(code) || code >= 8192 && code <= 8202;
}
function isJsWhitespaceChar(char) {
return char !== void 0 && isJsWhitespaceAt(char, 0);
}
function isAlnumChar(char) {
if (char === void 0) return false;
const code = char.charCodeAt(0);
return code >= 48 && code <= 57 || code >= 65 && code <= 90 || code >= 97 && code <= 122;
}
function isLetterChar(char) {
if (char === void 0) return false;
const code = char.charCodeAt(0);
return code >= 65 && code <= 90 || code >= 97 && code <= 122;
}
function isWordChar(char) {
return char === "_" || isAlnumChar(char);
}
function isHexChar(char) {
if (char === void 0) return false;
const code = char.charCodeAt(0);
return code >= 48 && code <= 57 || code >= 65 && code <= 70 || code >= 97 && code <= 102;
}
function isDigitChar(char) {
return char !== void 0 && char.charCodeAt(0) >= 48 && char.charCodeAt(0) <= 57;
}
function isBase64UrlChar(char) {
return isAlnumChar(char) || char === "+" || char === "/" || char === "=";
}
const prefixRegexPools = /* @__PURE__ */ new Map();
/** Reuse compiled prefixes without sharing mutable lastIndex with a reentrant scan. */
function* scanPrefixOccurrences(text, prefix) {
let pool = prefixRegexPools.get(prefix);
if (!pool) {
pool = [];
prefixRegexPools.set(prefix, pool);
}
const re = pool.pop() ?? new RegExp(prefix, "gi");
try {
re.lastIndex = 0;
for (let match = re.exec(text); match; match = re.exec(text)) yield match.index;
} finally {
re.lastIndex = 0;
pool.push(re);
}
}
/** The `;base64,` exemption under the shared case-insensitive compilation of these rules. */
function isDataUrlPrefix(text, runStart) {
return runStart >= 8 && text.slice(runStart - 8, runStart).toLowerCase() === ";base64,";
}
/** End of the maximal run of `isMember` characters starting at `start`. */
function runEndOf(text, start, isMember) {
let end = start;
while (end < text.length && isMember(text[end])) end += 1;
return end;
}
/**
* Shared matcher for the `BASE64_SAFE_TOKEN_BOUNDARY` vendor rules. The boundary group is
* checked per candidate in O(1); the `(?<!;base64,[A-Za-z0-9+/=]*)` exemption walks each
* base64 run once because candidates advance left to right and share runs, replacing the
* unbounded lookbehind that rescanned the whole run from every candidate. The token-end
* function is created per exec() call so concurrent redactions never share scanner state.
*/
function makeBase64SafeTokenMatcher(source, prefix, makeTokenEnd) {
function* exec(text) {
const tokenEnd = makeTokenEnd();
let runStart = -1;
let runEnd = -1;
let searchFloor = 0;
for (const candidate of scanPrefixOccurrences(text, prefix)) {
if (candidate < searchFloor) continue;
if (candidate > 0 && isAlnumChar(text[candidate - 1])) continue;
if (candidate > 0) {
let runAtCandidate;
if (isBase64UrlChar(text[candidate - 1])) {
if (candidate - 1 >= runStart && candidate - 1 < runEnd) runAtCandidate = runStart;
else {
let walk = candidate - 1;
while (walk > 0 && isBase64UrlChar(text[walk - 1])) walk -= 1;
runStart = walk;
runEnd = runEndOf(text, candidate, isBase64UrlChar);
runAtCandidate = walk;
}
} else runAtCandidate = candidate;
if (isDataUrlPrefix(text, runAtCandidate)) continue;
}
const end = tokenEnd(text, candidate);
if (end === null) continue;
const matchStart = candidate > 0 ? candidate - 1 : 0;
yield {
match: text.slice(matchStart, end),
groups: [candidate > 0 ? text[candidate - 1] : "", text.slice(candidate, end)],
input: text,
offset: matchStart
};
searchFloor = end + 1;
}
}
return Object.freeze({
source,
exec
});
}
const GAAAA_VALUE_CHAR = (char) => isAlnumChar(char) || char === "_" || char === "=" || char === "-";
const ATBB_VALUE_CHAR = (char) => isAlnumChar(char) || char === "_" || char === "=" || char === "." || char === "-";
const AT_VALUE_CHAR = (char) => isAlnumChar(char) || char === "+" || char === "/" || char === "=" || char === "-" || char === "_";
function fixedTailTokenEnd(tailStart, tailLength, isMember) {
return (text, start) => {
const end = start + tailStart + tailLength;
for (let index = start + tailStart; index < end; index++) if (!isMember(text[index])) return null;
return end;
};
}
/** `dapi` + 32 hex digits + an optional `-<digit>`; the optional tail is greedy like the regex. */
function dapiTokenEnd(text, start) {
const bodyEnd = start + 4 + 32;
for (let index = start + 4; index < bodyEnd; index++) if (!isHexChar(text[index])) return null;
if (text[bodyEnd] === "-" && isDigitChar(text[bodyEnd + 1])) return bodyEnd + 2;
return bodyEnd;
}
/**
* `ATCTT3xFfG`/`ATATT` value: one or more class characters, then `=`, then exactly eight
* alphanumerics. Greedy backtracking lands on the rightmost `=` in the run whose next
* eight characters are alphanumerics; each run is scanned once and candidates share it.
*/
function makeAtEqualsTokenMatcher(source, prefix) {
const prefixLength = prefix.length;
return makeBase64SafeTokenMatcher(source, prefix, () => {
let runStart = -1;
let runEnd = -1;
let equals = [];
return (text, start) => {
const valueStart = start + prefixLength;
if (valueStart < runStart || valueStart >= runEnd) {
runEnd = runEndOf(text, valueStart, AT_VALUE_CHAR);
runStart = valueStart;
equals = [];
for (let index = valueStart; index < runEnd; index++) if (text[index] === "=" && index + 9 <= text.length && isAlnumChar(text[index + 1]) && isAlnumChar(text[index + 2]) && isAlnumChar(text[index + 3]) && isAlnumChar(text[index + 4]) && isAlnumChar(text[index + 5]) && isAlnumChar(text[index + 6]) && isAlnumChar(text[index + 7]) && isAlnumChar(text[index + 8])) equals.push(index);
}
const minimum = valueStart + 1;
const split = equals[equals.length - 1];
return split !== void 0 && split >= minimum ? split + 9 : null;
};
});
}
const GAAAA_TOKEN_MATCHER = makeBase64SafeTokenMatcher("vendor-token gAAAA (linear)", "gAAAA", () => (text, start) => {
const end = runEndOf(text, start + 5, GAAAA_VALUE_CHAR);
return end - (start + 5) >= 20 ? end : null;
});
const ATBB_TOKEN_MATCHER = makeBase64SafeTokenMatcher("vendor-token ATBB (linear)", "ATBB", () => (text, start) => {
const end = runEndOf(text, start + 4, ATBB_VALUE_CHAR);
return end - (start + 4) >= 16 ? end : null;
});
const AKIA_TOKEN_MATCHER = makeBase64SafeTokenMatcher("vendor-token AKIA (linear)", "AKIA", () => fixedTailTokenEnd(4, 16, isAlnumChar));
const ASIA_TOKEN_MATCHER = makeBase64SafeTokenMatcher("vendor-token ASIA (linear)", "ASIA", () => fixedTailTokenEnd(4, 16, isAlnumChar));
const DAPI_TOKEN_MATCHER = makeBase64SafeTokenMatcher("vendor-token dapi (linear)", "dapi", () => dapiTokenEnd);
const ATCTT_TOKEN_MATCHER = makeAtEqualsTokenMatcher("vendor-token ATCTT3xFfG (linear)", "ATCTT3xFfG");
const ATATT_TOKEN_MATCHER = makeAtEqualsTokenMatcher("vendor-token ATATT (linear)", "ATATT");
const JWT_REDACT_PATTERN = String.raw`(eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,})`;
const JWT_SEGMENT_CHAR = (char) => isAlnumChar(char) || char === "_" || char === "-";
/** JWT rule: each `eyJ` candidate parses its three segments forward; class runs are shared. */
const JWT_MATCHER = Object.freeze({
source: "jwt (linear)",
*exec(text) {
const firstRun = {
start: -1,
end: -1
};
const secondRun = {
start: -1,
end: -1
};
const thirdRun = {
start: -1,
end: -1
};
const segmentEnd = (position, run) => {
if (position >= run.start && position < run.end) return run.end;
const end = runEndOf(text, position, JWT_SEGMENT_CHAR);
run.start = position;
run.end = end;
return end;
};
let searchFloor = 0;
for (const candidate of scanPrefixOccurrences(text, "eyj")) {
if (candidate < searchFloor) continue;
const firstEnd = segmentEnd(candidate + 3, firstRun);
if (firstEnd - (candidate + 3) < 10 || text[firstEnd] !== ".") continue;
const secondEnd = segmentEnd(firstEnd + 1, secondRun);
if (secondEnd - (firstEnd + 1) < 10 || text[secondEnd] !== ".") continue;
const thirdEnd = segmentEnd(secondEnd + 1, thirdRun);
if (thirdEnd - (secondEnd + 1) < 10) continue;
const token = text.slice(candidate, thirdEnd);
yield {
match: token,
groups: [token],
input: text,
offset: candidate
};
searchFloor = thirdEnd;
}
}
});
const URL_USERINFO_REDACT_PATTERN = String.raw`\b(?:https?|wss?|ftp):\/\/[^\/\s:@]*:([^\/\s@]+)@`;
const CONNECTION_STRING_REDACT_PATTERN = String.raw`\b(?:postgres(?:ql)?|mysql|mongodb(?:\+srv)?|rediss?|amqps?):\/\/[^:\s/@]*:([^@\s]+)@`;
const URL_USERINFO_SCHEMES = /* @__PURE__ */ new Set([
"http",
"https",
"ws",
"wss",
"ftp"
]);
const CONNECTION_STRING_SCHEMES = /* @__PURE__ */ new Set([
"postgres",
"postgresql",
"mysql",
"mongodb",
"mongodb+srv",
"redis",
"rediss",
"amqp",
"amqps"
]);
/**
* Shared matcher for the URL-userinfo and connection-string password rules. Both regexes are
* deterministic parses (each character class excludes its own terminators), so every `://`
* candidate parses forward once; failed password scans are memoized because later candidates
* share their tail regions.
*/
function makeUserInfoMatcher(source, schemes, isPassTerminator) {
function* exec(text) {
let searchFloor = 0;
let failedPassScanEnd = -1;
let separator = text.indexOf("://");
while (separator !== -1) {
let schemeStart = separator;
while (schemeStart > 0 && isWordChar(text[schemeStart - 1])) schemeStart -= 1;
let scheme = text.slice(schemeStart, separator).toLowerCase();
let matchStart = schemeStart;
if (!schemes.has(scheme) && schemeStart >= 8 && scheme === "srv" && text.slice(schemeStart - 8, schemeStart).toLowerCase() === "mongodb+") {
scheme = "mongodb+srv";
matchStart = schemeStart - 8;
}
if (separator >= searchFloor && schemes.has(scheme) && (matchStart === 0 || !isWordChar(text[matchStart - 1]))) {
let cursor = separator + 3;
while (cursor < text.length && text[cursor] !== ":" && text[cursor] !== "/" && text[cursor] !== "@" && !isJsWhitespaceAt(text, cursor)) cursor += 1;
if (text[cursor] === ":") {
const passStart = cursor + 1;
if (passStart >= failedPassScanEnd) {
const passEnd = runEndOf(text, passStart, (char) => !isPassTerminator(char));
if (text[passEnd] === "@" && passEnd > passStart) {
yield {
match: text.slice(matchStart, passEnd + 1),
groups: [text.slice(passStart, passEnd)],
input: text,
offset: matchStart
};
searchFloor = passEnd + 1;
separator = text.indexOf("://", passEnd + 1);
continue;
}
failedPassScanEnd = passEnd;
}
}
}
separator = text.indexOf("://", separator + 1);
}
}
return Object.freeze({
source,
exec
});
}
const URL_USERINFO_MATCHER = makeUserInfoMatcher("url-userinfo (linear)", URL_USERINFO_SCHEMES, (char) => char === "/" || char === "@" || isJsWhitespaceChar(char));
const CONNECTION_STRING_MATCHER = makeUserInfoMatcher("connection-string (linear)", CONNECTION_STRING_SCHEMES, (char) => char === "@" || isJsWhitespaceChar(char));
const FORM_BODY_FIRST_PAIR_REDACT_PATTERN = String.raw`(^|[\s,;])(?:${FORM_BODY_FIRST_PAIR_KEYS})=([^&\s]+)(?=&[A-Za-z_][A-Za-z0-9_.-]*=)`;
const FORM_FIRST_PAIR_KEYS_RE = new RegExp(`^(?:${FORM_BODY_FIRST_PAIR_KEYS})$`, "i");
const FORM_FIRST_PAIR_KEY_CHAR = (char) => isAlnumChar(char) || char === "-" || char === "_";
const FORM_FIRST_PAIR_TAIL_CHAR = (char) => isAlnumChar(char) || char === "_" || char === "." || char === "-";
/** The lookahead `&[A-Za-z_][A-Za-z0-9_.-]*=` is a deterministic forward check. */
function isFormPairBoundary(text, ampersand) {
const first = text[ampersand + 1];
if (!(first !== void 0 && (isLetterChar(first) || first === "_"))) return false;
return text[runEndOf(text, ampersand + 2, FORM_FIRST_PAIR_TAIL_CHAR)] === "=";
}
/**
* Form first-pair rule: the value class excludes `&` and whitespace, so the greedy value
* always ends at the first `&` or whitespace and the lookahead is checked there alone;
* per-candidate value runs are shared so long runs are read once.
*/
const FORM_BODY_FIRST_PAIR_MATCHER = Object.freeze({
source: "form-body-first-pair (linear)",
*exec(text) {
let runStart = -1;
let runEnd = -1;
const valueEnd = (start) => {
if (start >= runStart && start < runEnd) return runEnd;
const end = runEndOf(text, start, (char) => char !== "&" && !isJsWhitespaceChar(char));
runStart = start;
runEnd = end;
return end;
};
let searchFloor = 0;
let separator = text.indexOf("=");
while (separator !== -1) {
let keyStart = separator;
while (keyStart > 0 && FORM_FIRST_PAIR_KEY_CHAR(text[keyStart - 1])) keyStart -= 1;
const key = text.slice(keyStart, separator);
if (separator >= searchFloor && key.length > 0 && FORM_FIRST_PAIR_KEYS_RE.test(key) && (keyStart === 0 || text[keyStart - 1] === "," || text[keyStart - 1] === ";" || isJsWhitespaceAt(text, keyStart - 1))) {
const start = separator + 1;
const end = valueEnd(start);
if (end > start && end < text.length && text[end] === "&" && isFormPairBoundary(text, end)) {
const matchStart = keyStart > 0 ? keyStart - 1 : 0;
yield {
match: text.slice(matchStart, end),
groups: [keyStart > 0 ? text[keyStart - 1] : "", text.slice(start, end)],
input: text,
offset: matchStart
};
searchFloor = end;
separator = text.indexOf("=", end);
continue;
}
}
separator = text.indexOf("=", separator + 1);
}
}
});
const STANDALONE_QUOTED_PREFIX_SOURCE = String.raw`(^|[\s,;({\["])(?:${STANDALONE_ASSIGNMENT_SECRET_KEYS})=(["'\x60])`;
const CONFIG_QUOTED_PREFIX_SOURCE = String.raw`(^|[\s,{])(?:(?:${CONFIG_QUOTED_ASSIGNMENT_SECRET_KEYS})(?:\s*:\s*|\s+=\s*|=\s*)|[a-z0-9][a-z0-9._-]{0,79}[-_](?:${CONFIG_PREFIXED_PASSWORD_ASSIGNMENT_SECRET_KEYS})\s*[:=]\s*|[a-z0-9_.-]{1,80}\.(?:${CONFIG_ASSIGNMENT_SECRET_KEYS})\s*[:=]\s*)(["'\x60])`;
/**
* Shared matcher for the two quoted-value rules. The tempered dot `((?:(?!\2)[^\r\n])+)`
* forced a per-character lookahead and one backtrack stack entry per value character; the
* value here is a linear scan to the first closing quote or line end, and failed scans are
* memoized per quote character because later candidates share their tail regions.
*/
function makeQuotedAssignmentMatcher(source, prefixSource, flags) {
const prefixRegexPool = [];
function* exec(text) {
const prefixRe = prefixRegexPool.pop() ?? new RegExp(prefixSource, flags);
const failedScans = /* @__PURE__ */ new Map();
let searchFrom = 0;
try {
for (;;) {
prefixRe.lastIndex = searchFrom;
const prefix = prefixRe.exec(text);
if (!prefix) return;
const start = prefix.index;
const quote = prefix[2];
const valueStart = start + prefix[0].length;
const memo = failedScans.get(quote);
let closing = -1;
if (!(memo !== void 0 && valueStart >= memo.start && valueStart < memo.end)) {
closing = valueStart;
while (closing < text.length) {
const char = text[closing];
if (char === quote || char === "\r" || char === "\n") break;
closing += 1;
}
}
if (closing > valueStart && text[closing] === quote) {
const end = closing + 1;
yield {
match: text.slice(start, end),
groups: [
prefix[1] ?? "",
quote,
text.slice(valueStart, closing)
],
input: text,
offset: start
};
searchFrom = end;
} else {
if (closing !== -1) failedScans.set(quote, {
start: valueStart,
end: closing
});
searchFrom = start + 1;
}
}
} finally {
prefixRe.lastIndex = 0;
prefixRegexPool.push(prefixRe);
}
}
return Object.freeze({
source,
exec
});
}
const STANDALONE_QUOTED_MATCHER = makeQuotedAssignmentMatcher("standalone-quoted-assignment (linear)", STANDALONE_QUOTED_PREFIX_SOURCE, "gi");
const CONFIG_QUOTED_MATCHER = makeQuotedAssignmentMatcher("config-quoted-assignment (linear)", CONFIG_QUOTED_PREFIX_SOURCE, "g");
/** Default rule sources whose compilation routes to the linear matchers above. */
const LINEAR_MATCHER_SOURCES = /* @__PURE__ */ new Map([
[base64SafeToken(String.raw`gAAAA[A-Za-z0-9_=-]{20,}`), GAAAA_TOKEN_MATCHER],
[base64SafeToken(String.raw`ATCTT3xFfG[A-Za-z0-9+/=_-]+=[A-Za-z0-9]{8}`), ATCTT_TOKEN_MATCHER],
[base64SafeToken(String.raw`ATATT[A-Za-z0-9+/=_-]+=[A-Za-z0-9]{8}`), ATATT_TOKEN_MATCHER],
[base64SafeToken(String.raw`ATBB[A-Za-z0-9_=.-]{16,}`), ATBB_TOKEN_MATCHER],
[base64SafeToken(String.raw`dapi[0-9a-f]{32}(?:-\d)?`), DAPI_TOKEN_MATCHER],
[base64SafeToken(String.raw`AKIA[A-Z0-9]{16}`), AKIA_TOKEN_MATCHER],
[base64SafeToken(String.raw`ASIA[A-Z0-9]{16}`), ASIA_TOKEN_MATCHER],
[JWT_REDACT_PATTERN, JWT_MATCHER],
[URL_USERINFO_REDACT_PATTERN, URL_USERINFO_MATCHER],
[CONNECTION_STRING_REDACT_PATTERN, CONNECTION_STRING_MATCHER],
[FORM_BODY_FIRST_PAIR_REDACT_PATTERN, FORM_BODY_FIRST_PAIR_MATCHER],
[STANDALONE_ASSIGNMENT_QUOTED_REDACT_PATTERN, STANDALONE_QUOTED_MATCHER],
[CONFIG_QUOTED_ASSIGNMENT_REDACT_PATTERN, CONFIG_QUOTED_MATCHER]
]);
//#endregion
//#region src/logging/redact-mask.ts
const DEFAULT_REDACT_MIN_LENGTH = 18;
const DEFAULT_REDACT_KEEP_START = 6;
const SECRET_VALUE_TRAILING_DELIMITER_RE = /(["'`,;)}\]]+)$/u;
const SECRET_VALUE_SUFFIX_RE = /^["'`,;)}\]]*$/u;
const SECRET_VALUE_QUOTE_CHARS = /* @__PURE__ */ new Set([
"\"",
"'",
"`"
]);
function maskToken(token) {
if (token.length < DEFAULT_REDACT_MIN_LENGTH) return "***";
return `${sliceUtf16Safe(token, 0, DEFAULT_REDACT_KEEP_START)}…${sliceUtf16Safe(token, -4)}`;
}
function splitSecretValueForMask(token) {
const openingQuote = token[0] ?? "";
const contentStart = SECRET_VALUE_QUOTE_CHARS.has(openingQuote) ? 1 : 0;
if (contentStart) {
const closingQuoteIndex = token.lastIndexOf(openingQuote);
if (closingQuoteIndex > 0) {
const suffix = token.slice(closingQuoteIndex + 1);
if (SECRET_VALUE_SUFFIX_RE.test(suffix)) return {
maskable: token.slice(1, closingQuoteIndex),
suffix,
maskStart: 0,
maskEnd: closingQuoteIndex + 1
};
}
}
const content = token.slice(contentStart);
const trailingDelimiter = content.match(SECRET_VALUE_TRAILING_DELIMITER_RE)?.[1] ?? "";
const maskable = trailingDelimiter && trailingDelimiter.length < content.length ? content.slice(0, -trailingDelimiter.length) : content;
return {
maskable,
suffix: maskable === content ? "" : trailingDelimiter,
maskStart: 0,
maskEnd: contentStart + maskable.length
};
}
function splitFormAwareCredentialValue(token) {
const pairBoundary = token.search(/&[A-Za-z_][A-Za-z0-9_.-]*=/u);
return pairBoundary < 0 ? {
secret: token,
suffix: ""
} : {
secret: token.slice(0, pairBoundary),
suffix: token.slice(pairBoundary)
};
}
function maskSecretValue(token, options) {
const { maskable, suffix } = splitSecretValueForMask(token);
return `${options?.hinted ? maskToken(maskable) : "***"}${suffix}`;
}
//#endregion
//#region src/logging/redact-performance.ts
const redactionPerformance = channel("openclaw.redaction");
/** The capture subscriber receives counts and synchronous CPU time, never text or patterns. */
function startRedactionMeasurement(operation) {
if (!redactionPerformance.hasSubscribers) return;
const startedAt = performance.now();
const cpu = process.threadCpuUsage();
return (outcome, inputChars, patternCount) => {
const elapsedMs = performance.now() - startedAt;
const used = process.threadCpuUsage(cpu);
redactionPerformance.publish({
operation,
outcome,
pid: process.pid,
threadId,
isMainThread,
elapsedMs,
threadCpuMs: (used.user + used.system) / 1e3,
...inputChars === void 0 ? {} : { inputChars },
...patternCount === void 0 ? {} : { patternCount }
});
};
}
//#endregion
//#region src/logging/redact-prefilter.ts
const FIELD_KEY_PREFILTER_RE = /KEY|TOKEN|SECRET|PASSWORD|PASSWD|AUTH|COOKIE|SIGNATURE|CREDENTIAL|CARD|CVC|CVV|PAYMENT|security[-_]?code|\bpass\s*[=:]|\bpassphrase\s*[=:]|_(?:password|pass|passphrase|passwd)\s*[=:]|jwt\s*[=:]|session=|code=|\bsig\s*=/iu;
const SEPARATED_VENDOR_PREFILTER_RE = /sk-|gh[opsur]_|github_pat_|glpat-|gloas-|gldt-|glcbt-|glptt-|glft-|glimt-|glagent-|glwt-|glsoat-|glffct-|glrt-|glrtr-|_gitlab_session=|xox[baprs]-|xapp-|gsk_|pplx-|fal_|fc-|bb_live_|[sr]k_(?:live|test)_|npm_|pypi-|do[opr]_v1_|dckr_|bkua_|CCIPAT_|sbp_|dd[pw]_|glsa_|nfp_|CFPAT-|BBDC-|HRKU-|pat-(?:eu|na)1-|apify_api_|fio-u-|tvly-|exa_|syt_|retaindb_|mem0_|brv_|xai-|fw-|fw_|fpk_|(?:^|[^A-Za-z0-9_])(?:am_|sk_)|hf_|api_org_|r8_/iu;
const DEFAULT_REDACT_PREFILTER_RE = /PRIVATE KEY|\bBearer\s+|GR1348941|hooks\.slack\.com|discord|AIza|ya29\.|1\/\/0|eyJ|gAAAA|SG\.|dp\.(?:ct|pt|sa|st|scim|audit)\.|dapi[0-9a-f]|ATCTT3|ATATT|ATBB|FlyV1|A[KS]IA[A-Z0-9]|AKID|LTAI|\bbot\d{6,}:|\b\d{6,}:[A-Za-z0-9_-]{20,}/iu;
const OBFUSCATED_KEY_CHAR_RE = new RegExp(`[${FORM_BODY_KEY_INVISIBLE_CHARS}+%]`, "u");
const FULL_CONTEXT_REDACT_EXTRA_TRIGGERS_RE = /JWT|Bearer\s+|am_|sk_|(?<!\d)\d{6,}:[A-Za-z0-9_-]{20,}/i;
function couldMatchDefaultRedactPatterns(text) {
return FIELD_KEY_PREFILTER_RE.test(text) || (text.includes("-") || text.includes("_")) && SEPARATED_VENDOR_PREFILTER_RE.test(text) || DEFAULT_REDACT_PREFILTER_RE.test(text) || text.includes("://") && text.includes("@") || text.includes("=") && OBFUSCATED_KEY_CHAR_RE.test(text) || AWS_SECRET_ACCESS_KEY_MATCHER.couldMatch(text);
}
function couldMatchDefaultFullContextPatterns(text) {
return couldMatchDefaultRedactPatterns(text) || FULL_CONTEXT_REDACT_EXTRA_TRIGGERS_RE.test(text);
}
function couldMatchVendorRedactPatterns(text) {
return (text.includes("-") || text.includes("_")) && SEPARATED_VENDOR_PREFILTER_RE.test(text) || DEFAULT_REDACT_PREFILTER_RE.test(text) || FULL_CONTEXT_REDACT_EXTRA_TRIGGERS_RE.test(text);
}
function createRedactPrefilter(probe) {
const matches = /* @__PURE__ */ new Map();
return (text) => {
const cached = matches.get(text);
if (cached !== void 0) return cached;
const result = probe(text);
matches.set(text, result);
return result;
};
}
//#endregion
//#region src/shared/assistant-error-format.ts
const HTTP_STATUS_DELIMITER_RE = /(?:\s*:\s*|\s+)/;
new RegExp(`^(?:http\\s*)?(\\d{3})${HTTP_STATUS_DELIMITER_RE.source}(.+)$`, "i");
new RegExp(`^(?:http\\s*)?(\\d{3})(?:${HTTP_STATUS_DELIMITER_RE.source}([\\s\\S]+))?$`, "i");
const CONNECTION_FAILED_MESSAGE = "Couldn't connect to the AI service. Check your connection, then try again. For details, open Settings → Logs in the Control UI or run `openclaw logs --follow`.";
const TRANSPORT_ERRORS = [
{
code: /\beconnrefused\b/i,
phrases: ["connection refused", "actively refused"],
message: CONNECTION_FAILED_MESSAGE
},
{
code: /\beconnreset\b|\beconnaborted\b|\benetreset\b|\bepipe\b/i,
phrases: [
"socket hang up",
"connection reset",
"connection aborted"
],
message: "Lost the connection to the AI service. Check the conversation before trying again. For details, open Settings → Logs in the Control UI or run `openclaw logs --follow`."
},
{
code: /\benotfound\b|\beai_again\b|\benetunreach\b|\behostunreach\b|\behostdown\b/i,
phrases: [
"getaddrinfo",
"no such host",
"dns",
"network is unreachable",
"host is unreachable",
"fetch failed",
"connection error",
"network request failed"
],
message: CONNECTION_FAILED_MESSAGE
}
];
function isKnownTransportErrorCode(value) {
return TRANSPORT_ERRORS.some(({ code }) => code?.exec(value)?.[0] === value);
}
//#endregion
//#region src/logging/structured-authorization-code.ts
function shouldRedactStructuredAuthorizationCode(normalizedKey, path, transportCode) {
if (normalizedKey !== "code") return false;
const normalizedPath = path.map((part) => part.toLowerCase());
if (normalizedPath.length === 1 || normalizedPath.at(-1) === "code" && [
"error",
"nodeerror",
"status",
"details",
"warnings"
].includes(normalizedPath.at(-2) ?? "")) return false;
return !(transportCode !== void 0 && normalizedPath.length > 1 && normalizedPath.slice(0, -1).every((part) => part === "cause") && isKnownTransportErrorCode(transportCode));
}
//#endregion
//#region src/logging/redact.ts
const DEFAULT_REDACT_MODE = "tools";
const shellReferencePreservingPatterns = /* @__PURE__ */ new WeakSet();
const builtInResolvedPatterns = /* @__PURE__ */ new WeakSet();
const vendorResolvedPatterns = /* @__PURE__ */ new WeakMap();
const vendorSources = new Set(VENDOR_TOKEN_REDACT_PATTERNS);
const chunkUnsafePatterns = /* @__PURE__ */ new WeakSet();
const canonicalBuiltInSources = new Set([...DEFAULT_REDACT_PATTERNS, ...TOOL_PAYLOAD_REDACT_PATTERNS].filter((entry) => typeof entry === "string"));
const formAwareEqualsAssignmentPatterns = /* @__PURE__ */ new WeakSet();
const sourceAssignmentPatterns = /* @__PURE__ */ new WeakSet();
let defaultResolvedPatterns;
let toolPayloadResolvedPatterns;
const FORM_BODY_KEY_OBFUSCATION_RE = new RegExp(String.raw`[${FORM_BODY_KEY_INVISIBLE_CHARS}+]`, "gu");
const FORM_BODY_KEY_SEPARATOR_RE = /[\p{C}\p{Z}\u115F\u1160\u3164\uFFA0+]/gu;
const FORM_BODY_PERCENT_ESCAPE_RE = /%[0-9A-Fa-f]{2}/u;
const FORM_BODY_KEY = String.raw`[${FORM_BODY_KEY_INVISIBLE_CHARS}+]*(?:[A-Za-z_]|%[0-9A-Fa-f]{2})(?:[A-Za-z0-9_.-]|%[0-9A-Fa-f]{2}|[${FORM_BODY_KEY_INVISIBLE_CHARS}+])*`;
const FORM_BODY_VALUE = "[^&\\s<>]*";
const URL_QUERY_VALUE = "[^&#\\s<>]*";
const FORM_BODY_PAIR = String.raw`${FORM_BODY_KEY}=${FORM_BODY_VALUE}`;
const FORM_BODY_RE = new RegExp(String.raw`^${FORM_BODY_PAIR}(?:&${FORM_BODY_PAIR})+$`, "u");
const FORM_BODY_SUBSTRING_RE = new RegExp(String.raw`(^|[\s:({\[,="'` + "`" + String.raw`])(${FORM_BODY_PAIR}(?:&${FORM_BODY_PAIR})+)`, "gu");
const ENCODED_FORM_PAIR_RE = new RegExp(String.raw`(^|[\s:({\[,="'` + "`" + String.raw`&])(${FORM_BODY_KEY})=(${FORM_BODY_VALUE})`, "gu");
const FORM_BODY_CONTEXT_SINGLE_PAIR_RE = new RegExp(String.raw`(\b(?:body|form(?:[-_\s]?body)?)\s*[:=]\s*(["'\x60]?))(${FORM_BODY_KEY})=(${FORM_BODY_VALUE})(["'\x60]?)`, "giu");
const URL_QUERY_PAIR_RE = new RegExp(String.raw`([?&])(${FORM_BODY_KEY})=(${URL_QUERY_VALUE})`, "gu");
const FORM_BODY_LINE_BREAK_SPLIT_RE = /(\r\n|\r|\n)/u;
const FORM_BODY_LINE_BREAK_SEGMENT_RE = /^(?:\r\n|\r|\n)$/u;
const STRUCTURED_SECRET_FIELD_RE = new RegExp(String.raw`^(?:api[-_]?key|apiKey|api[-_]?token|apiToken|bearer[-_]?token|bearerToken|token|secret|password|passwd|${AWS_SECRET_ACCESS_KEY_FIELD_KEYS}|credential|authorization|private[-_]?key|privateKey|access[-_]?token|accessToken|refresh[-_]?token|refreshToken|id[-_]?token|idToken|auth[-_]?token|authToken|client[-_]?secret|clientSecret|app[-_]?secret|appSecret|secret[-_]?value|secretValue|raw[-_]?secret|rawSecret|secret[-_]?input|secretInput|key|key[-_]?material|keyMaterial|jwt|session|signature|cookie|set[-_]?cookie|${PAYMENT_CREDENTIAL_QUERY_KEYS}|${PAYMENT_CREDENTIAL_JSON_KEYS})$`, "i");
const STRUCTURED_INTERNAL_SOURCE_PATH_VALUE_RE = /^\$WORKSPACE_DIR\/[A-Za-z0-9._/-]+\.jsonl$/u;
const STRUCTURED_APP_PASSWORD_FIELD_RE = /^(?:apple|icloud|app[-_]?specific[-_]?password|appSpecificPassword|application[-_]?password|text|content|message|error|errorMessage|detail|details|reason)$/i;
const APP_SPECIFIC_PASSWORD_RE = /\b([a-z]{4}-[a-z]{4}-[a-z]{4}-[a-z]{4})\b/g;
const BENIGN_APP_PASSWORD_WORDS = /* @__PURE__ */ new Set([
"case",
"claw",
"demo",
"file",
"main",
"name",
"open",
"path",
"slug",
"test"
]);
const STRUCTURED_SECRET_ENV_FIELD_RE = new RegExp(String.raw`^(?:(?:[A-Z0-9]+[_-])+(?:KEY|TOKEN|SECRET|PASSWORD|PASSWD)|API[_-]?KEY|TOKEN|SECRET|PASSWORD|PASSWD|${PAYMENT_CREDENTIAL_ENV_KEYS})$`, "i");
function normalizeMode(value) {
return value === "off" ? "off" : DEFAULT_REDACT_MODE;
}
function parsePattern(raw) {
if (raw === PEM_REDACT_PATTERN_SOURCE) return PEM_REDACT_MATCHER;
if (typeof raw !== "string" && !(raw instanceof RegExp)) {
if (AMBIGUOUS_ASSIGNMENT_MATCHERS.has(raw)) sourceAssignmentPatterns.add(raw);
return raw;
}
let pattern = typeof raw === "string" ? LINEAR_MATCHER_SOURCES.get(raw) ?? null : null;
let canPrefilter = true;
if (raw instanceof RegExp) {
if (raw.flags.includes("g")) pattern = raw;
else pattern = new RegExp(raw.source, `${raw.flags}g`);
} else if (!pattern && raw.trim()) {
const [source, flags] = parseRedactPatternSource(raw);
const optimized = canonicalBuiltInSources.has(raw) ? rewriteOpenEndedRepeats(source, flags) : source;
pattern = compileConfigRegex(optimized, flags)?.regex ?? null;
canPrefilter = pattern !== null && pattern.source === optimized && pattern.flags === flags && pattern.exec === RegExp.prototype.exec && pattern[Symbol.replace] === RegExp.prototype[Symbol.replace];
}
if (pattern && typeof raw === "string" && SHELL_REFERENCE_PRESERVING_PATTERN_SOURCES.has(raw)) shellReferencePreservingPatterns.add(pattern);
if (pattern && typeof raw === "string" && TOOL_PAYLOAD_AMBIGUOUS_ASSIGNMENT_PATTERNS.has(raw)) sourceAssignmentPatterns.add(pattern);
if (pattern && typeof raw === "string" && FORM_AWARE_EQUALS_ASSIGNMENT_PATTERN_SOURCES.has(raw)) formAwareEqualsAssignmentPatterns.add(pattern);
if (pattern && typeof raw === "string" && (raw.startsWith(IDENTIFIER_SAFE_TOKEN_BOUNDARY) || CHUNK_UNSAFE_PATTERN_SOURCES.has(raw))) chunkUnsafePatterns.add(pattern);
if (pattern && typeof raw === "string") {
if (canonicalBuiltInSources.has(raw)) builtInResolvedPatterns.add(pattern);
if (canPrefilter && vendorSources.has(raw) && !vendorResolvedPatterns.has(pattern)) {
const source = pattern.source;
const exec = pattern.exec;
const flags = pattern instanceof RegExp ? pattern.flags : void 0;
const replace = pattern instanceof RegExp ? pattern[Symbol.replace] : void 0;
vendorResolvedPatterns.set(pattern, () => pattern.source === source && pattern.exec === exec && (!(pattern instanceof RegExp) || pattern.flags === flags && pattern[Symbol.replace] === replace));
}
const probe = REDACT_PATTERN_PREFILTERS.get(raw);
if (canPrefilter && probe) setRedactPatternPrefilter(pattern, probe);
}
return pattern;
}
function resolvePatterns(value) {
if (value === TOOL_PAYLOAD_REDACT_PATTERNS) {
toolPayloadResolvedPatterns ??= TOOL_PAYLOAD_REDACT_PATTERNS.map(parsePattern).filter((re) => Boolean(re));
return toolPayloadResolvedPatterns;
}
if (!value?.length || value === DEFAULT_REDACT_PATTERNS) {
defaultResolvedPatterns ??= DEFAULT_REDACT_PATTERNS.map(parsePattern).filter((re) => Boolean(re));
return defaultResolvedPatterns;
}
const seen = /* @__PURE__ */ new Set();
const combined = [];
for (const raw of value) {
const resolved = parsePattern(raw);
if (resolved !== null && !seen.has(resolved)) {
seen.add(resolved);
combined.push(resolved);
}
}
if (!seen.has(AWS_SECRET_ACCESS_KEY_MATCHER)) combined.push(AWS_SECRET_ACCESS_KEY_MATCHER);
return combined;
}
function usesBuiltInRedactPatterns(value) {
return !value?.length || value === DEFAULT_REDACT_PATTERNS || value === TOOL_PAYLOAD_REDACT_PATTERNS;
}
function normalizeSensitiveKeyName(value) {
const stripped = value.replace(FORM_BODY_KEY_SEPARATOR_RE, "");
try {
return decodeURIComponent(stripped).replace(FORM_BODY_KEY_SEPARATOR_RE, "").toLowerCase().replaceAll("-", "_");
} catch {
return stripped.toLowerCase().replaceAll("-", "_");
}
}
function isSensitiveBodyKey(key) {
return isSensitiveUrlQueryParamName(key) || BODY_SECRET_KEYS.has(normalizeSensitiveKeyName(key));
}
function hasEncodedOrInvisibleFormKey(key) {
return FORM_BODY_PERCENT_ESCAPE_RE.test(key) || key.replace(FORM_BODY_KEY_OBFUSCATION_RE, "") !== key;
}
function visitSensitiveAssignments(text, kind, visit) {
if (!text || kind === "url" && !text.includes("?")) return;
if (kind === "encoded" && !text.includes("%") && text.replace(FORM_BODY_KEY_OBFUSCATION_RE, "") === text) return;
if (kind === "context" && !/[=:]/u.test(text)) return;
const visitPair = (key, token, valueOffset) => {
if (kind === "encoded" && !hasEncodedOrInvisibleFormKey(key)) return;
if (!isSensitiveBodyKey(key)) return;
const { maskable, maskStart, maskEnd } = splitSecretValueForMask(token);
visit(valueOffset + maskStart, valueOffset + maskEnd, maskable);
};
if (kind === "form") {
let cursor = 0;
for (const pair of text.split("&")) {
const equalsIndex = pair.indexOf("=");
if (equalsIndex >= 0) visitPair(pair.slice(0, equalsIndex), pair.slice(equalsIndex + 1), cursor + equalsIndex + 1);
cursor += pair.length + 1;
}
return;
}
const pattern = kind === "url" ? URL_QUERY_PAIR_RE : kind === "encoded" ? ENCODED_FORM_PAIR_RE : FORM_BODY_CONTEXT_SINGLE_PAIR_RE;
const keyIndex = kind === "context" ? 3 : 2;
pattern.lastIndex = 0;
try {
for (let match = pattern.exec(text); match; match = pattern.exec(text)) {
const prefix = match[1] ?? "";
const key = match[keyIndex] ?? "";
visitPair(key, match[keyIndex + 1] ?? "", match.index + prefix.length + key.length + 1);
}
} finally {
pattern.lastIndex = 0;
}
}
function redactAssignmentValues(text, kind, onEdits) {
const parts = [];
const edits = [];
let cursor = 0;
visitSensitiveAssignments(text, kind, (start, end, maskable) => {
const replacement = kind === "url" ? maskToken(maskable) : "***";
parts.push(text.slice(cursor, start), replacement);
if (onEdits) edits.push({
start,
end,
replacement
});
cursor = end;
});
if (edits.length > 0) onEdits?.(edits);
return parts.length > 0 ? parts.join("") + text.slice(cursor) : text;
}
function redactFormBodyLine(text, onEdits) {
if (!text.includes("=")) return text;
const contextRedacted = redactAssignmentValues(redactAssignmentValues(text, "encoded", onEdits), "context", onEdits);
if (!contextRedacted.includes("&")) return contextRedacted;
if (FORM_BODY_RE.test(contextRedacted)) return redactAssignmentValues(contextRedacted, "form", onEdits);
const substringEdits = [];
const redacted = contextRedacted.replace(FORM_BODY_SUBSTRING_RE, (match, prefix, body, offset) => {
const redactedBody = redactAssignmentValues(body, "form", onEdits ? (edits) => {
for (const edit of edits) substringEdits.push({
...edit,
start: offset + prefix.length + edit.start,
end: offset + prefix.length + edit.end
});
} : void 0);
return redactedBody === body ? match : `${prefix}${redactedBody}`;
});
if (substringEdits.length > 0) onEdits?.(substringEdits);
return redactAssignmentValues(redactAssignmentValues(redacted, "encoded", onEdits), "context", onEdits);
}
function redactFormBody(text, onEdits) {
if (!text.includes("=")) return text;
if (FORM_BODY_LINE_BREAK_SPLIT_RE.test(text)) {
let offset = 0;
return text.split(FORM_BODY_LINE_BREAK_SPLIT_RE).map((segment) => {
const result = FORM_BODY_LINE_BREAK_SEGMENT_RE.test(segment) ? segment : redactFormBodyLine(segment, onEdits ? (edits) => onEdits(edits.map((edit) => ({
...edit,
start: offset + edit.start,
end: offset + edit.end
}))) : void 0);
offset += result.length;
return result;
}).join("");
}
return redactFormBodyLine(text, onEdits);
}
function isShellReferenceToKey(key, value) {
if (!/^[A-Z_][A-Z0-9_]*$/.test(key)) return false;
const bare = value.match(/^\$([A-Z_][A-Z0-9_]*)$/);
if (bare) return bare[1] === key;
return value.match(/^\$\{([A-Z_][A-Z0-9_]*)(?::[-=?+])?\}$/)?.[1] === key;
}
function maskSecretFieldValue(key, value) {
const expansion = value.match(/^\$\{([A-Z_][A-Z0-9_]*):[-=?+][^{}]+\}(?![\s\S])/);
if (expansion && expansion[1] === key) return `${value.slice(0, value.indexOf(":") + 2)}***}`;
return "***";
}
function shouldPreserveShellReferenceMatch(match, token) {
const key = match.match(/\b([A-Z_][A-Z0-9_]*)\b\s*[=:]/)?.[1];
return key ? isShellReferenceToKey(key, token) : false;
}
function isEmptyShellParameterExpansionTail(token) {
return /^[-=?+]\}$/.test(token);
}
function prepareRedactionCapture({ match, groups, input, offset, replacement: policyReplacement }, pattern, preserveSourceAssignment) {
if (match.includes("PRIVATE KEY-----")) return {
start: offset,
end: offset + match.length,
value: match,
redact: (target) => ({
start: target.start,
end: target.end,
value: target.value,
replacement: policyReplacement ?? redactPemBlock(target.value, "…redacted…")
})
};
const selected = selectSecretCapture(match, groups);
if (selected.value === "***") return;
const tokenIndex = selected.value === match ? 0 : getSecretCaptureStart(pattern, input, match, offset, selected);
if (tokenIndex < 0) return;
const start = offset + tokenIndex;
return {
start,
end: start + selected.value.length,
value: selected.value,
redact: (target) => {
const token = target.value;
if (sourceAssignmentPatterns.has(pattern) && preserveSourceAssignment?.(input, offset + getSecretCaptureStart(pattern, input, match, offset, selected))) return;
const formAwareValue = formAwareEqualsAssignmentPatterns.has(pattern) ? splitFormAwareCredentialValue(token) : {
secret: token,
suffix: ""
};
if (splitSecretValueForMask(formAwareValue.secret).maskable === "***") return;
const isShellReferencePattern = shellReferencePreservingPatterns.has(pattern);
if (isShellReferencePattern && (shouldPreserveShellReferenceMatch(match, token) || isEmptyShellParameterExpansionTail(token))) return;
const masked = policyReplacement ?? (preserveSourceAssignment && sourceAssignmentPatterns.has(pattern) ? maskSecretValue(token) : isShellReferencePattern ? maskToken(token) : `${maskSecretValue(formAwareValue.secret, { hinted: true })}${formAwareValue.suffix}`);
return {
start: target.start,
end: target.end,
replacement: masked
};
}
};
}
function redactMatch(match, pattern, preserveSourceAssignment) {
const capture = prepareRedactionCapture(match, pattern, preserveSourceAssignment);
const edit = capture?.redact(capture);
return edit ? match.match.slice(0, edit.start - match.offset) + edit.replacement + match.match.slice(edit.end - match.offset) : match.match;
}
function redactText(text, patterns, options) {
const finishMeasurement = startRedactionMeasurement("text");
let outcome = "error";
try {
let next = redactFormBody(redactAssignmentValues(redactStructuredAuthHeaders(text, "***"), "url"));
let pattern;
const replace = (match) => redactMatch(match, pattern, options?.preserveSourceAssignment);
const replaceRegex = (...args) => replace(readRedactMatch(args));
let vendorInput;
let vendorMatch = false;
for (pattern of patterns) {
if (vendorResolvedPatterns.get(pattern)?.()) {
if (vendorInput !== next) {
vendorInput = next;
vendorMatch = couldMatchVendorRedactPatterns(next);
}
if (!vendorMatch) {
if (pattern instanceof RegExp) pattern.lastIndex = 0;
continue;
}
}
next = pattern instanceof RegExp && !options?.fullContext && !builtInResolvedPatterns.has(pattern) && !chunkUnsafePatterns.has(pattern) ? replacePatternBounded(next, pattern, replaceRegex) : replaceRedactPattern(next, pattern, replace, replaceRegex);
}
outcome = "ok";
return next;
} finally {
finishMeasurement?.(outcome, text.length, patterns.length);
}
}
function looksLikeAppSpecificPassword(candidate) {
return candidate.split("-").every((part) => !BENIGN_APP_PASSWORD_WORDS.has(part.toLowerCase()));
}
function redactAppSpecificPasswords(text) {
if (!text.includes("-")) return text;
return text.replace(APP_SPECIFIC_PASSWORD_RE, (match, token) => looksLikeAppSpecificPassword(token) ? maskToken(token) : match);
}
function resolveConfigRedaction() {
const cfg = readLoggingConfig();
return {
mode: DEFAULT_REDACT_MODE,
patterns: cfg?.redactPatterns
};
}
function resolveRedactOptions(options) {
const resolved = options ?? resolveConfigRedaction();
const mode = normalizeMode(resolved.mode);
return {
mode,
patterns: mode === "off" ? [] : resolvePatterns(resolved.patterns)
};
}
function redactSensitiveText(text, options) {
if (!text) return text;
return redactSensitiveTextWithOptions(redactRegisteredSecretValues(text, maskToken), options ?? resolveConfigRedaction());
}
function redactSensitiveTextWithOptions(exactRedacted, resolvedOptions) {
if (normalizeMode(resolvedOptions.mode) === "off") return exactRedacted;
if (usesBuiltInRedactPatterns(resolvedOptions.patterns) && !couldMatchDefaultRedactPatterns(exactRedacted)) return exactRedacted;
return redactText(exactRedacted, resolveRedactOptions(resolvedOptions).patterns);
}
function resolveToolPayloadRedaction(loggingConfig = readLoggingConfig()) {
const userPatterns = loggingConfig?.redactPatterns;
return {
mode: "tools",
patterns: userPatterns && userPatterns.length > 0 ? [...userPatterns, ...DEFAULT_REDACT_PATTERNS] : void 0
};
}
function redactToolPayloadText(text) {
return redactToolPayloadTextWithConfig(text, readLoggingConfig());
}
function redactToolPayloadTextWithPolicy(text, loggingConfig, options) {
if (!text) return text;
if (!isFullContextToolPayloadRedaction(loggingConfig)) return redactSensitiveText(text, options);
const resolved = resolveRedactOptions(options);
return redactText(redactRegisteredSecretValues(text, maskToken), resolved.patterns, { fullContext: true });
}
function redactToolPayloadTextWithConfig(text, loggingConfig) {
return redactToolPayloadTextWithPolicy(text, loggingConfig, resolveToolPayloadRedaction(loggingConfig));
}
function isSensitiveFieldKey(key) {
return STRUCTURED_SECRET_FIELD_RE.test(key) || STRUCTURED_SECRET_ENV_FIELD_RE.test(key);
}
function isPublicShareIdPath(path) {
if (path.at(-1)?.toLowerCase() !== "id") return false;
return path.at(-2)?.toLowerCase().replaceAll("-", "").replaceAll("_", "") === "publicshare";
}
function redactSensitiveFieldValueWithOptions(key, value, options, path = [key], objectPath = true, couldMatch = couldMatchDefaultRedactPatterns) {
const exactRedacted = redactRegisteredSecretValues(value, maskToken);
if (isPublicShareIdPath(path)) return maskToken(exactRedacted);
const fieldOptions = isSensitiveFieldKey(key) && options.sensitiveFieldPatterns ? {
...options,
patterns: options.sensitiveFieldPatterns
} : options;
const resolved = resolveRedactOptions(fieldOptions);
if (resolved.mode === "off") return exactRedacted;
const redacted = !usesBuiltInRedactPatterns(fieldOptions.patterns) || couldMatch(exactRedacted) ? redactText(exactRedacted, resolved.patterns) : exactRedacted;
if (redacted !== value || STRUCTURED_APP_PASSWORD_FIELD_RE.test(key)) {
const appRedacted = redactAppSpecificPasswords(redacted);
if (appRedacted !== value) return appRedacted;
}
if (redacted !== value) return redacted;
return shouldRedactStructuredStringField(key, exactRedacted, path, objectPath) ? maskToken(exactRedacted) : exactRedacted;
}
function shouldRedactStructuredPrimitiveField(key, path) {
const normalizedKey = key.toLowerCase();
return isPublicShareIdPath(path) || shouldRedactStructuredAuthorizationCode(normalizedKey, path) || isSensitiveFieldKey(key);
}
function isPlainRedactableObject(value) {
const prototype = Object.getPrototypeOf(value);
return prototype === Object.prototype || prototype === null;
}
function redactStructuredSecretValue(key, value, seen, options, couldMatch, path = key ? [key] : [], objectPath = true) {
if (typeof value === "string") return redactSensitiveFieldValueWithOptions(key, value, options, path, objectPath, couldMatch);
if (value === null || value === void 0) return value;
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") return shouldRedactStructuredPrimitiveField(key, path) ? "***" : value;
if (Array.isArray(value)) {
if (seen.has(value)) return "[Circular]";
seen.add(value);
const out = value.map((entry) => redactStructuredSecretValue(key, entry, seen, options, couldMatch, path, false));
seen.delete(value);
return out;
}
if (typeof value === "object") {
if (seen.has(value)) return "[Circular]";
if (!isPlainRedactableObject(value)) return value;
seen.add(value);
const entries = Object.entries(value);
for (const entry of entries) {
const [name, child] = entry;
entry[1] = redactStructuredSecretValue(name, child, seen, options, couldMatch, [...path, name], objectPath);
}
seen.delete(value);
return Object.fromEntries(entries);
}
return value;
}
function redactSecretsWithOptions(value, options) {
if (typeof value === "string") return redactSensitiveText(value, options);
if (value === null || typeof value !== "object") return value;
return redactStructuredSecretValue("", value, /* @__PURE__ */ new WeakSet(), options, createRedactPrefilter(couldMatchDefaultRedactPatterns));
}
function redactSecrets(value) {
return redactSecretsWithOptions(value, resolveToolPayloadRedaction());
}
function preservesStructuredReference(key, value) {
return key.toLowerCase() === "session" && STRUCTURED_INTERNAL_SOURCE_PATH_VALUE_RE.test(value) || isShellReferenceToKey(key, value);
}
function shouldRedactStructuredStringField(key, value, path, objectPath) {
return shouldRedactStructuredAuthorizationCode(key.toLowerCase(), path, objectPath ? value : void 0) || isSensitiveFieldKey(key) && !preservesStructuredReference(key, value);
}
function classifyLogFieldProtection(key, path, objectPath, value) {
return (value === void 0 ? shouldRedactStructuredPrimitiveField(key, path) : isPublicShareIdPath(path) || shouldRedactStructuredStringField(key, value, path, objectPath)) ? "legacy" : CREDENTIAL_HEADER_FIELD_RE.test(key) ? "header" : void 0;
}
function getFieldRecordEdits(field, mode) {
const { key, value, path, objectPath } = field;
if (mode === "off" || !field.string && value === "null" || !classifyLogFieldProtection(key, path, objectPath, field.string ? value : void 0)) return [];
return [{
start: 0,
end: value.length,
replacement: maskSecretFieldValue(key, value)
}];
}
function getTextRecordEdits(field, mode, fileFields) {
const { value } = field;
if (field.isKey || fileFields && !field.origin.structured) return [];
if (fileFields && field.origin.primitiveMask) return [{
start: 0,
end: value.length,
replacement: "***"
}];
if (!field.string) return [];
const edits = [];
const registered = redactRegisteredSecretValues(value, (secret, start) => {
const replacement = maskToken(secret);
edits.push({
start,
end: start + secret.length,
replacement
});
return replacement;
});
if (fileFields && isPublicShareIdPath(field.path)) return [{
start: 0,
end: value.length,
replacement: maskToken(registered)
}];
if (mode === "off") return edits;
let combined = edits;
const receive = (added) => {
combined = composeRedactionEdits(value.length, combined, added);
};
const headers = findStructuredAuthParamRanges(registered).map(({ start, end }) => ({
start,
end,
replacement: "***"
}));
receive(headers);
redactFormBody(redactAssignmentValues(applyRedactionEdits(registered, headers), "url", receive), receive);
return combined;
}
function getLegacyFieldRecordEdits(field, value, beforeConversion = false) {
const { key, value: original, path, objectPath } = field;
if (field.isKey || !field.origin.structured || !field.string) return [];
if (isPublicShareIdPath(path)) return [];
const edits = [];
if (value !== original || STRUCTURED_APP_PASSWORD_FIELD_RE.test(key)) {
for (const match of value.matchAll(APP_SPECIFIC_PASSWORD_RE)) if (looksLikeAppSpecificPassword(match[0])) edits.push({
start: match.index,
end: match.index + match[0].length,
replacement: maskToken(match[0])
});
}
if (edits.length > 0 || value !== original) return edits;
const protection = classifyLogFieldProtection(key, path, objectPath, value);
return protection === "legacy" || beforeConversion && protection === "header" ? [{
start: 0,
end: value.length,
replacement: maskToken(value)
}] : [];
}
function resolveFileLogRedactOptions() {
return resolveRedactOptions(resolveToolPayloadRedaction());
}
function* redactionEditMatches(input, edits) {
for (const edit of edits) yield {
match: input.slice(edit.start, edit.end),
groups: [],
input,
offset: edit.start,
replacement: edit.replacement
};
}
const preparationPatterns = [
{
source: "registered secret values",
*exec(input) {
const matches = [];
redactRegisteredSecretValues(input, (secret, offset) => {
matches.push({
match: secret,
groups: [],
input,
offset,
replacement: maskToken(secret)
});
return secret;
});
yield* matches;
}
},
{
source: "structured authorization parameters",
*exec(input) {
for (const { start, end } of findStructuredAuthParamRanges(input)) yield {
match: input.slice(start, end),
groups: [],
input,
offset: start,
replacement: "***"
};
}
},
{
source: "URL query assignments",
*exec(input) {
const edits = [];
redactAssignmentValues(input, "url", (added) => edits.push(...added));
yield* redactionEditMatches(input, edits);
}
},
{
source: "form bodies",
*exec(input) {
let edits = [];
redactFormBody(input, (added) => {
edits = composeRedactionEdits(input.length, edits, added);
});
yield* redactionEditMatches(input, edits);
}
}
];
const CONSOLE_STRUCTURAL_FIELDS = /* @__PURE__ */ new Set(["time", "level"]);
function prepareFileToJsonReceivers(record, patterns) {
const decoded = /* @__PURE__ */ new WeakSet();
const active = /* @__PURE__ */ new WeakSet();
const visit = (value, key, path, objectPath, decode) => {
if (typeof value === "string" && decode) {
const field = {
key,
path,
objectPath,
value,
isKey: false,
string: true,
origin: {
structured: true,
primitiveMask: false
}
};
let current = applyRedactionEdits(value, getTextRecordEdits(field, "tools", true));
if (!isPublicShareIdPath(path)) for (const pattern of patterns) current = applyRedactionEdits(current, getPatternRedactionEdits(current, pattern, prepareRedactionCapture));
return applyRedactionEdits(current, getLegacyFieldRecordEdits(field, current, true));
}
if (value === null || typeof value !== "object") return decode && [
"number",
"boolean",
"bigint"
].includes(typeof value) && classifyLogFieldProtection(key, path, objectPath, void 0) ? "***" : value;
if (!Array.isArray(value) && !isPlainRedactableObject(value)) return value;
if (active.has(value)) return "[Circular]";
active.add(value);
let clone;
let decodeFields = decode;
if (Array.isArray(value)) clone = value.map((entry) => visit(entry, key, path, false, decodeFields));
else {
const entries = Object.entries(value);
decodeFields ||= entries.some(([name, entry]) => name === "toJSON" && typeof entry === "function");
clone = Object.fromEntries(entries.map(([name, entry]) => [name, visit(entry, name, [...path, name], objectPath, decodeFields)]));
}
if (decodeFields) decoded.add(clone);
active.delete(value);
return clone;
};
return {
record: visit(record, "", [], true, false),
decoded
};
}
/** Converts native values once and applies configured and structural protection before output. */
function redactLogRecord(record, options, finish) {
const finishMeasurement = startRedactionMeasurement("log-record");
let outcome = "error";
let inputChars;
try {
const resolved = resolveRedactOptions();
const prepared = options.format === "console" ? {
record,
decoded: /* @__PURE__ */ new WeakSet()
} : prepareFileToJsonReceivers(record, options.decodedOptions?.patterns ?? resolved.patterns);
const ordinary = {
structured: true,
primitiveMask: false
};
const origins = {
value: ordinary,
children: /* @__PURE__ */ new Map()
};
const ancestors = [];
const json = JSON.stringify(prepared.record, function(key, value) {
while (ancestors.length > 0 && ancestors.at(-1)?.value !== this) ancestors.pop();
const parent = ancestors.at(-1);
const array = Array.isArray(this);
const fieldKey = array && parent ? parent.key : key;
const path = array && parent ? parent.path : parent ? [...parent.path, key] : [];
const source = !parent ? prepared.record : parent.structured && options.format !== "console" ? Reflect.get(this, key) : value;
const structured = (parent?.structured ?? true) && (source === null || typeof source !== "object" || !prepared.decoded.has(source) && source === value && (Array.isArray(source) || isPlainRedactableObject(source)));
const primitiveMask = structured && [
"number",
"boolean",
"bigint"
].includes(typeof source) && classifyLogFieldProtection(fieldKey, path, !array, void 0) === "legacy";
const circular = value !== null && typeof value === "object" && ancestors.some((frame) => frame.value === value);
const emitted = circular ? "[Circular]" : typeof value === "bigint" ? String(value) : value;
const container = emitted !== null && typeof emitted === "object";
let node = origins;
if (parent && parent.structured && (container || !structured || primitiveMask || circular)) {
node = {
value: {
structured: structured && !circular,
primitiveMask
},
children: /* @__PURE__ */ new Map()
};
parent.origins.children.set(key, node);
} else if (parent) node = parent.origins;
else origins.value = {
structured,
primitiveMask
};
if (container) ancestors.push({
value: emitted,
path,
key: fieldKey,
origins: node,
structured
});
return emitted;
});
inputChars = json.length;
let materialized = JSON.parse(json);
const message = options.deriveMessage?.(materialized);
if (message) {
origins.children.set("message", {
value: ordinary,
children: /* @__PURE__ */ new Map()
});
if (Object.hasOwn(materialized, "message")) materialized.message = message.text;
else {
const entries = Object.entries(materialized);
entries.splice(entries.findIndex(([key]) => key === "hostname") + 1, 0, ["message", message.text]);
materialized = Object.fromEntries(entries);
}
}
const serialized = message ? JSON.stringify(materialized) : json;
const decodedPatterns = options.decodedOptions?.patterns ?? resolved.patterns;
const couldMatch = createRedactPrefilter(couldMatchDefaultFullContextPatterns);
const result = finish(redactJsonRecord(serialized, origins, [[{ patterns: decodedPatterns }], [{ patterns: preparationPatterns }, {
patterns: resolved.patterns,
...resolved.patterns === defaultResolvedPatterns ? { couldMatch } : {}
}]], prepareRedactionCapture, options.format === "console" ? () => [] : getLegacyFieldRecordEdits, (field) => getFieldRecordEdits(field, resolved.mode), (field) => getTextRecordEdits(field, resolved.mode, options.format !== "console"), (field, currentValue) => (options.format === "console" ? field.path.length === 1 && CONSOLE_STRUCTURAL_FIELDS.has(field.key) : !field.origin.structured || field.origin.primitiveMask || isPublicShareIdPath(field.path)) || decodedPatterns === defaultResolvedPatterns && !couldMatch(currentValue), message), message ? serialized : void 0);
outcome = "ok";
return result;
} finally {
finishMeasurement?.(outcome, inputChars);
}
}
function redactLogRecordForTransport(record, options = {}) {
return redactLogRecord(record, options, (redacted) => JSON.parse(redacted));
}
function serializeRedactedFileLogRecord(record, options = {}) {
return redactLogRecord(record, options, (redacted, canonical) => redacted === canonical ? redacted : JSON.stringify(JSON.parse(redacted)));
}
//#endregion
export { secretRefKey as $, hasErrnoCode as A, containsEnvVarReference as B, hashConfigIncludeRaw as C, parseJsonWithJson5Fallback as D, resolveConfigIncludes as E, resolveRootPathSync as F, resolveSecretInputRef as G, hasConfiguredSecretInput as H, matchRootFileOpenFailure as I, SINGLE_VALUE_FILE_REF_ID as J, DEFAULT_SECRET_PROVIDER_ALIAS as K, openRootFileSync as L, isMissingPathError as M, resolveIdentityPathViaExistingAncestorSync as N, getSealedRuntimeSecureTempRoot as O, resolvePathViaExistingAncestorSync$1 as P, resolveSecretRefProviderSourceMismatch as Q, readFileDescriptorBounded$1 as R, resolveProfileStateDir as S, resolveConfigIncludeWritePath as T, normalizeSecretInputString as U, resolveConfigEnvVars as V, parseSecretRef as W, isValidSecretProviderAlias as X, isBuiltInDefaultSecretProviderRef as Y, normalizeAndGroupSecretRefs as Z, resolveIsNixMode as _, resolveFileLogRedactOptions as a, sliceUtf16Safe as at, parseTcpPort as b, redactRegisteredSecretValues as c, redactSensitiveUrlLikeString as ct, loggingState as d, formatConcreteConfigPath as et, resolveConfigPath as f, resolveIncludeRoots as g, resolveGatewayPort as h, redactToolPayloadText as i, pruneMapToMaxSize as it, isErrno as j, escapeRegExp as k, registerSecretValueForRedaction as l, resolveGatewayLockDirForCanonicalStateDir as m, redactSecrets as n, parseConfigPathArrayIndex as nt, serializeRedactedFileLogRecord as o, truncateUtf16Safe as ot, resolveGatewayLockDir as p, ENV_SECRET_REF_ID_RE as q, redactSensitiveText as r, isBlockedObjectKey as rt, hasRegisteredSecretValuesForRedaction as s, expectDefined as st, redactLogRecordForTransport as t, tokenizeConcreteConfigPath as tt, readLoggingConfig as u, resolveStateDir as v, readConfigIncludeFileWithGuards as w, normalizeProfileName as x, isVitestRuntimeEnv as y, readFileDescriptorBoundedSync$1 as z };