



















@@ -39,11 +39,21 @@ const STRUCTURED_SECRET_ENV_FIELD_RE = new RegExp(
3939"i",
4040);
414142+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`;
43+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`;
44+const STANDALONE_ASSIGNMENT_REDACT_PATTERN = String.raw`(^|[\s,;])(?:access_token|refresh_token|auth[-_]?token|api[-_]?key|client[-_]?secret|app[-_]?secret|token|secret|password|passwd|${PAYMENT_CREDENTIAL_QUERY_KEYS})=([^\s&#]+)`;
45+const SHELL_REFERENCE_PRESERVING_PATTERN_SOURCES = new Set([
46+ENV_ASSIGNMENT_REDACT_PATTERN,
47+ESCAPED_ENV_ASSIGNMENT_REDACT_PATTERN,
48+STANDALONE_ASSIGNMENT_REDACT_PATTERN,
49+]);
50+const shellReferencePreservingPatterns = new WeakSet<RegExp>();
51+4252const DEFAULT_REDACT_PATTERNS: string[] = [
4353// ENV-style assignments. Keep this case-sensitive so diagnostics like
4454// `Unrecognized key: "llm"` do not lose the actual config key.
45-String.raw`/\b[A-Z0-9_]*(?:KEY|TOKEN|SECRET|PASSWORD|PASSWD|${PAYMENT_CREDENTIAL_ENV_KEYS})\b\s*[=:]\s*(["']?)([^\s"'\\]+)\1/g`,
46-String.raw`/\b[A-Z0-9_]*(?:KEY|TOKEN|SECRET|PASSWORD|PASSWD|${PAYMENT_CREDENTIAL_ENV_KEYS})\b\s*[=:]\s*\\+(["'])([^\s"'\\]+)\\+\1/g`,
55+ENV_ASSIGNMENT_REDACT_PATTERN,
56+ESCAPED_ENV_ASSIGNMENT_REDACT_PATTERN,
4757// URL query parameters. Keep this separate from ENV-style assignments so
4858// lower-case URL secrets stay redacted without hiding config-key diagnostics.
4959String.raw`/[?&](?:access[-_]?token|auth[-_]?token|hook[-_]?token|refresh[-_]?token|api[-_]?key|client[-_]?secret|token|key|secret|password|pass|passwd|auth|signature|${PAYMENT_CREDENTIAL_QUERY_KEYS})=([^&\s"'<>]+)/gi`,
@@ -62,7 +72,7 @@ const DEFAULT_REDACT_PATTERNS: string[] = [
6272String.raw`\bBearer\s+([A-Za-z0-9._\-+=]{18,})\b`,
6373// Standalone token assignments in CLI or HTTP diagnostics. URL query params
6474// are handled above so non-secret params survive and long values stay hinted.
65-String.raw`(^|[\s,;])(?:access_token|refresh_token|auth[-_]?token|api[-_]?key|client[-_]?secret|app[-_]?secret|token|secret|password|passwd|${PAYMENT_CREDENTIAL_QUERY_KEYS})=([^\s&#]+)`,
75+STANDALONE_ASSIGNMENT_REDACT_PATTERN,
6676// PEM blocks.
6777String.raw`-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]+?-----END [A-Z ]*PRIVATE KEY-----`,
6878// Common token prefixes.
@@ -103,21 +113,26 @@ function normalizeMode(value?: string): RedactSensitiveMode {
103113}
104114105115function parsePattern(raw: RedactPattern): RegExp | null {
116+let pattern: RegExp | null = null;
106117if (raw instanceof RegExp) {
107118if (raw.flags.includes("g")) {
108-return raw;
119+pattern = raw;
120+} else {
121+pattern = new RegExp(raw.source, `${raw.flags}g`);
122+}
123+} else if (raw.trim()) {
124+const match = raw.match(/^\/(.+)\/([gimsuy]*)$/);
125+if (match) {
126+const flags = match[2].includes("g") ? match[2] : `${match[2]}g`;
127+pattern = compileConfigRegex(match[1], flags)?.regex ?? null;
128+} else {
129+pattern = compileConfigRegex(raw, "gi")?.regex ?? null;
109130}
110-return new RegExp(raw.source, `${raw.flags}g`);
111-}
112-if (!raw.trim()) {
113-return null;
114131}
115-const match = raw.match(/^\/(.+)\/([gimsuy]*)$/);
116-if (match) {
117-const flags = match[2].includes("g") ? match[2] : `${match[2]}g`;
118-return compileConfigRegex(match[1], flags)?.regex ?? null;
132+if (pattern && typeof raw === "string" && SHELL_REFERENCE_PRESERVING_PATTERN_SOURCES.has(raw)) {
133+shellReferencePreservingPatterns.add(pattern);
119134}
120-return compileConfigRegex(raw, "gi")?.regex ?? null;
135+return pattern;
121136}
122137123138function resolvePatterns(value?: RedactPattern[]): RegExp[] {
@@ -142,11 +157,46 @@ function redactPemBlock(block: string): string {
142157return `${lines[0]}\n…redacted…\n${lines[lines.length - 1]}`;
143158}
144159145-function redactMatch(match: string, groups: string[]): string {
160+function isShellReferenceToKey(key: string, value: string): boolean {
161+if (!/^[A-Z_][A-Z0-9_]*$/.test(key)) {
162+return false;
163+}
164+const bare = value.match(/^\$([A-Z_][A-Z0-9_]*)$/);
165+if (bare) {
166+return bare[1] === key;
167+}
168+const braced = value.match(/^\$\{([A-Z_][A-Z0-9_]*)(?::[-=?+])?\}$/);
169+return braced?.[1] === key;
170+}
171+172+function readEnvAssignmentKey(match: string): string | undefined {
173+return match.match(/\b([A-Z_][A-Z0-9_]*)\b\s*[=:]/)?.[1];
174+}
175+176+function shouldPreserveShellReferenceMatch(match: string, token: string): boolean {
177+const key = readEnvAssignmentKey(match);
178+return key ? isShellReferenceToKey(key, token) : false;
179+}
180+181+function isEmptyShellParameterExpansionTail(token: string): boolean {
182+return /^[-=?+]\}$/.test(token);
183+}
184+185+function redactMatch(
186+match: string,
187+groups: string[],
188+options: { preserveShellReferences?: boolean } = {},
189+): string {
146190if (match.includes("PRIVATE KEY-----")) {
147191return redactPemBlock(match);
148192}
149193const token = groups.findLast((value) => typeof value === "string" && value.length > 0) ?? match;
194+const preserveShellReferences =
195+options.preserveShellReferences &&
196+(shouldPreserveShellReferenceMatch(match, token) || isEmptyShellParameterExpansionTail(token));
197+if (preserveShellReferences) {
198+return match;
199+}
150200const masked = maskToken(token);
151201if (token === match) {
152202return masked;
@@ -158,7 +208,9 @@ function redactText(text: string, patterns: RegExp[]): string {
158208let next = text;
159209for (const pattern of patterns) {
160210next = replacePatternBounded(next, pattern, (...args: string[]) =>
161-redactMatch(args[0], args.slice(1, -2)),
211+redactMatch(args[0], args.slice(1, -2), {
212+preserveShellReferences: shellReferencePreservingPatterns.has(pattern),
213+}),
162214);
163215}
164216return next;
@@ -272,6 +324,9 @@ function redactSensitiveFieldValueWithOptions(
272324return redacted;
273325}
274326if (isSensitiveFieldKey(key)) {
327+if (isShellReferenceToKey(key, value)) {
328+return value;
329+}
275330return maskToken(value);
276331}
277332return value;
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。