UNPKG

@bitrix24/b24jssdk

Version:

Bitrix24 REST API JavaScript SDK

1 lines 9.8 kB
{"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":"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"}