@walkeros/core 4.7.0-next-1790245733116 → 4.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +90 -55
- package/dist/dev.d.mts +155 -11
- package/dist/dev.d.ts +155 -11
- package/dist/dev.js +1 -1
- package/dist/dev.js.map +1 -1
- package/dist/dev.mjs +1 -1
- package/dist/dev.mjs.map +1 -1
- package/dist/index.d.mts +138 -7
- package/dist/index.d.ts +138 -7
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/node.d.mts +59 -24
- package/dist/node.d.ts +59 -24
- package/dist/node.js +1 -1
- package/dist/node.js.map +1 -1
- package/dist/node.mjs +1 -1
- package/dist/node.mjs.map +1 -1
- package/package.json +1 -1
package/dist/node.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/node.ts","../src/types/logger.ts","../src/logger.ts","../src/redactLine.ts"],"sourcesContent":["/**\n * Node entry point (`@walkeros/core/node`).\n *\n * Helpers shared by `@walkeros/cli` and `@walkeros/runner` that do not belong\n * in the browser `.` entry: the temp-path helpers import node builtins, the\n * terminal logger config writes to the console, and the secret redactor has\n * no browser consumer (in `.` it was not tree-shaken out of walker.js). Core gains no dependency\n * for them: the colouriser is injected by the caller, so chalk stays in the\n * packages that already depend on it.\n */\n\n/* eslint-disable no-console */\nimport os from 'os';\nimport path from 'path';\nimport { createLogger } from './logger';\nimport { scrubSecrets } from './redactLine';\n\nexport { scrubSecrets, redactLine } from './redactLine';\nimport type { Config, Instance } from './types/logger';\nimport { Level } from './types/logger';\n\nconst DEFAULT_TMP_ROOT = os.tmpdir();\n\n/**\n * Get a path within the temp directory.\n *\n * @param tmpDir - Custom temp directory (optional, for --tmp-dir flag)\n * @param segments - Path segments to join\n * @returns Absolute path within temp directory\n *\n * @example\n * ```typescript\n * getTmpPath() // → \"/tmp\"\n * getTmpPath(undefined, 'entry.js') // → \"/tmp/entry.js\"\n * getTmpPath(undefined, 'cache', 'builds') // → \"/tmp/cache/builds\"\n * getTmpPath('/custom', 'cache') // → \"/custom/cache\"\n * ```\n */\nexport function getTmpPath(tmpDir?: string, ...segments: string[]): string {\n const root = tmpDir || DEFAULT_TMP_ROOT;\n // Always return absolute path (esbuild requirement)\n const absoluteRoot = path.isAbsolute(root) ? root : path.resolve(root);\n return path.join(absoluteRoot, ...segments);\n}\n\n/**\n * A temp path resolver with the root directory baked in.\n *\n * Use {@link createTmpResolver} at entry points to capture the temp root once,\n * then pass the resolver to downstream functions. This prevents the class of\n * bugs where callers forget to pass tmpDir.\n *\n * @example\n * ```typescript\n * const tmp = createTmpResolver(buildOptions.tempDir);\n * const cacheDir = tmp('cache', 'packages'); // root is baked in\n * ```\n */\nexport type TmpResolver = (...segments: string[]) => string;\n\nexport function createTmpResolver(tmpDir?: string): TmpResolver {\n const root = tmpDir || DEFAULT_TMP_ROOT;\n const absoluteRoot = path.isAbsolute(root) ? root : path.resolve(root);\n return (...segments: string[]) => path.join(absoluteRoot, ...segments);\n}\n\n/**\n * Get the default temp root directory.\n */\nexport function getDefaultTmpRoot(): string {\n return DEFAULT_TMP_ROOT;\n}\n\nexport interface CLILoggerOptions {\n verbose?: boolean;\n silent?: boolean;\n json?: boolean;\n stderr?: boolean;\n onLine?: (level: Level, message: string) => void;\n}\n\n/** Formats one already-scrubbed line for the terminal. */\nexport type LineFormatter = (line: string) => string;\n\n/**\n * Per-level terminal formatters. Every level defaults to identity, so a caller\n * that passes nothing gets plain uncoloured output.\n */\nexport interface CLILoggerColors {\n error?: LineFormatter;\n warn?: LineFormatter;\n info?: LineFormatter;\n debug?: LineFormatter;\n}\n\nconst identity: LineFormatter = (line) => line;\n\n/**\n * Build the `Logger.Config` (level + handler + jsonHandler) that backs a\n * terminal logger. Returned separately so the runtime can hand the SAME\n * handler (including the `onLine` ring tap) to the deployed bundle's\n * collector.\n *\n * The collector in a deployed bundle builds its own logger from this config\n * (`config.logger`), so its destination errors flow through the identical\n * `onLine` tap the runtime logger uses, landing in the shared ErrorRing.\n * There is no parallel ring: one handler, one tap.\n *\n * Level: `Level.DEBUG` so the handler sees every line and controls visibility\n * itself (verbose/silent gating). ERROR is therefore always emitted into the\n * ring regardless of `--verbose`.\n *\n * `colors` formats each level for the terminal. It is applied to console\n * output only, never to the line handed to `onLine`.\n */\nexport function createCLILoggerConfig(\n options: CLILoggerOptions = {},\n colors: CLILoggerColors = {},\n): Config {\n const {\n verbose = false,\n silent = false,\n json = false,\n stderr = false,\n } = options;\n const out = stderr ? console.error : console.log;\n const errorColor = colors.error ?? identity;\n const warnColor = colors.warn ?? identity;\n const infoColor = colors.info ?? identity;\n const debugColor = colors.debug ?? identity;\n\n return {\n // Let handler control visibility: pass everything through. With the gate\n // at DEBUG, ERROR always reaches the handler (and the ring) even without\n // --verbose.\n level: Level.DEBUG,\n handler: (level, message, context, scope) => {\n // Build formatted message\n const scopePath = scope.length > 0 ? `[${scope.join(':')}] ` : '';\n // Serialize the structured context into the line so error details (gRPC\n // status codes, row counts, target tables) reach stderr and the ring.\n // Serialization happens BEFORE scrubSecrets so redaction covers context\n // values too; the heartbeat path's 256-char cap stays as the wire\n // backstop. A context that cannot stringify (circular) must never break\n // logging.\n let meta = '';\n if (Object.keys(context).length > 0) {\n try {\n meta = ` ${JSON.stringify(context)}`;\n } catch {\n meta = ' [unserializable context]';\n }\n }\n // Redact secrets ONCE here, before BOTH the onLine ring tap and the\n // console.* output. stderr is shipped directly by the log collector, so\n // the heartbeat-egress redactor alone would miss it; doing it in the\n // handler scrubs every line routed through this logger (collector and\n // steps) on both paths. Length is preserved here (no truncation); the\n // heartbeat path applies the 256-char wire cap separately as a backstop\n // on already-redacted text.\n const fullMessage = scrubSecrets(`${scopePath}${message}${meta}`);\n\n // Tap every line before any early return so no level is dropped from capture.\n try {\n options.onLine?.(level, fullMessage);\n } catch {\n // Swallow: a throwing consumer must never break logging (and we must\n // not call logger methods here to avoid infinite recursion).\n }\n\n // ERROR: always shown unless json mode\n if (level === Level.ERROR) {\n if (!json) console.error(errorColor(fullMessage));\n return;\n }\n\n // Non-errors suppressed in silent or json mode\n if (silent || json) return;\n\n // DEBUG: only with verbose\n if (level === Level.DEBUG) {\n if (!verbose) return;\n out(` ${debugColor(fullMessage)}`);\n return;\n }\n\n // WARN / INFO: normal output\n out(\n level === Level.WARN ? warnColor(fullMessage) : infoColor(fullMessage),\n );\n },\n jsonHandler: (data) => {\n if (!silent) out(JSON.stringify(data, null, 2));\n },\n };\n}\n\n/**\n * Create a core Logger.Instance with terminal-appropriate behavior.\n *\n * Behavior:\n * - ERROR: always shown (via console.error, `colors.error`) unless --json\n * - WARN: shown unless --silent or --json\n * - INFO: shown unless --silent or --json\n * - DEBUG: shown only with --verbose (and not --silent/--json)\n * - json(): shown unless --silent\n */\nexport function createCLILogger(\n options: CLILoggerOptions = {},\n colors: CLILoggerColors = {},\n): Instance {\n return createLogger(createCLILoggerConfig(options, colors));\n}\n","/**\n * Log levels from most to least severe\n */\nexport enum Level {\n ERROR = 0,\n WARN = 1,\n INFO = 2,\n DEBUG = 3,\n}\n\n/**\n * Normalized error context extracted from Error objects\n */\nexport interface ErrorContext {\n message: string;\n name: string;\n stack?: string;\n cause?: unknown;\n}\n\n/**\n * Context passed to log handlers\n * If an Error was passed, it's normalized into the error property\n */\nexport interface LogContext {\n [key: string]: unknown;\n error?: ErrorContext;\n}\n\n/**\n * Log message function signature\n * Accepts string or Error as message, with optional context\n */\nexport type LogFn = (\n message: string | Error,\n context?: unknown | Error,\n) => void;\n\n/**\n * Throw function signature - logs error then throws\n * Returns never as it always throws\n */\nexport type ThrowFn = (message: string | Error, context?: unknown) => never;\n\n/**\n * Default handler function (passed to custom handlers for chaining)\n */\nexport type DefaultHandler = (\n level: Level,\n message: string,\n context: LogContext,\n scope: string[],\n) => void;\n\n/**\n * Custom log handler function\n * Receives originalHandler to allow middleware-style chaining\n */\nexport type Handler = (\n level: Level,\n message: string,\n context: LogContext,\n scope: string[],\n originalHandler: DefaultHandler,\n) => void;\n\n/**\n * Logger instance with scoping support\n * All logs automatically include trace path from scoping\n */\nexport interface Instance {\n /**\n * Log an error message (always visible unless silenced)\n */\n error: LogFn;\n\n /**\n * Log a warning (degraded state, config issues, transient failures)\n */\n warn: LogFn;\n\n /**\n * Log an informational message\n */\n info: LogFn;\n\n /**\n * Log a debug message\n */\n debug: LogFn;\n\n /**\n * Log an error message and throw an Error\n * Combines logging and throwing in one call\n */\n throw: ThrowFn;\n\n /**\n * Output structured JSON data\n */\n json: (data: unknown) => void;\n\n /**\n * Create a scoped child logger with automatic trace path\n * @param name - Scope name (e.g., destination type, destination key)\n * @returns A new logger instance with the scope applied\n */\n scope: (name: string) => Instance;\n}\n\n/**\n * Logger configuration options\n */\nexport interface Config {\n /**\n * Minimum log level to display\n * @default Level.ERROR\n */\n level?: Level | keyof typeof Level;\n\n /**\n * Custom log handler function\n * Receives originalHandler to preserve default behavior\n */\n handler?: Handler;\n\n /** Custom handler for json() output. Default: console.log(JSON.stringify(data, null, 2)) */\n jsonHandler?: (data: unknown) => void;\n}\n\n/**\n * Internal config with resolved values and scope\n */\nexport interface InternalConfig {\n level: Level;\n handler?: Handler;\n jsonHandler?: (data: unknown) => void;\n scope: string[];\n}\n\n/**\n * Logger factory function type\n */\nexport type Factory = (config?: Config) => Instance;\n","import type {\n Config,\n DefaultHandler,\n ErrorContext,\n Handler,\n Instance,\n InternalConfig,\n LogContext,\n} from './types/logger';\nimport { Level } from './types/logger';\n\n/**\n * Normalize an Error object into ErrorContext\n */\nfunction normalizeError(err: Error): ErrorContext {\n return {\n message: err.message,\n name: err.name,\n stack: err.stack,\n cause: (err as Error & { cause?: unknown }).cause,\n };\n}\n\n/**\n * Normalize message and context parameters\n * Handles Error objects passed as message or context\n */\nfunction normalizeParams(\n message: string | Error,\n context?: unknown | Error,\n): { message: string; context: LogContext } {\n let normalizedMessage: string;\n let normalizedContext: LogContext = {};\n\n // Handle message\n if (message instanceof Error) {\n normalizedMessage = message.message;\n normalizedContext.error = normalizeError(message);\n } else {\n normalizedMessage = message;\n }\n\n // Handle context\n if (context !== undefined) {\n if (context instanceof Error) {\n normalizedContext.error = normalizeError(context);\n } else if (typeof context === 'object' && context !== null) {\n normalizedContext = { ...normalizedContext, ...(context as object) };\n // If context has an error property that's an Error, normalize it\n if (\n 'error' in normalizedContext &&\n normalizedContext.error instanceof Error\n ) {\n normalizedContext.error = normalizeError(\n normalizedContext.error as unknown as Error,\n );\n }\n } else {\n normalizedContext.value = context;\n }\n }\n\n return { message: normalizedMessage, context: normalizedContext };\n}\n\n/**\n * Default console-based log handler\n */\nconst defaultHandler: DefaultHandler = (level, message, context, scope) => {\n const levelName = Level[level];\n const scopePath = scope.length > 0 ? ` [${scope.join(':')}]` : '';\n const prefix = `${levelName}${scopePath}`;\n\n const hasContext = Object.keys(context).length > 0;\n\n const consoleFn =\n level === Level.ERROR\n ? console.error\n : level === Level.WARN\n ? console.warn\n : console.log;\n\n if (hasContext) {\n consoleFn(prefix, message, context);\n } else {\n consoleFn(prefix, message);\n }\n};\n\n/**\n * Normalize log level from string or enum to enum value\n */\nfunction normalizeLevel(level: Level | keyof typeof Level): Level {\n if (typeof level === 'string') {\n return Level[level as keyof typeof Level];\n }\n return level;\n}\n\n/**\n * Create a logger instance\n *\n * @param config - Logger configuration\n * @returns Logger instance with all log methods and scoping support\n *\n * @example\n * ```typescript\n * // Basic usage\n * const logger = createLogger({ level: 'DEBUG' });\n * logger.info('Hello world');\n *\n * // With scoping\n * const destLogger = logger.scope('gtag').scope('myInstance');\n * destLogger.debug('Processing event'); // DEBUG [gtag:myInstance] Processing event\n *\n * // With custom handler\n * const logger = createLogger({\n * handler: (level, message, context, scope, originalHandler) => {\n * // Custom logic (e.g., send to Sentry)\n * originalHandler(level, message, context, scope);\n * }\n * });\n * ```\n *\n * // TODO: Consider compile-time stripping of debug logs in production builds\n * // e.g., if (__DEV__) { logger.debug(...) }\n */\nexport function createLogger(config: Config = {}): Instance {\n const level =\n config.level !== undefined ? normalizeLevel(config.level) : Level.ERROR;\n const customHandler = config.handler;\n const jsonHandler = config.jsonHandler;\n const scope: string[] = [];\n\n return createLoggerInternal({\n level,\n handler: customHandler,\n jsonHandler,\n scope,\n });\n}\n\n/**\n * Internal logger creation with resolved config\n */\nfunction createLoggerInternal(config: InternalConfig): Instance {\n const { level, handler, jsonHandler, scope } = config;\n\n /**\n * Internal log function that checks level and delegates to handler\n */\n const log = (\n logLevel: Level,\n message: string | Error,\n context?: unknown | Error,\n ): void => {\n if (logLevel <= level) {\n const normalized = normalizeParams(message, context);\n\n if (handler) {\n // Custom handler with access to default handler\n handler(\n logLevel,\n normalized.message,\n normalized.context,\n scope,\n defaultHandler,\n );\n } else {\n defaultHandler(logLevel, normalized.message, normalized.context, scope);\n }\n }\n };\n\n /**\n * Log and throw - combines logging and throwing\n */\n const logAndThrow = (message: string | Error, context?: unknown): never => {\n // Always log errors regardless of level\n const normalized = normalizeParams(message, context);\n\n if (handler) {\n handler(\n Level.ERROR,\n normalized.message,\n normalized.context,\n scope,\n defaultHandler,\n );\n } else {\n defaultHandler(\n Level.ERROR,\n normalized.message,\n normalized.context,\n scope,\n );\n }\n\n // Throw with the message\n throw new Error(normalized.message);\n };\n\n return {\n error: (message, context) => log(Level.ERROR, message, context),\n warn: (message, context) => log(Level.WARN, message, context),\n info: (message, context) => log(Level.INFO, message, context),\n debug: (message, context) => log(Level.DEBUG, message, context),\n throw: logAndThrow,\n json: (data: unknown) => {\n if (jsonHandler) {\n jsonHandler(data);\n } else {\n // eslint-disable-next-line no-console\n console.log(JSON.stringify(data, null, 2));\n }\n },\n scope: (name: string) =>\n createLoggerInternal({\n level,\n handler,\n jsonHandler,\n scope: [...scope, name],\n }),\n };\n}\n\n// Re-export Level enum for convenience\nexport { Level };\n","/**\n * Shared secret redactor. One set of patterns for every path that egresses a\n * log line: the CLI logger handler and the runtime logger handler (scrubbing\n * console and the error ring), and the runtime heartbeat (scrubbing the ring\n * snapshot before the POST). A pattern added here covers all of them.\n *\n * Two entry points:\n * - `scrubSecrets(line)`: mask secrets, keep the message length intact (for\n * console/stderr legibility).\n * - `redactLine(line)`: `scrubSecrets` plus truncation to the 256-char\n * heartbeat wire contract.\n *\n * All regex are linear (single unambiguous quantifiers, no nested quantifiers),\n * so there is no catastrophic backtracking even on a BEGIN-marker-with-no-END\n * input or a long no-space line. Exported from `@walkeros/core/node` only: no\n * browser consumer needs it, and it must not ride into web bundles.\n */\n\nconst MAX_LENGTH = 256;\n\n// Minimum length for a standalone run to be considered a candidate token.\nconst MIN_TOKEN_LEN = 20;\n\n// Shannon entropy threshold (bits/char). Random tokens score high (~4.5-6),\n// natural-language words and identifiers score low (~2-3.5).\nconst ENTROPY_THRESHOLD = 4.0;\n\n// Known secret prefixes that force-mask their run regardless of entropy/shape.\nconst FORCE_MASK_PREFIXES = [\n 'sk-',\n 'sk_',\n 'pk_',\n 'ghp_',\n 'gho_',\n 'xoxb-',\n 'xoxp-',\n 'AKIA',\n];\n\n// ── Regex constants (all linear, no nested quantifiers) ─────────────────────\n\n// URL credentials: scheme://user:password@host, keep user, mask password.\nconst RE_URL_CREDS = /(:\\/\\/[^/:@\\s]+:)[^@\\s]+(@)/g;\n\n// JSON service-account field values, catches service-account blobs.\n// `private_key` holds the PEM body; `client_email`, `private_key_id`,\n// `client_id`, and `client_x509_cert_url` identify and pair with the leaked\n// credential and must not ship either (the cert url embeds the URL-encoded\n// client email). Naming them explicitly beats relying on the token-run\n// heuristics, which can miss an email or a URL. The value may contain escaped\n// newlines (\\n as literal backslash-n) which do not split the line. [^\"] is\n// safe: these values contain no embedded quotes. One alternation over the field\n// names keeps the scan single-pass and linear.\nconst RE_JSON_SA_FIELD =\n /(\"(?:private_key|client_email|private_key_id|client_id|client_x509_cert_url)\"\\s*:\\s*)\"[^\"]*\"/g;\n\n// PEM block boundaries (structural removal, not regex masking).\n// Case-insensitive, tolerate leading whitespace so a lowercase or indented\n// `-----begin private key-----` block does not leak its body.\nconst RE_PEM_BEGIN = /^\\s*-----BEGIN [A-Z ]*PRIVATE KEY-----/i;\nconst RE_PEM_END = /-----END[A-Z -]*-----/i;\n\n// KEY=value / KEY: value secret pattern.\n// ReDoS-safe: the key is anchored to start-of-line or whitespace and bounded to\n// 64 chars ({0,63} after the first char), so there is no O(n²) backtracking on\n// long no-space lines. The value class includes - and . so hyphenated/dotted\n// tokens are captured whole. Two capture groups are preserved in the\n// replacement: the leading (^|\\s) boundary and the key=.\nconst TOKEN_VALUE_CLASS = '[A-Za-z0-9+/._-]';\nconst RE_KV_SECRET = new RegExp(\n `(^|\\\\s)([A-Za-z_][A-Za-z0-9_.]{0,63}\\\\s*[=:]\\\\s*)(${TOKEN_VALUE_CLASS}{12,}={0,2})`,\n 'g',\n);\n\n// Standalone candidate runs: any run of token chars (incl. - and .) ≥ 20.\n// The decision to mask happens in the callback (shouldMaskToken), keeping the\n// regex itself trivial and linear.\nconst RE_TOKEN_RUN = new RegExp(`${TOKEN_VALUE_CLASS}{${MIN_TOKEN_LEN},}`, 'g');\n\n// Force-mask prefix detector for runs that carry a known secret prefix.\n// Matches a known prefix followed by token chars (any length), so e.g.\n// `ghp_xxxxxxxx` or `xoxb-2384-2384-AbCdEf` is masked even under 20 chars.\n// Built from FORCE_MASK_PREFIXES so the list is the single source of truth.\n// Prefixes are regex-escaped (only `-`/`_` appear, both literal here, but escape\n// defensively) and ordered longest-first so the alternation is unambiguous.\nfunction escapeRegex(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\nconst PREFIX_ALTERNATION = [...FORCE_MASK_PREFIXES]\n .sort((a, b) => b.length - a.length)\n .map(escapeRegex)\n .join('|');\nconst RE_PREFIXED_TOKEN = new RegExp(\n `(?:${PREFIX_ALTERNATION})${TOKEN_VALUE_CLASS}*`,\n 'g',\n);\n\n// ── Entropy ──────────────────────────────────────────────────────────────────\n\n/** Shannon entropy in bits per character. Linear in input length. */\nfunction shannonEntropy(s: string): number {\n if (s.length === 0) return 0;\n const counts = new Map<string, number>();\n for (const ch of s) counts.set(ch, (counts.get(ch) ?? 0) + 1);\n let entropy = 0;\n for (const count of counts.values()) {\n const p = count / s.length;\n entropy -= p * Math.log2(p);\n }\n return entropy;\n}\n\n// ── Token masking decision ─────────────────────────────────────────────────\n\nconst RE_ALL_HEX = /^[0-9a-fA-F]+$/;\nconst RE_ALL_DIGIT = /^[0-9]+$/;\n// Real base64 padding/special chars. `/` is intentionally EXCLUDED: it is a\n// structural path/URL separator (FQNs, gs:// paths, request paths, googleapis\n// URLs), not a secret signal. Since redaction now runs on every console line,\n// treating `/` as a secret char nuked legitimate BigQuery/GCS diagnostics. Real\n// base64 secrets still carry `+`/`=` padding, are all-hex, or are high-entropy.\nconst RE_BASE64_SPECIAL = /[+=]/;\nconst RE_HAS_DIGIT = /[0-9]/;\nconst RE_HAS_LETTER = /[A-Za-z]/;\n\n/**\n * Decide whether a candidate run (a single token, no `/`) is a secret to mask.\n *\n * Mask when ANY of:\n * - all-hex (digests, SHA, etc.)\n * - all-digit (numeric tokens/ids)\n * - contains a base64 special char (+ =)\n * - contains both a digit and a letter (mixed token)\n * - Shannon entropy ≥ 4.0 bits/char (random all-letter / mixed-case tokens)\n */\nfunction isSecretSegment(run: string): boolean {\n if (RE_ALL_HEX.test(run)) return true;\n if (RE_ALL_DIGIT.test(run)) return true;\n if (RE_BASE64_SPECIAL.test(run)) return true;\n if (RE_HAS_DIGIT.test(run) && RE_HAS_LETTER.test(run)) return true;\n if (shannonEntropy(run) >= ENTROPY_THRESHOLD) return true;\n return false;\n}\n\n/**\n * Decide whether a ≥20-char candidate run is a secret to mask.\n *\n * A run containing `/` is treated as a path/URL structure, not one opaque token.\n * Paths and URLs split into short alphanumeric segments (`v2`, `projects`,\n * `12345`), each of which trips the digit+letter or entropy heuristic on its own\n * even though the whole thing is legible diagnostics. So a `/`-bearing run is\n * masked only when one of its long (≥20-char) segments independently looks like\n * a secret (e.g. a base64 blob embedded in a URL path). A base64 secret with `/`\n * still carries `+`/`=` (handled per-segment) or is hex/high-entropy, so real\n * secrets remain masked while FQNs, gs:// paths, request paths, and googleapis\n * URLs survive.\n *\n * ACCEPTED best-effort limitation: a bare all-letters run with no digit, no\n * special char, no known prefix, and entropy < 4.0 (e.g. `supercalifragilistic`\n * or a long camelCase identifier like `getUserAccountSettings`) may pass through\n * unmasked. Masking every such run would destroy log readability by redacting\n * ordinary identifiers and words, which defeats the purpose of legible\n * diagnostics. This is a deliberate trade-off, not an oversight.\n */\nfunction shouldMaskToken(run: string): boolean {\n if (run.includes('/')) {\n // Per-segment: only mask if a long segment is itself secret-shaped. Short\n // path segments (the bulk of any URL) never qualify, keeping URLs legible.\n return run\n .split('/')\n .filter((segment) => segment.length >= MIN_TOKEN_LEN)\n .some(isSecretSegment);\n }\n return isSecretSegment(run);\n}\n\n// ── PEM block removal ────────────────────────────────────────────────────────\n\n/**\n * Remove all PEM private-key blocks from an array of lines.\n * A block starts at a BEGIN PRIVATE KEY marker and ends at the matching END\n * marker (inclusive). If no END is found, everything to end of entry is dropped.\n */\nfunction removePemBlocks(lines: string[]): string[] {\n const out: string[] = [];\n let inBlock = false;\n\n for (const line of lines) {\n if (!inBlock) {\n if (RE_PEM_BEGIN.test(line)) {\n inBlock = true;\n continue; // start-of-block line dropped\n }\n out.push(line);\n } else {\n if (RE_PEM_END.test(line)) {\n inBlock = false;\n }\n // drop every in-block line, including the END marker\n }\n }\n\n return out;\n}\n\n// ── Per-line secret masking ──────────────────────────────────────────────────\n\n/**\n * Mask secrets in a single (non-PEM) line.\n * All patterns are applied in order; all regex are linear (no nested quantifiers).\n */\nfunction maskLine(line: string): string {\n let s = line;\n\n // 1. URL credentials: ://user:secret@host → ://user:***@host\n s = s.replace(RE_URL_CREDS, '$1***$2');\n\n // 2. KEY=secret / KEY: secret (anchored, bounded key, ReDoS-safe).\n // Mask the value only when it is secret-shaped. This stops `scheme://host/...`\n // (matched as `gs:` / `https:` key + `//...` value) from masking legitimate\n // URLs/paths, while real `TOKEN=secret` values still mask. The `/`-aware\n // shouldMaskToken keeps path-shaped values legible.\n s = s.replace(\n RE_KV_SECRET,\n (match: string, boundary: string, keyPart: string, value: string) =>\n shouldMaskToken(value) ? `${boundary}${keyPart}***` : match,\n );\n\n // 3. Known-prefix tokens (force-mask regardless of length/entropy)\n s = s.replace(RE_PREFIXED_TOKEN, '***');\n\n // 4. Standalone candidate runs ≥ 20 chars, mask by shape/entropy\n s = s.replace(RE_TOKEN_RUN, (match) =>\n shouldMaskToken(match) ? '***' : match,\n );\n\n return s;\n}\n\n// ── Public API ───────────────────────────────────────────────────────────────\n\n/**\n * Scrub credential material from a log line (or multi-line entry) WITHOUT\n * truncating. This is the shared secret redactor used by the CLI logger handler\n * (covers console/stderr and the ring before egress).\n *\n * Algorithm:\n * 1. Mask JSON service-account fields (private_key, client_email,\n * private_key_id, client_id, client_x509_cert_url) BEFORE splitting\n * (service-account blobs embed PEM blocks as \\\\n-encoded strings; masking\n * first prevents the BEGIN marker from appearing on its own line and dropping\n * surrounding fields).\n * 2. Split on \\\\n.\n * 3. Remove PEM private-key blocks structurally (BEGIN…END inclusive,\n * case-insensitive; a no-END block drops to end of entry).\n * 4. For each surviving line: mask URL creds, KEY=secret, prefixed tokens,\n * high-entropy/shape-based token runs.\n * 5. Rejoin with \\\\n.\n */\nexport function scrubSecrets(line: string): string {\n // Step 1: JSON service-account field masking before any line splitting\n const withoutJsonKey = line.replace(RE_JSON_SA_FIELD, '$1\"***\"');\n\n // Step 2: split\n const rawLines = withoutJsonKey.split('\\n');\n\n // Step 3: remove PEM blocks (any remaining standalone markers after step 1)\n const cleanLines = removePemBlocks(rawLines);\n\n // Step 4: mask each surviving line\n const maskedLines = cleanLines.map(maskLine);\n\n // Step 5: rejoin\n return maskedLines.join('\\n');\n}\n\n/**\n * Redact a log line and truncate to the heartbeat wire contract (256 chars).\n * This is the heartbeat-egress variant: scrub secrets, then truncate AFTER\n * masking so a token straddling char 256 is masked, not left partially visible.\n *\n * The app's heartbeat schema enforces message .max(256); a 257-char string\n * would fail zod .parse and drop the whole heartbeat. Reserve one char for the\n * ellipsis so the TOTAL length is at most MAX_LENGTH (256).\n */\nexport function redactLine(line: string): string {\n const scrubbed = scrubSecrets(line);\n\n if (scrubbed.length > MAX_LENGTH) {\n return scrubbed.slice(0, MAX_LENGTH - 1) + '…';\n }\n\n return scrubbed;\n}\n"],"mappings":";AAYA,OAAO,QAAQ;AACf,OAAO,UAAU;;;ACVV,IAAK,QAAL,kBAAKA,WAAL;AACL,EAAAA,cAAA,WAAQ,KAAR;AACA,EAAAA,cAAA,UAAO,KAAP;AACA,EAAAA,cAAA,UAAO,KAAP;AACA,EAAAA,cAAA,WAAQ,KAAR;AAJU,SAAAA;AAAA,GAAA;;;ACWZ,SAAS,eAAe,KAA0B;AAChD,SAAO;AAAA,IACL,SAAS,IAAI;AAAA,IACb,MAAM,IAAI;AAAA,IACV,OAAO,IAAI;AAAA,IACX,OAAQ,IAAoC;AAAA,EAC9C;AACF;AAMA,SAAS,gBACP,SACA,SAC0C;AAC1C,MAAI;AACJ,MAAI,oBAAgC,CAAC;AAGrC,MAAI,mBAAmB,OAAO;AAC5B,wBAAoB,QAAQ;AAC5B,sBAAkB,QAAQ,eAAe,OAAO;AAAA,EAClD,OAAO;AACL,wBAAoB;AAAA,EACtB;AAGA,MAAI,YAAY,QAAW;AACzB,QAAI,mBAAmB,OAAO;AAC5B,wBAAkB,QAAQ,eAAe,OAAO;AAAA,IAClD,WAAW,OAAO,YAAY,YAAY,YAAY,MAAM;AAC1D,0BAAoB,EAAE,GAAG,mBAAmB,GAAI,QAAmB;AAEnE,UACE,WAAW,qBACX,kBAAkB,iBAAiB,OACnC;AACA,0BAAkB,QAAQ;AAAA,UACxB,kBAAkB;AAAA,QACpB;AAAA,MACF;AAAA,IACF,OAAO;AACL,wBAAkB,QAAQ;AAAA,IAC5B;AAAA,EACF;AAEA,SAAO,EAAE,SAAS,mBAAmB,SAAS,kBAAkB;AAClE;AAKA,IAAM,iBAAiC,CAAC,OAAO,SAAS,SAAS,UAAU;AACzE,QAAM,YAAY,MAAM,KAAK;AAC7B,QAAM,YAAY,MAAM,SAAS,IAAI,KAAK,MAAM,KAAK,GAAG,CAAC,MAAM;AAC/D,QAAM,SAAS,GAAG,SAAS,GAAG,SAAS;AAEvC,QAAM,aAAa,OAAO,KAAK,OAAO,EAAE,SAAS;AAEjD,QAAM,YACJ,0BACI,QAAQ,QACR,yBACE,QAAQ,OACR,QAAQ;AAEhB,MAAI,YAAY;AACd,cAAU,QAAQ,SAAS,OAAO;AAAA,EACpC,OAAO;AACL,cAAU,QAAQ,OAAO;AAAA,EAC3B;AACF;AAKA,SAAS,eAAe,OAA0C;AAChE,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO,MAAM,KAA2B;AAAA,EAC1C;AACA,SAAO;AACT;AA8BO,SAAS,aAAa,SAAiB,CAAC,GAAa;AAC1D,QAAM,QACJ,OAAO,UAAU,SAAY,eAAe,OAAO,KAAK;AAC1D,QAAM,gBAAgB,OAAO;AAC7B,QAAM,cAAc,OAAO;AAC3B,QAAM,QAAkB,CAAC;AAEzB,SAAO,qBAAqB;AAAA,IAC1B;AAAA,IACA,SAAS;AAAA,IACT;AAAA,IACA;AAAA,EACF,CAAC;AACH;AAKA,SAAS,qBAAqB,QAAkC;AAC9D,QAAM,EAAE,OAAO,SAAS,aAAa,MAAM,IAAI;AAK/C,QAAM,MAAM,CACV,UACA,SACA,YACS;AACT,QAAI,YAAY,OAAO;AACrB,YAAM,aAAa,gBAAgB,SAAS,OAAO;AAEnD,UAAI,SAAS;AAEX;AAAA,UACE;AAAA,UACA,WAAW;AAAA,UACX,WAAW;AAAA,UACX;AAAA,UACA;AAAA,QACF;AAAA,MACF,OAAO;AACL,uBAAe,UAAU,WAAW,SAAS,WAAW,SAAS,KAAK;AAAA,MACxE;AAAA,IACF;AAAA,EACF;AAKA,QAAM,cAAc,CAAC,SAAyB,YAA6B;AAEzE,UAAM,aAAa,gBAAgB,SAAS,OAAO;AAEnD,QAAI,SAAS;AACX;AAAA;AAAA,QAEE,WAAW;AAAA,QACX,WAAW;AAAA,QACX;AAAA,QACA;AAAA,MACF;AAAA,IACF,OAAO;AACL;AAAA;AAAA,QAEE,WAAW;AAAA,QACX,WAAW;AAAA,QACX;AAAA,MACF;AAAA,IACF;AAGA,UAAM,IAAI,MAAM,WAAW,OAAO;AAAA,EACpC;AAEA,SAAO;AAAA,IACL,OAAO,CAAC,SAAS,YAAY,mBAAiB,SAAS,OAAO;AAAA,IAC9D,MAAM,CAAC,SAAS,YAAY,kBAAgB,SAAS,OAAO;AAAA,IAC5D,MAAM,CAAC,SAAS,YAAY,kBAAgB,SAAS,OAAO;AAAA,IAC5D,OAAO,CAAC,SAAS,YAAY,mBAAiB,SAAS,OAAO;AAAA,IAC9D,OAAO;AAAA,IACP,MAAM,CAAC,SAAkB;AACvB,UAAI,aAAa;AACf,oBAAY,IAAI;AAAA,MAClB,OAAO;AAEL,gBAAQ,IAAI,KAAK,UAAU,MAAM,MAAM,CAAC,CAAC;AAAA,MAC3C;AAAA,IACF;AAAA,IACA,OAAO,CAAC,SACN,qBAAqB;AAAA,MACnB;AAAA,MACA;AAAA,MACA;AAAA,MACA,OAAO,CAAC,GAAG,OAAO,IAAI;AAAA,IACxB,CAAC;AAAA,EACL;AACF;;;AC9MA,IAAM,aAAa;AAGnB,IAAM,gBAAgB;AAItB,IAAM,oBAAoB;AAG1B,IAAM,sBAAsB;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAKA,IAAM,eAAe;AAWrB,IAAM,mBACJ;AAKF,IAAM,eAAe;AACrB,IAAM,aAAa;AAQnB,IAAM,oBAAoB;AAC1B,IAAM,eAAe,IAAI;AAAA,EACvB,qDAAqD,iBAAiB;AAAA,EACtE;AACF;AAKA,IAAM,eAAe,IAAI,OAAO,GAAG,iBAAiB,IAAI,aAAa,MAAM,GAAG;AAQ9E,SAAS,YAAY,GAAmB;AACtC,SAAO,EAAE,QAAQ,uBAAuB,MAAM;AAChD;AACA,IAAM,qBAAqB,CAAC,GAAG,mBAAmB,EAC/C,KAAK,CAAC,GAAG,MAAM,EAAE,SAAS,EAAE,MAAM,EAClC,IAAI,WAAW,EACf,KAAK,GAAG;AACX,IAAM,oBAAoB,IAAI;AAAA,EAC5B,MAAM,kBAAkB,IAAI,iBAAiB;AAAA,EAC7C;AACF;AAKA,SAAS,eAAe,GAAmB;AACzC,MAAI,EAAE,WAAW,EAAG,QAAO;AAC3B,QAAM,SAAS,oBAAI,IAAoB;AACvC,aAAW,MAAM,EAAG,QAAO,IAAI,KAAK,OAAO,IAAI,EAAE,KAAK,KAAK,CAAC;AAC5D,MAAI,UAAU;AACd,aAAW,SAAS,OAAO,OAAO,GAAG;AACnC,UAAM,IAAI,QAAQ,EAAE;AACpB,eAAW,IAAI,KAAK,KAAK,CAAC;AAAA,EAC5B;AACA,SAAO;AACT;AAIA,IAAM,aAAa;AACnB,IAAM,eAAe;AAMrB,IAAM,oBAAoB;AAC1B,IAAM,eAAe;AACrB,IAAM,gBAAgB;AAYtB,SAAS,gBAAgB,KAAsB;AAC7C,MAAI,WAAW,KAAK,GAAG,EAAG,QAAO;AACjC,MAAI,aAAa,KAAK,GAAG,EAAG,QAAO;AACnC,MAAI,kBAAkB,KAAK,GAAG,EAAG,QAAO;AACxC,MAAI,aAAa,KAAK,GAAG,KAAK,cAAc,KAAK,GAAG,EAAG,QAAO;AAC9D,MAAI,eAAe,GAAG,KAAK,kBAAmB,QAAO;AACrD,SAAO;AACT;AAsBA,SAAS,gBAAgB,KAAsB;AAC7C,MAAI,IAAI,SAAS,GAAG,GAAG;AAGrB,WAAO,IACJ,MAAM,GAAG,EACT,OAAO,CAAC,YAAY,QAAQ,UAAU,aAAa,EACnD,KAAK,eAAe;AAAA,EACzB;AACA,SAAO,gBAAgB,GAAG;AAC5B;AASA,SAAS,gBAAgB,OAA2B;AAClD,QAAM,MAAgB,CAAC;AACvB,MAAI,UAAU;AAEd,aAAW,QAAQ,OAAO;AACxB,QAAI,CAAC,SAAS;AACZ,UAAI,aAAa,KAAK,IAAI,GAAG;AAC3B,kBAAU;AACV;AAAA,MACF;AACA,UAAI,KAAK,IAAI;AAAA,IACf,OAAO;AACL,UAAI,WAAW,KAAK,IAAI,GAAG;AACzB,kBAAU;AAAA,MACZ;AAAA,IAEF;AAAA,EACF;AAEA,SAAO;AACT;AAQA,SAAS,SAAS,MAAsB;AACtC,MAAI,IAAI;AAGR,MAAI,EAAE,QAAQ,cAAc,SAAS;AAOrC,MAAI,EAAE;AAAA,IACJ;AAAA,IACA,CAAC,OAAe,UAAkB,SAAiB,UACjD,gBAAgB,KAAK,IAAI,GAAG,QAAQ,GAAG,OAAO,QAAQ;AAAA,EAC1D;AAGA,MAAI,EAAE,QAAQ,mBAAmB,KAAK;AAGtC,MAAI,EAAE;AAAA,IAAQ;AAAA,IAAc,CAAC,UAC3B,gBAAgB,KAAK,IAAI,QAAQ;AAAA,EACnC;AAEA,SAAO;AACT;AAsBO,SAAS,aAAa,MAAsB;AAEjD,QAAM,iBAAiB,KAAK,QAAQ,kBAAkB,SAAS;AAG/D,QAAM,WAAW,eAAe,MAAM,IAAI;AAG1C,QAAM,aAAa,gBAAgB,QAAQ;AAG3C,QAAM,cAAc,WAAW,IAAI,QAAQ;AAG3C,SAAO,YAAY,KAAK,IAAI;AAC9B;AAWO,SAAS,WAAW,MAAsB;AAC/C,QAAM,WAAW,aAAa,IAAI;AAElC,MAAI,SAAS,SAAS,YAAY;AAChC,WAAO,SAAS,MAAM,GAAG,aAAa,CAAC,IAAI;AAAA,EAC7C;AAEA,SAAO;AACT;;;AHhRA,IAAM,mBAAmB,GAAG,OAAO;AAiB5B,SAAS,WAAW,WAAoB,UAA4B;AACzE,QAAM,OAAO,UAAU;AAEvB,QAAM,eAAe,KAAK,WAAW,IAAI,IAAI,OAAO,KAAK,QAAQ,IAAI;AACrE,SAAO,KAAK,KAAK,cAAc,GAAG,QAAQ;AAC5C;AAiBO,SAAS,kBAAkB,QAA8B;AAC9D,QAAM,OAAO,UAAU;AACvB,QAAM,eAAe,KAAK,WAAW,IAAI,IAAI,OAAO,KAAK,QAAQ,IAAI;AACrE,SAAO,IAAI,aAAuB,KAAK,KAAK,cAAc,GAAG,QAAQ;AACvE;AAKO,SAAS,oBAA4B;AAC1C,SAAO;AACT;AAwBA,IAAM,WAA0B,CAAC,SAAS;AAoBnC,SAAS,sBACd,UAA4B,CAAC,GAC7B,SAA0B,CAAC,GACnB;AACR,QAAM;AAAA,IACJ,UAAU;AAAA,IACV,SAAS;AAAA,IACT,OAAO;AAAA,IACP,SAAS;AAAA,EACX,IAAI;AACJ,QAAM,MAAM,SAAS,QAAQ,QAAQ,QAAQ;AAC7C,QAAM,aAAa,OAAO,SAAS;AACnC,QAAM,YAAY,OAAO,QAAQ;AACjC,QAAM,YAAY,OAAO,QAAQ;AACjC,QAAM,aAAa,OAAO,SAAS;AAEnC,SAAO;AAAA;AAAA;AAAA;AAAA,IAIL;AAAA,IACA,SAAS,CAAC,OAAO,SAAS,SAAS,UAAU;AAE3C,YAAM,YAAY,MAAM,SAAS,IAAI,IAAI,MAAM,KAAK,GAAG,CAAC,OAAO;AAO/D,UAAI,OAAO;AACX,UAAI,OAAO,KAAK,OAAO,EAAE,SAAS,GAAG;AACnC,YAAI;AACF,iBAAO,IAAI,KAAK,UAAU,OAAO,CAAC;AAAA,QACpC,QAAQ;AACN,iBAAO;AAAA,QACT;AAAA,MACF;AAQA,YAAM,cAAc,aAAa,GAAG,SAAS,GAAG,OAAO,GAAG,IAAI,EAAE;AAGhE,UAAI;AACF,gBAAQ,SAAS,OAAO,WAAW;AAAA,MACrC,QAAQ;AAAA,MAGR;AAGA,UAAI,yBAAuB;AACzB,YAAI,CAAC,KAAM,SAAQ,MAAM,WAAW,WAAW,CAAC;AAChD;AAAA,MACF;AAGA,UAAI,UAAU,KAAM;AAGpB,UAAI,yBAAuB;AACzB,YAAI,CAAC,QAAS;AACd,YAAI,KAAK,WAAW,WAAW,CAAC,EAAE;AAClC;AAAA,MACF;AAGA;AAAA,QACE,yBAAuB,UAAU,WAAW,IAAI,UAAU,WAAW;AAAA,MACvE;AAAA,IACF;AAAA,IACA,aAAa,CAAC,SAAS;AACrB,UAAI,CAAC,OAAQ,KAAI,KAAK,UAAU,MAAM,MAAM,CAAC,CAAC;AAAA,IAChD;AAAA,EACF;AACF;AAYO,SAAS,gBACd,UAA4B,CAAC,GAC7B,SAA0B,CAAC,GACjB;AACV,SAAO,aAAa,sBAAsB,SAAS,MAAM,CAAC;AAC5D;","names":["Level"]}
|
|
1
|
+
{"version":3,"sources":["../src/node.ts","../src/types/logger.ts","../src/logger.ts","../src/redactLine.ts","../src/toPrintable.ts","../src/scrubJson.ts"],"sourcesContent":["/**\n * Node entry point (`@walkeros/core/node`).\n *\n * Helpers shared by `@walkeros/cli` and `@walkeros/runner` that do not belong\n * in the browser `.` entry: the temp-path helpers import node builtins, the\n * terminal logger config writes to the console, and the secret redactor and\n * the simulate output formatter (`toPrintable`, it reads `Buffer`) have\n * no browser consumer (in `.` it was not tree-shaken out of walker.js). Core gains no dependency\n * for them: the colouriser is injected by the caller, so chalk stays in the\n * packages that already depend on it.\n */\n\n/* eslint-disable no-console */\nimport os from 'os';\nimport path from 'path';\nimport { createLogger } from './logger';\nimport { maskKnownValues, scrubSecrets } from './redactLine';\n\nexport { scrubSecrets, redactLine } from './redactLine';\nexport type { ScrubOptions } from './redactLine';\nexport { scrubJson } from './scrubJson';\nexport { toPrintable } from './toPrintable';\nimport type { Config, Instance } from './types/logger';\nimport { Level } from './types/logger';\n\nconst DEFAULT_TMP_ROOT = os.tmpdir();\n\n/**\n * Get a path within the temp directory.\n *\n * @param tmpDir - Custom temp directory (optional, for --tmp-dir flag)\n * @param segments - Path segments to join\n * @returns Absolute path within temp directory\n *\n * @example\n * ```typescript\n * getTmpPath() // → \"/tmp\"\n * getTmpPath(undefined, 'entry.js') // → \"/tmp/entry.js\"\n * getTmpPath(undefined, 'cache', 'builds') // → \"/tmp/cache/builds\"\n * getTmpPath('/custom', 'cache') // → \"/custom/cache\"\n * ```\n */\nexport function getTmpPath(tmpDir?: string, ...segments: string[]): string {\n const root = tmpDir || DEFAULT_TMP_ROOT;\n // Always return absolute path (esbuild requirement)\n const absoluteRoot = path.isAbsolute(root) ? root : path.resolve(root);\n return path.join(absoluteRoot, ...segments);\n}\n\n/**\n * A temp path resolver with the root directory baked in.\n *\n * Use {@link createTmpResolver} at entry points to capture the temp root once,\n * then pass the resolver to downstream functions. This prevents the class of\n * bugs where callers forget to pass tmpDir.\n *\n * @example\n * ```typescript\n * const tmp = createTmpResolver(buildOptions.tempDir);\n * const cacheDir = tmp('cache', 'packages'); // root is baked in\n * ```\n */\nexport type TmpResolver = (...segments: string[]) => string;\n\nexport function createTmpResolver(tmpDir?: string): TmpResolver {\n const root = tmpDir || DEFAULT_TMP_ROOT;\n const absoluteRoot = path.isAbsolute(root) ? root : path.resolve(root);\n return (...segments: string[]) => path.join(absoluteRoot, ...segments);\n}\n\n/**\n * Get the default temp root directory.\n */\nexport function getDefaultTmpRoot(): string {\n return DEFAULT_TMP_ROOT;\n}\n\nexport interface CLILoggerOptions {\n verbose?: boolean;\n silent?: boolean;\n json?: boolean;\n stderr?: boolean;\n onLine?: (level: Level, message: string) => void;\n /**\n * Exact secret values masked in every line (see `scrubSecrets`). An array is\n * copied at creation; a function is read per line, so values learned after\n * the logger exists (secrets fetched at startup) are masked too.\n */\n knownSecrets?: readonly string[] | (() => readonly string[]);\n}\n\n/** Formats one already-scrubbed line for the terminal. */\nexport type LineFormatter = (line: string) => string;\n\n/**\n * Per-level terminal formatters. Every level defaults to identity, so a caller\n * that passes nothing gets plain uncoloured output.\n */\nexport interface CLILoggerColors {\n error?: LineFormatter;\n warn?: LineFormatter;\n info?: LineFormatter;\n debug?: LineFormatter;\n}\n\nconst identity: LineFormatter = (line) => line;\n\nconst TMP_ROOT_LABEL = '$TMPDIR';\n\n/**\n * Replace each occurrence of the literal temp root, followed by a path\n * separator, with `$TMPDIR`. Only occurrences that start a path count: the\n * character before must not continue a path, so `/mnt/tmp/x` keeps its text\n * when the root is `/tmp`. Literal matching only, never a pattern built from\n * the path.\n */\nfunction labelTmpRoot(line: string, root: string): string {\n if (!root || root === path.sep) return line;\n const needle = root.endsWith(path.sep) ? root : root + path.sep;\n const parts = line.split(needle);\n if (parts.length === 1) return line;\n let result = parts[0];\n for (let i = 1; i < parts.length; i++) {\n const before = result.length > 0 ? result[result.length - 1] : '';\n const startsPath = before === '' || !/[\\w./\\\\~-]/.test(before);\n result += (startsPath ? TMP_ROOT_LABEL + path.sep : needle) + parts[i];\n }\n return result;\n}\n\n/**\n * Build the `Logger.Config` (level + handler + jsonHandler) that backs a\n * terminal logger. Returned separately so the runtime can hand the SAME\n * handler (including the `onLine` ring tap) to the deployed bundle's\n * collector.\n *\n * The collector in a deployed bundle builds its own logger from this config\n * (`config.logger`), so its destination errors flow through the identical\n * `onLine` tap the runtime logger uses, landing in the shared ErrorRing.\n * There is no parallel ring: one handler, one tap.\n *\n * Level: `Level.DEBUG` so the handler sees every line and controls visibility\n * itself (verbose/silent gating). ERROR is therefore always emitted into the\n * ring regardless of `--verbose`.\n *\n * `colors` formats each level for the terminal. It is applied to console\n * output only, never to the line handed to `onLine`.\n */\nexport function createCLILoggerConfig(\n options: CLILoggerOptions = {},\n colors: CLILoggerColors = {},\n): Config {\n const {\n verbose = false,\n silent = false,\n json = false,\n stderr = false,\n knownSecrets,\n } = options;\n const knownCopy =\n typeof knownSecrets === 'function'\n ? undefined\n : knownSecrets\n ? [...knownSecrets]\n : undefined;\n const readKnown = (): readonly string[] | undefined =>\n typeof knownSecrets === 'function' ? knownSecrets() : knownCopy;\n const tmpRoot = os.tmpdir();\n const out = stderr ? console.error : console.log;\n const errorColor = colors.error ?? identity;\n const warnColor = colors.warn ?? identity;\n const infoColor = colors.info ?? identity;\n const debugColor = colors.debug ?? identity;\n\n return {\n // Let handler control visibility: pass everything through. With the gate\n // at DEBUG, ERROR always reaches the handler (and the ring) even without\n // --verbose.\n level: Level.DEBUG,\n handler: (level, message, context, scope) => {\n // Build formatted message\n const scopePath = scope.length > 0 ? `[${scope.join(':')}] ` : '';\n // Serialize the structured context into the line so error details (gRPC\n // status codes, row counts, target tables) reach stderr and the ring.\n // Serialization happens BEFORE scrubSecrets so redaction covers context\n // values too; the heartbeat path's 256-char cap stays as the wire\n // backstop. A context that cannot stringify (circular) must never break\n // logging.\n let meta = '';\n if (Object.keys(context).length > 0) {\n try {\n meta = ` ${JSON.stringify(context)}`;\n } catch {\n meta = ' [unserializable context]';\n }\n }\n // Redact secrets ONCE here, before BOTH the onLine ring tap and the\n // console.* output. stderr is shipped directly by the log collector, so\n // the heartbeat-egress redactor alone would miss it; doing it in the\n // handler scrubs every line routed through this logger (collector and\n // steps) on both paths. Length is preserved here (no truncation); the\n // heartbeat path applies the 256-char wire cap separately as a backstop\n // on already-redacted text.\n // Order: known values first, so the label can never split one (a\n // `sqlite:/tmp/...` URL, or a value that only touches the root at its\n // edge); then the temp root label, since a long per-user root (macOS\n // `/var/folders/...`) would otherwise read as a token; then the\n // pattern rules.\n const fullMessage = scrubSecrets(\n labelTmpRoot(\n maskKnownValues(`${scopePath}${message}${meta}`, readKnown()),\n tmpRoot,\n ),\n );\n\n // Tap every line before any early return so no level is dropped from capture.\n try {\n options.onLine?.(level, fullMessage);\n } catch {\n // Swallow: a throwing consumer must never break logging (and we must\n // not call logger methods here to avoid infinite recursion).\n }\n\n // ERROR: always shown unless json mode\n if (level === Level.ERROR) {\n if (!json) console.error(errorColor(fullMessage));\n return;\n }\n\n // Non-errors suppressed in silent or json mode\n if (silent || json) return;\n\n // DEBUG: only with verbose\n if (level === Level.DEBUG) {\n if (!verbose) return;\n out(` ${debugColor(fullMessage)}`);\n return;\n }\n\n // WARN / INFO: normal output\n out(\n level === Level.WARN ? warnColor(fullMessage) : infoColor(fullMessage),\n );\n },\n jsonHandler: (data) => {\n if (!silent) out(JSON.stringify(data, null, 2));\n },\n };\n}\n\n/**\n * Create a core Logger.Instance with terminal-appropriate behavior.\n *\n * Behavior:\n * - ERROR: always shown (via console.error, `colors.error`) unless --json\n * - WARN: shown unless --silent or --json\n * - INFO: shown unless --silent or --json\n * - DEBUG: shown only with --verbose (and not --silent/--json)\n * - json(): shown unless --silent\n */\nexport function createCLILogger(\n options: CLILoggerOptions = {},\n colors: CLILoggerColors = {},\n): Instance {\n return createLogger(createCLILoggerConfig(options, colors));\n}\n","/**\n * Log levels from most to least severe\n */\nexport enum Level {\n ERROR = 0,\n WARN = 1,\n INFO = 2,\n DEBUG = 3,\n}\n\n/**\n * Normalized error context extracted from Error objects\n */\nexport interface ErrorContext {\n message: string;\n name: string;\n stack?: string;\n cause?: unknown;\n}\n\n/**\n * Context passed to log handlers\n * If an Error was passed, it's normalized into the error property\n */\nexport interface LogContext {\n [key: string]: unknown;\n error?: ErrorContext;\n}\n\n/**\n * Log message function signature\n * Accepts string or Error as message, with optional context\n */\nexport type LogFn = (\n message: string | Error,\n context?: unknown | Error,\n) => void;\n\n/**\n * Throw function signature - logs error then throws\n * Returns never as it always throws\n */\nexport type ThrowFn = (message: string | Error, context?: unknown) => never;\n\n/**\n * Default handler function (passed to custom handlers for chaining)\n */\nexport type DefaultHandler = (\n level: Level,\n message: string,\n context: LogContext,\n scope: string[],\n) => void;\n\n/**\n * Custom log handler function\n * Receives originalHandler to allow middleware-style chaining\n */\nexport type Handler = (\n level: Level,\n message: string,\n context: LogContext,\n scope: string[],\n originalHandler: DefaultHandler,\n) => void;\n\n/**\n * Logger instance with scoping support\n * All logs automatically include trace path from scoping\n */\nexport interface Instance {\n /**\n * Log an error message (always visible unless silenced)\n */\n error: LogFn;\n\n /**\n * Log a warning (degraded state, config issues, transient failures)\n */\n warn: LogFn;\n\n /**\n * Log an informational message\n */\n info: LogFn;\n\n /**\n * Log a debug message\n */\n debug: LogFn;\n\n /**\n * Log an error message and throw an Error\n * Combines logging and throwing in one call\n */\n throw: ThrowFn;\n\n /**\n * Output structured JSON data\n */\n json: (data: unknown) => void;\n\n /**\n * Create a scoped child logger with automatic trace path\n * @param name - Scope name (e.g., destination type, destination key)\n * @returns A new logger instance with the scope applied\n */\n scope: (name: string) => Instance;\n}\n\n/**\n * Logger configuration options\n */\nexport interface Config {\n /**\n * Minimum log level to display\n * @default Level.ERROR\n */\n level?: Level | keyof typeof Level;\n\n /**\n * Custom log handler function\n * Receives originalHandler to preserve default behavior\n */\n handler?: Handler;\n\n /** Custom handler for json() output. Default: console.log(JSON.stringify(data, null, 2)) */\n jsonHandler?: (data: unknown) => void;\n}\n\n/**\n * Internal config with resolved values and scope\n */\nexport interface InternalConfig {\n level: Level;\n handler?: Handler;\n jsonHandler?: (data: unknown) => void;\n scope: string[];\n}\n\n/**\n * Logger factory function type\n */\nexport type Factory = (config?: Config) => Instance;\n","import type {\n Config,\n DefaultHandler,\n ErrorContext,\n Handler,\n Instance,\n InternalConfig,\n LogContext,\n} from './types/logger';\nimport { Level } from './types/logger';\n\n/**\n * Normalize an Error object into ErrorContext\n */\nfunction normalizeError(err: Error): ErrorContext {\n return {\n message: err.message,\n name: err.name,\n stack: err.stack,\n cause: (err as Error & { cause?: unknown }).cause,\n };\n}\n\n/**\n * Normalize message and context parameters\n * Handles Error objects passed as message or context\n */\nfunction normalizeParams(\n message: string | Error,\n context?: unknown | Error,\n): { message: string; context: LogContext } {\n let normalizedMessage: string;\n let normalizedContext: LogContext = {};\n\n // Handle message\n if (message instanceof Error) {\n normalizedMessage = message.message;\n normalizedContext.error = normalizeError(message);\n } else {\n normalizedMessage = message;\n }\n\n // Handle context\n if (context !== undefined) {\n if (context instanceof Error) {\n normalizedContext.error = normalizeError(context);\n } else if (typeof context === 'object' && context !== null) {\n normalizedContext = { ...normalizedContext, ...(context as object) };\n // If context has an error property that's an Error, normalize it\n if (\n 'error' in normalizedContext &&\n normalizedContext.error instanceof Error\n ) {\n normalizedContext.error = normalizeError(\n normalizedContext.error as unknown as Error,\n );\n }\n } else {\n normalizedContext.value = context;\n }\n }\n\n return { message: normalizedMessage, context: normalizedContext };\n}\n\n/**\n * Default console-based log handler\n */\nconst defaultHandler: DefaultHandler = (level, message, context, scope) => {\n const levelName = Level[level];\n const scopePath = scope.length > 0 ? ` [${scope.join(':')}]` : '';\n const prefix = `${levelName}${scopePath}`;\n\n const hasContext = Object.keys(context).length > 0;\n\n const consoleFn =\n level === Level.ERROR\n ? console.error\n : level === Level.WARN\n ? console.warn\n : console.log;\n\n if (hasContext) {\n consoleFn(prefix, message, context);\n } else {\n consoleFn(prefix, message);\n }\n};\n\n/**\n * Normalize log level from string or enum to enum value\n */\nfunction normalizeLevel(level: Level | keyof typeof Level): Level {\n if (typeof level === 'string') {\n return Level[level as keyof typeof Level];\n }\n return level;\n}\n\n/**\n * Create a logger instance\n *\n * @param config - Logger configuration\n * @returns Logger instance with all log methods and scoping support\n *\n * @example\n * ```typescript\n * // Basic usage\n * const logger = createLogger({ level: 'DEBUG' });\n * logger.info('Hello world');\n *\n * // With scoping\n * const destLogger = logger.scope('gtag').scope('myInstance');\n * destLogger.debug('Processing event'); // DEBUG [gtag:myInstance] Processing event\n *\n * // With custom handler\n * const logger = createLogger({\n * handler: (level, message, context, scope, originalHandler) => {\n * // Custom logic (e.g., send to Sentry)\n * originalHandler(level, message, context, scope);\n * }\n * });\n * ```\n *\n * // TODO: Consider compile-time stripping of debug logs in production builds\n * // e.g., if (__DEV__) { logger.debug(...) }\n */\nexport function createLogger(config: Config = {}): Instance {\n const level =\n config.level !== undefined ? normalizeLevel(config.level) : Level.ERROR;\n const customHandler = config.handler;\n const jsonHandler = config.jsonHandler;\n const scope: string[] = [];\n\n return createLoggerInternal({\n level,\n handler: customHandler,\n jsonHandler,\n scope,\n });\n}\n\n/**\n * Internal logger creation with resolved config\n */\nfunction createLoggerInternal(config: InternalConfig): Instance {\n const { level, handler, jsonHandler, scope } = config;\n\n /**\n * Internal log function that checks level and delegates to handler\n */\n const log = (\n logLevel: Level,\n message: string | Error,\n context?: unknown | Error,\n ): void => {\n if (logLevel <= level) {\n const normalized = normalizeParams(message, context);\n\n if (handler) {\n // Custom handler with access to default handler\n handler(\n logLevel,\n normalized.message,\n normalized.context,\n scope,\n defaultHandler,\n );\n } else {\n defaultHandler(logLevel, normalized.message, normalized.context, scope);\n }\n }\n };\n\n /**\n * Log and throw - combines logging and throwing\n */\n const logAndThrow = (message: string | Error, context?: unknown): never => {\n // Always log errors regardless of level\n const normalized = normalizeParams(message, context);\n\n if (handler) {\n handler(\n Level.ERROR,\n normalized.message,\n normalized.context,\n scope,\n defaultHandler,\n );\n } else {\n defaultHandler(\n Level.ERROR,\n normalized.message,\n normalized.context,\n scope,\n );\n }\n\n // Throw with the message\n throw new Error(normalized.message);\n };\n\n return {\n error: (message, context) => log(Level.ERROR, message, context),\n warn: (message, context) => log(Level.WARN, message, context),\n info: (message, context) => log(Level.INFO, message, context),\n debug: (message, context) => log(Level.DEBUG, message, context),\n throw: logAndThrow,\n json: (data: unknown) => {\n if (jsonHandler) {\n jsonHandler(data);\n } else {\n // eslint-disable-next-line no-console\n console.log(JSON.stringify(data, null, 2));\n }\n },\n scope: (name: string) =>\n createLoggerInternal({\n level,\n handler,\n jsonHandler,\n scope: [...scope, name],\n }),\n };\n}\n\n// Re-export Level enum for convenience\nexport { Level };\n","/**\n * Shared secret redactor. One set of patterns for every path that egresses a\n * log line: the CLI logger handler and the runtime logger handler (scrubbing\n * console and the error ring), and the runtime heartbeat (scrubbing the ring\n * snapshot before the POST). A pattern added here covers all of them.\n *\n * Two entry points:\n * - `scrubSecrets(line, { known })`: mask secrets (and the exact known values), keep the message length intact (for\n * console/stderr legibility).\n * - `redactLine(line)`: `scrubSecrets` plus truncation to the 256-char\n * heartbeat wire contract.\n *\n * All regex are linear (single unambiguous quantifiers, no nested quantifiers),\n * so there is no catastrophic backtracking even on a BEGIN-marker-with-no-END\n * input or a long no-space line. Exported from `@walkeros/core/node` only: no\n * browser consumer needs it, and it must not ride into web bundles.\n */\n\nconst MAX_LENGTH = 256;\n\n// Minimum length for a standalone run to be considered a candidate token.\nconst MIN_TOKEN_LEN = 20;\n\n// Shannon entropy threshold (bits/char). Random tokens score high (~4.5-6),\n// natural-language words and identifiers score low (~2-3.5).\nconst ENTROPY_THRESHOLD = 4.0;\n\n// Known secret prefixes that force-mask their run regardless of entropy/shape.\nconst FORCE_MASK_PREFIXES = [\n 'sk-',\n 'sk_',\n 'pk_',\n 'ghp_',\n 'gho_',\n 'xoxb-',\n 'xoxp-',\n 'AKIA',\n];\n\n// ── Regex constants (all linear, no nested quantifiers) ─────────────────────\n\n// URL credentials: scheme://user:password@host, keep user, mask password.\n// The user may be empty (`redis://:pw@host`). Neither part crosses a quote,\n// backslash or `/`, so a URL in serialized JSON never reaches an `@` further\n// along the line (an email in another field).\nconst RE_URL_CREDS = /(:\\/\\/[^/:@\\s\"\\\\]*:)[^@\\s/\"\\\\]+(@)/g;\n\n// JSON service-account field values, catches service-account blobs.\n// `private_key` holds the PEM body; `client_email`, `private_key_id`,\n// `client_id`, and `client_x509_cert_url` identify and pair with the leaked\n// credential and must not ship either (the cert url embeds the URL-encoded\n// client email). Naming them explicitly beats relying on the token-run\n// heuristics, which can miss an email or a URL. The value may contain escaped\n// newlines (\\n as literal backslash-n) which do not split the line. [^\"] is\n// safe: these values contain no embedded quotes. One alternation over the field\n// names keeps the scan single-pass and linear. The escaped `\\\"private_key\\\"`\n// form (a service account inside a serialized JSON string) matches too; its\n// optional backslash is captured and repeated around the mask.\nconst RE_JSON_SA_FIELD =\n /(\\\\?\"(?:private_key|client_email|private_key_id|client_id|client_x509_cert_url)\\\\?\"\\s*:\\s*)(\\\\?)\"[^\"]*\"/g;\n\n// Credential fields by name: the value is a secret whatever its shape (a\n// plain-word password or a `Basic`/`Bearer` header slips past the token\n// heuristics). Word parts may be joined by `-`, `_` or nothing (`api_key`,\n// `apiKey`, `API-Key`), and a header name may carry an `x-` prefix\n// (`X-API-Key`).\nconst SECRET_FIELD_NAMES =\n '(?:x-)?(?:password|passwd|secret|client[-_]?secret|secret[-_]?access[-_]?key|api[-_]?key|api[-_]?secret|access[-_]?token|refresh[-_]?token|auth[-_]?token|id[-_]?token|session[-_]?token|private[-_]?key|token|authorization)';\n\n// Plain `\"key\": value`: a string value runs to its closing quote (an escaped\n// char is consumed as a pair; the alternatives start on different chars, so\n// the scan is linear), a numeric value to its last digit. The mask is a\n// string, so the JSON stays valid.\nconst RE_JSON_SECRET_FIELD = new RegExp(\n `(\"(?:${SECRET_FIELD_NAMES})\"\\\\s*:\\\\s*)(?:\"(?:[^\"\\\\\\\\]|\\\\\\\\.)*\"|-?\\\\d[\\\\d.eE+-]*)`,\n 'gi',\n);\n\n// Escaped `\\\"key\\\":\\\"value\\\"`, the form a serialized request body carries\n// inside a JSON string. A string value ends at the escaped closing quote; a\n// quote or backslash inside it is escaped twice (`\\\\\\\"`, `\\\\\\\\`), so each\n// `\\\\` pair is consumed together with the escape it carries. Every\n// alternative starts on a different char pair, so the scan is linear. A\n// numeric value runs to its last digit.\nconst RE_JSON_SECRET_FIELD_ESCAPED = new RegExp(\n `(\\\\\\\\\"(?:${SECRET_FIELD_NAMES})\\\\\\\\\"\\\\s*:\\\\s*)(?:(\\\\\\\\\")(?:[^\"\\\\\\\\]|\\\\\\\\[^\"\\\\\\\\]|\\\\\\\\\\\\\\\\(?:\\\\\\\\\\\\\\\\|\\\\\\\\\"|[^\"\\\\\\\\]))*|-?\\\\d[\\\\d.eE+-]*)`,\n 'gi',\n);\n\n// A credential-named URL query parameter (`?api_secret=...`,\n// `&access_token=...`): the value runs to the next `&`, `#`, quote, backslash\n// or whitespace.\nconst RE_QUERY_SECRET = new RegExp(\n `([?&]${SECRET_FIELD_NAMES}=)[^&#\\\\s\"\\\\\\\\]+`,\n 'gi',\n);\n\n// A PEM private key inline in one line (a JSON string with escaped newlines),\n// under any field name: marker and body up to the END marker's dashes.\nconst RE_PEM_INLINE = /-----BEGIN [A-Z ]*PRIVATE KEY-----[^-]*/gi;\n\n// PEM block boundaries (structural removal, not regex masking).\n// Case-insensitive, tolerate leading whitespace so a lowercase or indented\n// `-----begin private key-----` block does not leak its body.\nconst RE_PEM_BEGIN = /^\\s*-----BEGIN [A-Z ]*PRIVATE KEY-----/i;\nconst RE_PEM_END = /-----END[A-Z -]*-----/i;\n\n// KEY=value / KEY: value secret pattern.\n// ReDoS-safe: the key is anchored to start-of-line or whitespace and bounded to\n// 64 chars ({0,63} after the first char), so there is no O(n²) backtracking on\n// long no-space lines. The value class includes - and . so hyphenated/dotted\n// tokens are captured whole. Two capture groups are preserved in the\n// replacement: the leading (^|\\s) boundary and the key=.\nconst TOKEN_VALUE_CLASS = '[A-Za-z0-9+/._-]';\nconst RE_KV_SECRET = new RegExp(\n `(^|\\\\s)([A-Za-z_][A-Za-z0-9_.]{0,63}\\\\s*[=:]\\\\s*)(${TOKEN_VALUE_CLASS}{12,}={0,2})`,\n 'g',\n);\n\n// Standalone candidate runs: any run of token chars (incl. - and .) ≥ 20.\n// The decision to mask happens in the callback (shouldMaskToken), keeping the\n// regex itself trivial and linear.\nconst RE_TOKEN_RUN = new RegExp(`${TOKEN_VALUE_CLASS}{${MIN_TOKEN_LEN},}`, 'g');\n\n// Force-mask prefix detector for runs that carry a known secret prefix.\n// Matches a known prefix followed by token chars (any length), so e.g.\n// `ghp_xxxxxxxx` or `xoxb-2384-2384-AbCdEf` is masked even under 20 chars.\n// Built from FORCE_MASK_PREFIXES so the list is the single source of truth.\n// Prefixes are regex-escaped (only `-`/`_` appear, both literal here, but escape\n// defensively) and ordered longest-first so the alternation is unambiguous.\nfunction escapeRegex(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\nconst PREFIX_ALTERNATION = [...FORCE_MASK_PREFIXES]\n .sort((a, b) => b.length - a.length)\n .map(escapeRegex)\n .join('|');\nconst RE_PREFIXED_TOKEN = new RegExp(\n `(?:${PREFIX_ALTERNATION})${TOKEN_VALUE_CLASS}*`,\n 'g',\n);\n\n// A run right after a backslash starts with that escape's letters (`\\n`, `\\t`,\n// `\\u00e9` in serialized JSON). Masking keeps them so the text stays valid JSON.\nconst RE_ESCAPE_HEAD = /^(?:u[0-9a-fA-F]{4}|[bfnrt])/;\n\n// ── Entropy ──────────────────────────────────────────────────────────────────\n\n/** Shannon entropy in bits per character. Linear in input length. */\nfunction shannonEntropy(s: string): number {\n if (s.length === 0) return 0;\n const counts = new Map<string, number>();\n for (const ch of s) counts.set(ch, (counts.get(ch) ?? 0) + 1);\n let entropy = 0;\n for (const count of counts.values()) {\n const p = count / s.length;\n entropy -= p * Math.log2(p);\n }\n return entropy;\n}\n\n// ── Token masking decision ─────────────────────────────────────────────────\n\nconst RE_ALL_HEX = /^[0-9a-fA-F]+$/;\nconst RE_ALL_DIGIT = /^[0-9]+$/;\n// Real base64 padding/special chars. `/` is intentionally EXCLUDED: it is a\n// structural path/URL separator (FQNs, gs:// paths, request paths, googleapis\n// URLs), not a secret signal. Since redaction runs on every logger line,\n// treating `/` as a secret char nuked legitimate BigQuery/GCS diagnostics. Real\n// base64 secrets still carry `+`/`=` padding, are all-hex, or are high-entropy.\nconst RE_BASE64_SPECIAL = /[+=]/;\nconst RE_HAS_DIGIT = /[0-9]/;\nconst RE_HAS_LETTER = /[A-Za-z]/;\n// A dotted run is judged per segment; segments shorter than this (`oauth2`,\n// `com`) are never judged, longer ones only mask when secret-shaped (name\n// parts like `JSONWriter` or `googleapis` are not).\nconst MIN_DOT_SEGMENT_LEN = 8;\n\n/**\n * Decide whether a candidate run (a single token, no `/`) is a secret to mask.\n *\n * Mask when ANY of:\n * - all-hex (digests, SHA, etc.)\n * - all-digit (numeric tokens/ids)\n * - contains a base64 special char (+ =)\n * - contains both a digit and a letter (mixed token)\n * - Shannon entropy ≥ 4.0 bits/char (random all-letter / mixed-case tokens)\n */\nfunction isSecretSegment(run: string): boolean {\n if (RE_ALL_HEX.test(run)) return true;\n if (RE_ALL_DIGIT.test(run)) return true;\n if (RE_BASE64_SPECIAL.test(run)) return true;\n if (RE_HAS_DIGIT.test(run) && RE_HAS_LETTER.test(run)) return true;\n if (shannonEntropy(run) >= ENTROPY_THRESHOLD) return true;\n return false;\n}\n\n/**\n * Decide whether a ≥20-char candidate run is a secret to mask.\n *\n * A run containing `/` is treated as a path/URL structure, not one opaque token.\n * Paths and URLs split into short alphanumeric segments (`v2`, `projects`,\n * `12345`), each of which trips the digit+letter or entropy heuristic on its own\n * even though the whole thing is legible diagnostics. So a `/`-bearing run is\n * masked only when one of its long (≥20-char) segments independently looks like\n * a secret (e.g. a base64 blob embedded in a URL path). A base64 secret with `/`\n * still carries `+`/`=` (handled per-segment) or is hex/high-entropy, so real\n * secrets remain masked while FQNs, gs:// paths, request paths, and googleapis\n * URLs survive.\n *\n * A `.` splits a run (or a path segment) too, with a shorter bar: host names\n * (`oauth2.googleapis.com`) and dotted call paths (`JSONWriter.appendRows`)\n * stay legible, while a dotted run with any secret-shaped segment of 8+\n * chars (each part of a JWT, `abc123def456gh.ghi789...`) is masked. A\n * KEY=value value without `/` (`keyedValue`) is judged whole, dots included:\n * `DB_PASSWORD=Sup3rS3cret.Pass2024` masks although both parts are short.\n *\n * ACCEPTED best-effort limitation: a bare all-letters run with no digit, no\n * special char, no known prefix, and entropy < 4.0 (e.g. `supercalifragilistic`\n * or a long camelCase identifier like `getUserAccountSettings`) may pass through\n * unmasked. Masking every such run would destroy log readability by redacting\n * ordinary identifiers and words, which defeats the purpose of legible\n * diagnostics. This is a deliberate trade-off, not an oversight.\n */\n// A semantic version (optionally v-prefixed, with prerelease and build\n// metadata) is never a secret. Release snapshots such as\n// `4.7.0-next-1790408404259` are long enough to reach the token-run length\n// and would otherwise be masked in image tags and package specifiers.\nconst RE_SEMVER =\n /^v?\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?$/;\n\nfunction shouldMaskToken(run: string, keyedValue = false): boolean {\n if (RE_SEMVER.test(run)) return false;\n if (run.includes('/')) {\n // Per-segment: only mask if a long segment is itself secret-shaped. Short\n // path segments (the bulk of any URL) never qualify, keeping URLs legible.\n return run\n .split('/')\n .filter((segment) => segment.length >= MIN_TOKEN_LEN)\n .some(isSecretDottedRun);\n }\n return keyedValue ? isSecretSegment(run) : isSecretDottedRun(run);\n}\n\n/** A run without `/`: secret when any 8+ char dot segment is secret-shaped. */\nfunction isSecretDottedRun(run: string): boolean {\n if (!run.includes('.')) return isSecretSegment(run);\n return run\n .split('.')\n .filter((segment) => segment.length >= MIN_DOT_SEGMENT_LEN)\n .some(isSecretSegment);\n}\n\n// ── PEM block removal ────────────────────────────────────────────────────────\n\n/**\n * Remove all PEM private-key blocks from an array of lines.\n * A block starts at a BEGIN PRIVATE KEY marker and ends at the matching END\n * marker (inclusive). If no END is found, everything to end of entry is dropped.\n */\nfunction removePemBlocks(lines: string[]): string[] {\n const out: string[] = [];\n let inBlock = false;\n\n for (const line of lines) {\n if (!inBlock) {\n if (RE_PEM_BEGIN.test(line)) {\n inBlock = true;\n continue; // start-of-block line dropped\n }\n out.push(line);\n } else {\n if (RE_PEM_END.test(line)) {\n inBlock = false;\n }\n // drop every in-block line, including the END marker\n }\n }\n\n return out;\n}\n\n// ── Per-line secret masking ──────────────────────────────────────────────────\n\n/**\n * Mask secrets in a single (non-PEM) line.\n * All patterns are applied in order; all regex are linear (no nested quantifiers).\n */\nfunction maskLine(line: string): string {\n let s = line;\n\n // 1. URL credentials: ://user:secret@host → ://user:***@host\n s = s.replace(RE_URL_CREDS, '$1***$2');\n\n // 1a. Credential-named query parameters, whatever the value's shape.\n s = s.replace(RE_QUERY_SECRET, '$1***');\n\n // 1b. A PEM private key inline (a JSON string), whatever its field name.\n s = s.replace(RE_PEM_INLINE, '***');\n\n // 2. KEY=secret / KEY: secret (anchored, bounded key, ReDoS-safe).\n // Mask the value only when it is secret-shaped. This stops `scheme://host/...`\n // (matched as `gs:` / `https:` key + `//...` value) from masking legitimate\n // URLs/paths, while real `TOKEN=secret` values still mask. The `/`-aware\n // shouldMaskToken keeps path-shaped values legible.\n s = s.replace(\n RE_KV_SECRET,\n (match: string, boundary: string, keyPart: string, value: string) =>\n shouldMaskToken(value, true) ? `${boundary}${keyPart}***` : match,\n );\n\n // 3. Known-prefix tokens (force-mask regardless of length/entropy)\n s = s.replace(RE_PREFIXED_TOKEN, '***');\n\n // 4. Standalone candidate runs ≥ 20 chars, mask by shape/entropy\n s = s.replace(\n RE_TOKEN_RUN,\n (match: string, offset: number, input: string) => {\n if (!shouldMaskToken(match)) return match;\n const head =\n offset > 0 && input[offset - 1] === '\\\\'\n ? RE_ESCAPE_HEAD.exec(match)?.[0]\n : undefined;\n return head ? `${head}***` : '***';\n },\n );\n\n return s;\n}\n\n// ── Known secret values ──────────────────────────────────────────────────────\n\n// Shorter values are too likely to occur in ordinary text to mask on sight.\nexport const MIN_KNOWN_LEN = 6;\n\nexport interface ScrubOptions {\n /**\n * Exact secret values to mask wherever they occur (e.g. the resolved\n * `$secret.NAME` values of a flow). Values under 6 characters are ignored.\n */\n known?: readonly string[];\n}\n\n/**\n * Mask every exact occurrence of each known value, raw, JSON-escaped (the\n * form it takes inside serialized JSON), escaped twice (a JSON string\n * serialized again, e.g. a request body inside JSON output) and URL-encoded\n * (a URL, or a form body where a space is `+`), longest first so\n * a value containing another is masked whole. Literal split/join, never a regex built from the\n * value.\n */\nexport function maskKnownValues(\n line: string,\n known?: readonly string[],\n): string {\n if (!known || known.length === 0) return line;\n const forms = new Set<string>();\n for (const value of known) {\n if (value.length < MIN_KNOWN_LEN) continue;\n const escaped = JSON.stringify(value).slice(1, -1);\n forms.add(value);\n forms.add(escaped);\n forms.add(JSON.stringify(escaped).slice(1, -1));\n // In a URL or a form body: `encodeURIComponent` (also with a space as\n // `+`), `encodeURI` (keeps `/ : + =`) and `URLSearchParams` (also\n // encodes `! ' ( ) ~`, a space as `+`).\n const component = uriEncoded(encodeURIComponent, value);\n if (component) {\n forms.add(component);\n forms.add(component.replace(/%20/g, '+'));\n }\n const uri = uriEncoded(encodeURI, value);\n if (uri) forms.add(uri);\n forms.add(new URLSearchParams({ v: value }).toString().slice(2));\n }\n let s = line;\n for (const form of [...forms].sort((a, b) => b.length - a.length)) {\n if (s.includes(form)) s = replaceKnown(s, form);\n }\n return s;\n}\n\n/** `encode(value)`, or undefined for a value it cannot encode (a lone surrogate). */\nfunction uriEncoded(\n encode: (value: string) => string,\n value: string,\n): string | undefined {\n try {\n return encode(value);\n } catch {\n return undefined;\n }\n}\n\n/**\n * Replace every occurrence of `form` with `***`. An occurrence right after an\n * escaping backslash (an odd run of them) keeps the escape's letters, as\n * `maskLine` does, so serialized JSON stays valid.\n */\nfunction replaceKnown(line: string, form: string): string {\n let out = '';\n let from = 0;\n let at = line.indexOf(form);\n while (at !== -1) {\n // Count the backslashes just before the match; text before `from` was\n // an earlier match, now `***`, so the run stops there.\n let slashes = 0;\n while (at - slashes > from && line[at - slashes - 1] === '\\\\') slashes++;\n const head =\n slashes % 2 === 1 ? (RE_ESCAPE_HEAD.exec(form)?.[0] ?? form[0]) : '';\n out += `${line.slice(from, at)}${head}***`;\n from = at + form.length;\n at = line.indexOf(form, from);\n }\n return out + line.slice(from);\n}\n\n// ── Public API ───────────────────────────────────────────────────────────────\n\n/**\n * Scrub credential material from a log line (or multi-line entry) WITHOUT\n * truncating. This is the shared secret redactor used by the CLI and runtime\n * logger handlers (covers their console/stderr lines and the ring before\n * egress). Output a dependency writes straight to `console` or stderr never\n * passes through it.\n *\n * Algorithm:\n * 0. Mask each `options.known` value exactly, raw, JSON-escaped and escaped\n * twice, longest first.\n * 1. Mask JSON service-account fields (private_key, client_email,\n * private_key_id, client_id, client_x509_cert_url) and credential-named\n * JSON fields (password, token, authorization, ...) BEFORE splitting\n * (service-account blobs embed PEM blocks as \\\\n-encoded strings; masking\n * first prevents the BEGIN marker from appearing on its own line and dropping\n * surrounding fields).\n * 2. Split on \\\\n.\n * 3. Remove PEM private-key blocks structurally (BEGIN…END inclusive,\n * case-insensitive; a no-END block drops to end of entry).\n * 4. For each surviving line: mask URL creds, inline PEM keys, KEY=secret,\n * prefixed tokens,\n * high-entropy/shape-based token runs.\n * 5. Rejoin with \\\\n.\n */\nexport function scrubSecrets(line: string, options: ScrubOptions = {}): string {\n // Step 0: known secret values, exact, before any pattern can split them\n const withoutKnown = maskKnownValues(line, options.known);\n\n // Step 1: JSON service-account field masking before any line splitting\n const withoutJsonKey = withoutKnown\n .replace(RE_JSON_SA_FIELD, '$1$2\"***$2\"')\n .replace(\n RE_JSON_SECRET_FIELD_ESCAPED,\n (_match: string, keyPart: string, quote: string | undefined) =>\n quote ? `${keyPart}${quote}***` : `${keyPart}\\\\\"***\\\\\"`,\n )\n .replace(RE_JSON_SECRET_FIELD, '$1\"***\"');\n\n // Step 2: split\n const rawLines = withoutJsonKey.split('\\n');\n\n // Step 3: remove PEM blocks (any remaining standalone markers after step 1)\n const cleanLines = removePemBlocks(rawLines);\n\n // Step 4: mask each surviving line\n const maskedLines = cleanLines.map(maskLine);\n\n // Step 5: rejoin\n return maskedLines.join('\\n');\n}\n\n/**\n * Redact a log line and truncate to the heartbeat wire contract (256 chars).\n * This is the heartbeat-egress variant: scrub secrets, then truncate AFTER\n * masking so a token straddling char 256 is masked, not left partially visible.\n *\n * The app's heartbeat schema enforces message .max(256); a 257-char string\n * would fail zod .parse and drop the whole heartbeat. Reserve one char for the\n * ellipsis so the TOTAL length is at most MAX_LENGTH (256).\n */\nexport function redactLine(line: string): string {\n const scrubbed = scrubSecrets(line);\n\n if (scrubbed.length > MAX_LENGTH) {\n return scrubbed.slice(0, MAX_LENGTH - 1) + '…';\n }\n\n return scrubbed;\n}\n","import { TextDecoder } from 'util';\n\n/**\n * Turn a recorded simulate value (call arguments, events, errors) into plain\n * JSON-safe data. One formatter for the CLI `push` output (`--json` and text)\n * and the MCP `flow_simulate` result, so a value a vendor call really\n * receives prints readably and never throws:\n *\n * - `Error` → `{ name, message }`\n * - `Buffer`, any typed array, `DataView`, `ArrayBuffer` → utf8 string of its\n * bytes, or `base64:<...>` when not valid utf8\n * - `bigint` → string\n * - `Map` → array of `[key, value]` entries, `Set` → array of values\n * - a cycle → `\"[Circular]\"`\n * - a function → `\"[Function <name>]\"`\n * - a class instance (an SDK command, say) → `{ <ClassName>: <own fields> }`,\n * named by its `Symbol.toStringTag` when it has one\n * - a value whose getter, `toJSON` or proxy trap throws → `\"[Unprintable]\"`\n *\n * Secrets are not this function's concern: the printed text egresses through\n * `scrubSecrets`.\n */\nexport function toPrintable(value: unknown): unknown {\n return walk(value, new Set());\n}\n\nconst utf8 = new TextDecoder('utf-8', { fatal: true });\n\nfunction bytesToString(bytes: Uint8Array): string {\n try {\n return utf8.decode(bytes);\n } catch {\n return `base64:${Buffer.from(bytes).toString('base64')}`;\n }\n}\n\n// Values can come from another realm (a web flow simulates inside jsdom), where\n// `instanceof` against this realm's constructors fails. The built-in tag does\n// not depend on the realm.\nfunction tagOf(value: object): string {\n return Object.prototype.toString.call(value);\n}\n\nfunction isError(value: object): value is Error {\n return tagOf(value) === '[object Error]';\n}\n\nfunction isArrayBuffer(value: object): value is ArrayBuffer {\n return tagOf(value) === '[object ArrayBuffer]';\n}\n\n/** The raw bytes of a typed array, `DataView` or `ArrayBuffer`. */\nfunction bytesOf(value: object): Uint8Array | undefined {\n if (ArrayBuffer.isView(value))\n return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);\n if (isArrayBuffer(value)) return new Uint8Array(value);\n return undefined;\n}\n\nfunction isMap(value: object): value is Map<unknown, unknown> {\n return tagOf(value) === '[object Map]';\n}\n\nfunction isSet(value: object): value is Set<unknown> {\n return tagOf(value) === '[object Set]';\n}\n\nfunction hasToJSON(value: object): value is { toJSON: () => unknown } {\n return 'toJSON' in value && typeof value.toJSON === 'function';\n}\n\n/**\n * Name of a class instance; undefined for plain objects. A custom\n * `Symbol.toStringTag` wins over the constructor name, which a minifier may\n * have renamed.\n */\nfunction className(value: object): string | undefined {\n const tag = tagOf(value).slice('[object '.length, -1);\n if (tag !== 'Object') return tag;\n const proto: unknown = Object.getPrototypeOf(value);\n if (proto === null || proto === Object.prototype) return undefined;\n if (typeof proto !== 'object') return undefined;\n const ctor: unknown = Reflect.get(proto, 'constructor');\n // By name, not identity: a plain object from another realm has that\n // realm's `Object` as its constructor.\n if (typeof ctor !== 'function' || ctor.name === 'Object') return undefined;\n return ctor.name || undefined;\n}\n\nfunction walk(value: unknown, ancestors: Set<object>): unknown {\n if (typeof value === 'bigint') return value.toString();\n if (typeof value === 'function')\n return `[Function ${value.name || 'anonymous'}]`;\n if (typeof value === 'symbol') return value.toString();\n if (value === null || typeof value !== 'object') return value;\n\n // A throwing getter, `toJSON` or proxy trap must not break the output.\n try {\n return walkObject(value, ancestors);\n } catch {\n return '[Unprintable]';\n }\n}\n\nfunction walkObject(value: object, ancestors: Set<object>): unknown {\n if (isError(value)) return { name: value.name, message: value.message };\n const bytes = bytesOf(value);\n if (bytes) return bytesToString(bytes);\n if (ancestors.has(value)) return '[Circular]';\n\n ancestors.add(value);\n try {\n if (isMap(value))\n return Array.from(value.entries(), ([k, v]) => [\n walk(k, ancestors),\n walk(v, ancestors),\n ]);\n if (isSet(value))\n return Array.from(value.values(), (v) => walk(v, ancestors));\n if (Array.isArray(value)) return value.map((v) => walk(v, ancestors));\n if (hasToJSON(value)) return walk(value.toJSON(), ancestors);\n\n const fields: Record<string, unknown> = {};\n for (const [key, field] of Object.entries(value)) {\n fields[key] = walk(field, ancestors);\n }\n const name = className(value);\n return name ? { [name]: fields } : fields;\n } finally {\n ancestors.delete(value);\n }\n}\n","import { MIN_KNOWN_LEN, scrubSecrets, type ScrubOptions } from './redactLine';\nimport { toPrintable } from './toPrintable';\n\n/**\n * A deep copy of `value` in which every number whose printed form contains a\n * known secret (6+ characters, e.g. `12345678`, `-1234567`, `1234567.5`)\n * becomes `'***'`. Masking such a number inside serialized JSON would leave an\n * unquoted `***` and break the JSON, so it runs on the value before\n * `JSON.stringify`. Exported for its unit test; egress goes through\n * `scrubJson`, which owns the order.\n */\nexport function maskKnownNumbers(\n value: unknown,\n known: readonly string[],\n): unknown {\n const candidates = known.filter((secret) => secret.length >= MIN_KNOWN_LEN);\n const mask = (item: unknown): unknown => {\n if (typeof item === 'number') {\n const printed = String(item);\n return candidates.some((secret) => printed.includes(secret))\n ? '***'\n : item;\n }\n if (Array.isArray(item)) return item.map(mask);\n if (item && typeof item === 'object') {\n const copy: Record<string, unknown> = {};\n for (const [key, entry] of Object.entries(item)) copy[key] = mask(entry);\n return copy;\n }\n return item;\n };\n return mask(value);\n}\n\n/**\n * Serialize a value for egress with secrets masked: its printable form (see\n * `toPrintable`), numbers that print a known value become `'***'`,\n * `JSON.stringify`, then `scrubSecrets` with the same known values. The order\n * keeps the result valid JSON. A value with no JSON form prints `undefined`.\n */\nexport function scrubJson(\n value: unknown,\n options: ScrubOptions & { space?: number } = {},\n): string {\n const known = options.known ?? [];\n const text =\n JSON.stringify(\n maskKnownNumbers(toPrintable(value), known),\n null,\n options.space,\n ) ?? 'undefined';\n return scrubSecrets(text, { known });\n}\n"],"mappings":"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","names":["Level"]}
|
package/package.json
CHANGED