{"version":3,"file":"redact.cjs","sources":["../../../../src/core/http/redact.ts"],"sourcesContent":["/**\n * Bounded-depth redact for params that may contain credentials before they\n * enter any logger or error-rendering surface.\n *\n * Callers: `Http._sanitizeParams` (logger context), `_makeAxiosRequest`\n * (`post/send` and `post/catchError` info logs), `AjaxError` constructor\n * (stores `requestInfo.params` exposed by `toJSON()` / `toString()`).\n * Keeping a single source of truth means the redaction list stays\n * consistent across all of them.\n *\n * Two complementary passes run over each value:\n *   1. Key match — a property whose (lower-cased) name is in\n *      {@link SENSITIVE_PARAM_KEYS} has its whole value replaced, so a nested\n *      credential object (e.g. `auth: { application_token }`) is masked\n *      wholesale (#151).\n *   2. Query-string scrub — a *string* value is scanned for\n *      `<sensitive-key>=<value>` pairs and the value is masked. This catches the\n *      batch `cmd[i]` shape (`method?auth=<token>&...`) where `_prepareParams`\n *      has already serialised the credential into text the key walk can't see\n *      (#229).\n *\n * The object walk descends two levels into nested objects and arrays — the\n * minimum that covers batch payloads (`{ cmd: [{ method, params:\n * { ...credentials... } }, ...] }`) and flat one-level-nested payloads like\n * `{ data: { token } }`.\n *\n * Residual risk (documented, accepted):\n *   - credential keys nested deeper than two object levels are NOT masked —\n *     redact at the callsite for those;\n *   - the query-string scrub only masks a `key=value` pair whose key is itself\n *     a sensitive key; a bracketed/encoded query key (`auth[application_token]=`)\n *     is not matched by the string pass (its `auth` prefix object form is,\n *     though, via pass 1).\n *   - `key` is deliberately broad: any property literally named `key` (and any\n *     `?key=…` query pair) is masked. In Bitrix24 REST `key` is a credential\n *     parameter (e.g. the Pull shared config), so this is a conservative,\n *     accepted trade-off — it can over-redact a non-credential field that\n *     happens to be named `key`.\n *   - `signature` is broad in the same way (added in #43 for the Pull channel\n *     HMAC, `TypeChanel.signature`): any property named `signature` and any\n *     `?signature=…` query pair is masked. In the Bitrix24 push/pull domain\n *     `signature` is the channel HMAC, so the breadth is accepted — at the cost\n *     of over-redacting a non-credential field that happens to be named so.\n *   - empty / nullish values are still treated as sensitive — an empty\n *     `access_token` is unusual but not safe to leave un-redacted.\n */\n\nexport const SENSITIVE_PARAM_KEYS: readonly string[] = [\n  'auth',\n  'password',\n  'token',\n  'secret',\n  'access_token',\n  'refresh_token',\n  'client_secret',\n  'application_token',\n  'sessid',\n  'key',\n  'signature'\n]\n\nexport const REDACTED_PLACEHOLDER = '***REDACTED***'\n\n// Matches `<sep><sensitive-key>=<value>` inside a string, case-insensitively,\n// and masks the value. The `([?&]|^)` prefix anchors to a real query-param\n// boundary so a credential name appearing inside a value (`foo=token=x`) or as\n// the tail of a longer key (`access_token` vs `token`) is not mis-matched. The\n// value runs to the next `&`, `#`, or `;`, so a `;`-separated adjacent param is\n// not swallowed into the redacted span. An embedded `?token=…` inside a nested\n// URL value IS masked (intended — still a credential). Single-line: `^` carries\n// no `m` flag, so a credential after a newline in a multi-line string value is\n// not caught (accepted residual risk, same class as encoded/bracketed keys).\nconst QS_SENSITIVE_RE = new RegExp(\n  `([?&]|^)(${SENSITIVE_PARAM_KEYS.join('|')})=[^&#;]*`,\n  'gi'\n)\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n  return value !== null && typeof value === 'object' && !Array.isArray(value)\n}\n\nfunction redactQueryString(value: string): string {\n  if (!value.includes('=')) return value\n  return value.replace(\n    QS_SENSITIVE_RE,\n    (_match, sep: string, key: string) => `${sep}${key}=${REDACTED_PLACEHOLDER}`\n  )\n}\n\n// String scrubbing runs before the `depth <= 0` guard on purpose: scanning a\n// string is cheap and bounded, so a serialised credential is masked even at a\n// level the object walk would stop descending into. Arrays do not consume a\n// depth slot (only object descent decrements `depth`), so an array nested in an\n// array is still walked.\nfunction redactValue(value: unknown, depth: number): unknown {\n  if (typeof value === 'string') return redactQueryString(value)\n  if (depth <= 0) return value\n  if (isPlainObject(value)) return redactObject(value, depth - 1)\n  if (Array.isArray(value)) return value.map(item => redactValue(item, depth))\n  return value\n}\n\nfunction redactObject(\n  source: Record<string, unknown>,\n  depth: number\n): Record<string, unknown> {\n  const sanitized: Record<string, unknown> = { ...source }\n  for (const key of Object.keys(sanitized)) {\n    if (SENSITIVE_PARAM_KEYS.includes(key.toLowerCase())) {\n      sanitized[key] = REDACTED_PLACEHOLDER\n      continue\n    }\n    sanitized[key] = redactValue(sanitized[key], depth)\n  }\n  return sanitized\n}\n\nconst DEFAULT_REDACT_DEPTH = 2\n\n/**\n * Returns a copy of `params` with any known credential-bearing key replaced by\n * `REDACTED_PLACEHOLDER`, and any credential value embedded in a query-string\n * value masked in place. Walks up to two levels into nested objects/arrays so\n * batch-shaped payloads (`cmd[i].params.<key>` and `cmd[i]` query strings) are\n * covered. Non-object inputs are returned as-is so callers don't have to\n * pre-check.\n */\nexport function redactSensitiveParams(\n  params: Record<string, unknown>\n): Record<string, unknown>\nexport function redactSensitiveParams<T>(params: T): T\nexport function redactSensitiveParams(params: unknown): unknown {\n  if (!isPlainObject(params)) return params\n  return redactObject(params, DEFAULT_REDACT_DEPTH)\n}\n\n/**\n * Redact credential query-string values in a URL string — e.g. a Pull\n * `connectionPath` surfaced by `getDebugInfo()`. Masks every\n * {@link SENSITIVE_PARAM_KEYS} value plus any caller-supplied `extraKeys`\n * (e.g. Pull's `CHANNEL_ID`, a private identifier that is not a global\n * credential key). `extraKeys` are regex-escaped, so any literal key name is\n * safe to pass. Anchored and bounded exactly like the in-object scrub, so a\n * value-position `=` and non-query strings are left intact. Non-string input is\n * returned unchanged (a defensive guard for untyped JS callers).\n */\nexport function redactSensitiveUrl(url: string, extraKeys: readonly string[] = []): string {\n  if (typeof url !== 'string' || !url.includes('=')) return url\n  if (extraKeys.length === 0) return redactQueryString(url)\n  const escaped = extraKeys.map(key => key.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'))\n  const re = new RegExp(\n    `([?&]|^)(${[...SENSITIVE_PARAM_KEYS, ...escaped].join('|')})=[^&#;]*`,\n    'gi'\n  )\n  return url.replace(re, (_match, sep: string, key: string) => `${sep}${key}=${REDACTED_PLACEHOLDER}`)\n}\n"],"names":[],"mappings":";;;;;;;;;;;;AA+CO,MAAM,oBAAA,GAA0C;AAAA,EACrD,MAAA;AAAA,EACA,UAAA;AAAA,EACA,OAAA;AAAA,EACA,QAAA;AAAA,EACA,cAAA;AAAA,EACA,eAAA;AAAA,EACA,eAAA;AAAA,EACA,mBAAA;AAAA,EACA,QAAA;AAAA,EACA,KAAA;AAAA,EACA;AACF;AAEO,MAAM,oBAAA,GAAuB;AAWpC,MAAM,kBAAkB,IAAI,MAAA;AAAA,EAC1B,CAAA,SAAA,EAAY,oBAAA,CAAqB,IAAA,CAAK,GAAG,CAAC,CAAA,SAAA,CAAA;AAAA,EAC1C;AACF,CAAA;AAEA,SAAS,cAAc,KAAA,EAAkD;AACvE,EAAA,OAAO,KAAA,KAAU,QAAQ,OAAO,KAAA,KAAU,YAAY,CAAC,KAAA,CAAM,QAAQ,KAAK,CAAA;AAC5E;AAFS,MAAA,CAAA,aAAA,EAAA,eAAA,CAAA;AAIT,SAAS,kBAAkB,KAAA,EAAuB;AAChD,EAAA,IAAI,CAAC,KAAA,CAAM,QAAA,CAAS,GAAG,GAAG,OAAO,KAAA;AACjC,EAAA,OAAO,KAAA,CAAM,OAAA;AAAA,IACX,eAAA;AAAA,IACA,CAAC,QAAQ,GAAA,EAAa,GAAA,KAAgB,GAAG,GAAG,CAAA,EAAG,GAAG,CAAA,CAAA,EAAI,oBAAoB,CAAA;AAAA,GAC5E;AACF;AANS,MAAA,CAAA,iBAAA,EAAA,mBAAA,CAAA;AAaT,SAAS,WAAA,CAAY,OAAgB,KAAA,EAAwB;AAC3D,EAAA,IAAI,OAAO,KAAA,KAAU,QAAA,EAAU,OAAO,kBAAkB,KAAK,CAAA;AAC7D,EAAA,IAAI,KAAA,IAAS,GAAG,OAAO,KAAA;AACvB,EAAA,IAAI,cAAc,KAAK,CAAA,SAAU,YAAA,CAAa,KAAA,EAAO,QAAQ,CAAC,CAAA;AAC9D,EAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,EAAG,OAAO,KAAA,CAAM,GAAA,CAAI,CAAA,IAAA,KAAQ,WAAA,CAAY,IAAA,EAAM,KAAK,CAAC,CAAA;AAC3E,EAAA,OAAO,KAAA;AACT;AANS,MAAA,CAAA,WAAA,EAAA,aAAA,CAAA;AAQT,SAAS,YAAA,CACP,QACA,KAAA,EACyB;AACzB,EAAA,MAAM,SAAA,GAAqC,EAAE,GAAG,MAAA,EAAO;AACvD,EAAA,KAAA,MAAW,GAAA,IAAO,MAAA,CAAO,IAAA,CAAK,SAAS,CAAA,EAAG;AACxC,IAAA,IAAI,oBAAA,CAAqB,QAAA,CAAS,GAAA,CAAI,WAAA,EAAa,CAAA,EAAG;AACpD,MAAA,SAAA,CAAU,GAAG,CAAA,GAAI,oBAAA;AACjB,MAAA;AAAA,IACF;AACA,IAAA,SAAA,CAAU,GAAG,CAAA,GAAI,WAAA,CAAY,SAAA,CAAU,GAAG,GAAG,KAAK,CAAA;AAAA,EACpD;AACA,EAAA,OAAO,SAAA;AACT;AAbS,MAAA,CAAA,YAAA,EAAA,cAAA,CAAA;AAeT,MAAM,oBAAA,GAAuB,CAAA;AActB,SAAS,sBAAsB,MAAA,EAA0B;AAC9D,EAAA,IAAI,CAAC,aAAA,CAAc,MAAM,CAAA,EAAG,OAAO,MAAA;AACnC,EAAA,OAAO,YAAA,CAAa,QAAQ,oBAAoB,CAAA;AAClD;AAHgB,MAAA,CAAA,qBAAA,EAAA,uBAAA,CAAA;AAeT,SAAS,kBAAA,CAAmB,GAAA,EAAa,SAAA,GAA+B,EAAC,EAAW;AACzF,EAAA,IAAI,OAAO,QAAQ,QAAA,IAAY,CAAC,IAAI,QAAA,CAAS,GAAG,GAAG,OAAO,GAAA;AAC1D,EAAA,IAAI,SAAA,CAAU,MAAA,KAAW,CAAA,EAAG,OAAO,kBAAkB,GAAG,CAAA;AACxD,EAAA,MAAM,OAAA,GAAU,UAAU,GAAA,CAAI,CAAA,GAAA,KAAO,IAAI,OAAA,CAAQ,qBAAA,EAAuB,MAAM,CAAC,CAAA;AAC/E,EAAA,MAAM,KAAK,IAAI,MAAA;AAAA,IACb,CAAA,SAAA,EAAY,CAAC,GAAG,oBAAA,EAAsB,GAAG,OAAO,CAAA,CAAE,IAAA,CAAK,GAAG,CAAC,CAAA,SAAA,CAAA;AAAA,IAC3D;AAAA,GACF;AACA,EAAA,OAAO,GAAA,CAAI,OAAA,CAAQ,EAAA,EAAI,CAAC,MAAA,EAAQ,GAAA,EAAa,GAAA,KAAgB,CAAA,EAAG,GAAG,CAAA,EAAG,GAAG,CAAA,CAAA,EAAI,oBAAoB,CAAA,CAAE,CAAA;AACrG;AATgB,MAAA,CAAA,kBAAA,EAAA,oBAAA,CAAA;;;;;;;"}