@reyaxyz/common 0.375.0 → 0.375.1

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/README.md CHANGED
@@ -6,5 +6,5 @@
6
6
 
7
7
  | Statements | Branches | Functions | Lines |
8
8
  | --------------------------- | ----------------------- | ------------------------- | ----------------- |
9
- | ![Statements](https://img.shields.io/badge/statements-57.37%25-red.svg?style=flat) | ![Branches](https://img.shields.io/badge/branches-58.11%25-red.svg?style=flat) | ![Functions](https://img.shields.io/badge/functions-45.04%25-red.svg?style=flat) | ![Lines](https://img.shields.io/badge/lines-60.97%25-red.svg?style=flat) |
9
+ | ![Statements](https://img.shields.io/badge/statements-58.3%25-red.svg?style=flat) | ![Branches](https://img.shields.io/badge/branches-58.8%25-red.svg?style=flat) | ![Functions](https://img.shields.io/badge/functions-45.93%25-red.svg?style=flat) | ![Lines](https://img.shields.io/badge/lines-61.84%25-red.svg?style=flat) |
10
10
 
@@ -17,6 +17,7 @@ export * from './transaction-receipt';
17
17
  export * from './layerzero';
18
18
  export * from './throttled-error';
19
19
  export * from './error-sanitizer';
20
+ export * from './url-secret-redaction';
20
21
  export * from './realized-balance';
21
22
  export * from './error-chain';
22
23
  export * from './receipt-rpc-error';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"/","sources":["utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,qBAAqB,CAAC;AACpC,cAAc,UAAU,CAAC;AACzB,cAAc,qBAAqB,CAAC;AACpC,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC;AACxB,cAAc,aAAa,CAAC;AAC5B,cAAc,SAAS,CAAC;AACxB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,uBAAuB,CAAC;AACtC,cAAc,aAAa,CAAC;AAC5B,cAAc,SAAS,CAAC;AACxB,cAAc,uBAAuB,CAAC;AACtC,cAAc,aAAa,CAAC;AAC5B,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"/","sources":["utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,qBAAqB,CAAC;AACpC,cAAc,UAAU,CAAC;AACzB,cAAc,qBAAqB,CAAC;AACpC,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC;AACxB,cAAc,aAAa,CAAC;AAC5B,cAAc,SAAS,CAAC;AACxB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,uBAAuB,CAAC;AACtC,cAAc,aAAa,CAAC;AAC5B,cAAc,SAAS,CAAC;AACxB,cAAc,uBAAuB,CAAC;AACtC,cAAc,aAAa,CAAC;AAC5B,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,wBAAwB,CAAC;AACvC,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,qBAAqB,CAAC"}
@@ -0,0 +1,15 @@
1
+ export declare const REDACTED_URL_SECRET = "<redacted>";
2
+ /**
3
+ * Masks credentials inside every URL in `text`: key-shaped path and query
4
+ * tokens, secret query parameters and userinfo. The scheme and host survive,
5
+ * so a redacted line still says which endpoint failed.
6
+ */
7
+ export declare function redactUrlSecrets(text: string): string;
8
+ /**
9
+ * Pure winston-format body for the end of a transport's chain, after `json()`.
10
+ * Redacting the serialized line covers every field (message, stack, a raw
11
+ * error's `info.requestUrl`, nested causes, `toJSON` output) whatever its name
12
+ * or depth, and runs only for lines that passed the transport's level gate.
13
+ */
14
+ export declare function redactSerializedLogLine<T extends object>(info: T): T;
15
+ //# sourceMappingURL=url-secret-redaction.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"url-secret-redaction.d.ts","sourceRoot":"/","sources":["utils/url-secret-redaction.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,mBAAmB,eAAe,CAAC;AA0ChD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGrD;AAKD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,CAAC,SAAS,MAAM,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,CAKpE"}
@@ -33,6 +33,7 @@ __exportStar(require("./transaction-receipt"), exports);
33
33
  __exportStar(require("./layerzero"), exports);
34
34
  __exportStar(require("./throttled-error"), exports);
35
35
  __exportStar(require("./error-sanitizer"), exports);
36
+ __exportStar(require("./url-secret-redaction"), exports);
36
37
  __exportStar(require("./realized-balance"), exports);
37
38
  __exportStar(require("./error-chain"), exports);
38
39
  __exportStar(require("./receipt-rpc-error"), exports);
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"/","sources":["utils/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2CAAyB;AACzB,2CAAyB;AACzB,8CAA4B;AAC5B,sDAAoC;AACpC,2CAAyB;AACzB,sDAAoC;AACpC,4CAA0B;AAC1B,2CAAyB;AACzB,0CAAwB;AACxB,8CAA4B;AAC5B,0CAAwB;AACxB,iDAA+B;AAC/B,wDAAsC;AACtC,8CAA4B;AAC5B,0CAAwB;AACxB,wDAAsC;AACtC,8CAA4B;AAC5B,oDAAkC;AAClC,oDAAkC;AAClC,qDAAmC;AACnC,gDAA8B;AAC9B,sDAAoC","sourcesContent":["export * from './consts';\nexport * from './points';\nexport * from './calculate';\nexport * from './compute-trade-pnl';\nexport * from './struct';\nexport * from './cooldown-interval';\nexport * from './network';\nexport * from './number';\nexport * from './retry';\nexport * from './timestamp';\nexport * from './token';\nexport * from './task-wrapper';\nexport * from './whitelisted-wallets';\nexport * from './stringify';\nexport * from './delay';\nexport * from './transaction-receipt';\nexport * from './layerzero';\nexport * from './throttled-error';\nexport * from './error-sanitizer';\nexport * from './realized-balance';\nexport * from './error-chain';\nexport * from './receipt-rpc-error';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"/","sources":["utils/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2CAAyB;AACzB,2CAAyB;AACzB,8CAA4B;AAC5B,sDAAoC;AACpC,2CAAyB;AACzB,sDAAoC;AACpC,4CAA0B;AAC1B,2CAAyB;AACzB,0CAAwB;AACxB,8CAA4B;AAC5B,0CAAwB;AACxB,iDAA+B;AAC/B,wDAAsC;AACtC,8CAA4B;AAC5B,0CAAwB;AACxB,wDAAsC;AACtC,8CAA4B;AAC5B,oDAAkC;AAClC,oDAAkC;AAClC,yDAAuC;AACvC,qDAAmC;AACnC,gDAA8B;AAC9B,sDAAoC","sourcesContent":["export * from './consts';\nexport * from './points';\nexport * from './calculate';\nexport * from './compute-trade-pnl';\nexport * from './struct';\nexport * from './cooldown-interval';\nexport * from './network';\nexport * from './number';\nexport * from './retry';\nexport * from './timestamp';\nexport * from './token';\nexport * from './task-wrapper';\nexport * from './whitelisted-wallets';\nexport * from './stringify';\nexport * from './delay';\nexport * from './transaction-receipt';\nexport * from './layerzero';\nexport * from './throttled-error';\nexport * from './error-sanitizer';\nexport * from './url-secret-redaction';\nexport * from './realized-balance';\nexport * from './error-chain';\nexport * from './receipt-rpc-error';\n"]}
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.redactSerializedLogLine = exports.redactUrlSecrets = exports.REDACTED_URL_SECRET = void 0;
4
+ // RPC providers carry the API key in the URL path (Conduit, Alchemy, Infura,
5
+ // Tenderly, ...), and ethers v6 embeds the full request URL in a SERVER_ERROR's
6
+ // message, stack and `info.requestUrl`. Every logged RPC failure therefore
7
+ // wrote the keyed endpoint, which Datadog then pasted into alert samples. URL
8
+ // credentials are masked here, host-agnostically, so a new provider is covered
9
+ // without a list to keep in sync.
10
+ exports.REDACTED_URL_SECRET = '<redacted>';
11
+ // A URL runs from its scheme to the first character that cannot appear
12
+ // unescaped in one: whitespace, a quote, a backslash (JSON escaping inside a
13
+ // serialized log line) or an angle bracket.
14
+ var URL_IN_TEXT = /\b[a-z][a-z0-9+.-]*:\/\/[^\s"'`\\<>]+/gi;
15
+ var AUTHORITY_END = /[/?#]/;
16
+ // Provider keys are 22-40 characters. A run is matched wherever it sits in the
17
+ // path or query, so `/bot<id>:<token>/` style segments are covered too.
18
+ var LONG_TOKEN = /[A-Za-z0-9_-]{20,}/g;
19
+ // Long runs that are identifiers rather than keys: 0x hashes and addresses,
20
+ // UUIDs, single-case words and slugs, camelCase words and numbers. A random
21
+ // key is practically never any of these: sampled, about 1 in 6,000 random
22
+ // 22-character base62 keys, and none in 400,000 at 33 (Conduit's length).
23
+ var IDENTIFIER = /^(?:0x[0-9a-fA-F]+|[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}|[a-z_-]+|[A-Z_-]+|[a-z]*(?:[A-Z][a-z]+)+|[0-9_-]+)$/;
24
+ // Secret-bearing query parameters whose values may be shorter than LONG_TOKEN.
25
+ var SECRET_PARAM = /([?&;](?:api[-_]?key|key|token|access[-_]?token|auth|secret|client[-_]?secret|password|passwd|pwd|dkey|dd-api-key|x-api-key|signature|sig)=)[^&#;]*/gi;
26
+ var redactUrl = function (url) {
27
+ var schemeEnd = url.indexOf('://') + 3;
28
+ var rest = url.slice(schemeEnd);
29
+ var authorityLength = rest.search(AUTHORITY_END);
30
+ var authority = authorityLength === -1 ? rest : rest.slice(0, authorityLength);
31
+ var tail = authorityLength === -1 ? '' : rest.slice(authorityLength);
32
+ // `user:pass@host`: the whole userinfo goes, since some providers put the
33
+ // key in the username.
34
+ var at = authority.lastIndexOf('@');
35
+ if (at !== -1)
36
+ authority = "".concat(exports.REDACTED_URL_SECRET, "@").concat(authority.slice(at + 1));
37
+ var redactedTail = tail
38
+ .replace(SECRET_PARAM, "$1".concat(exports.REDACTED_URL_SECRET))
39
+ .replace(LONG_TOKEN, function (token) {
40
+ return IDENTIFIER.test(token) ? token : exports.REDACTED_URL_SECRET;
41
+ });
42
+ return url.slice(0, schemeEnd) + authority + redactedTail;
43
+ };
44
+ /**
45
+ * Masks credentials inside every URL in `text`: key-shaped path and query
46
+ * tokens, secret query parameters and userinfo. The scheme and host survive,
47
+ * so a redacted line still says which endpoint failed.
48
+ */
49
+ function redactUrlSecrets(text) {
50
+ if (!text.includes('://'))
51
+ return text;
52
+ return text.replace(URL_IN_TEXT, redactUrl);
53
+ }
54
+ exports.redactUrlSecrets = redactUrlSecrets;
55
+ // triple-beam's MESSAGE: the serialized line a winston transport writes.
56
+ var MESSAGE = Symbol.for('message');
57
+ /**
58
+ * Pure winston-format body for the end of a transport's chain, after `json()`.
59
+ * Redacting the serialized line covers every field (message, stack, a raw
60
+ * error's `info.requestUrl`, nested causes, `toJSON` output) whatever its name
61
+ * or depth, and runs only for lines that passed the transport's level gate.
62
+ */
63
+ function redactSerializedLogLine(info) {
64
+ var record = info;
65
+ var line = record[MESSAGE];
66
+ if (typeof line === 'string')
67
+ record[MESSAGE] = redactUrlSecrets(line);
68
+ return info;
69
+ }
70
+ exports.redactSerializedLogLine = redactSerializedLogLine;
71
+ //# sourceMappingURL=url-secret-redaction.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"url-secret-redaction.js","sourceRoot":"/","sources":["utils/url-secret-redaction.ts"],"names":[],"mappings":";;;AAAA,6EAA6E;AAC7E,gFAAgF;AAChF,2EAA2E;AAC3E,8EAA8E;AAC9E,+EAA+E;AAC/E,kCAAkC;AACrB,QAAA,mBAAmB,GAAG,YAAY,CAAC;AAEhD,uEAAuE;AACvE,6EAA6E;AAC7E,4CAA4C;AAC5C,IAAM,WAAW,GAAG,yCAAyC,CAAC;AAC9D,IAAM,aAAa,GAAG,OAAO,CAAC;AAC9B,+EAA+E;AAC/E,wEAAwE;AACxE,IAAM,UAAU,GAAG,qBAAqB,CAAC;AACzC,4EAA4E;AAC5E,4EAA4E;AAC5E,0EAA0E;AAC1E,0EAA0E;AAC1E,IAAM,UAAU,GACd,oJAAoJ,CAAC;AACvJ,+EAA+E;AAC/E,IAAM,YAAY,GAChB,uJAAuJ,CAAC;AAE1J,IAAM,SAAS,GAAG,UAAC,GAAW;IAC5B,IAAM,SAAS,GAAG,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACzC,IAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IAClC,IAAM,eAAe,GAAG,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;IACnD,IAAI,SAAS,GACX,eAAe,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,eAAe,CAAC,CAAC;IACjE,IAAM,IAAI,GAAG,eAAe,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC;IAEvE,0EAA0E;IAC1E,uBAAuB;IACvB,IAAM,EAAE,GAAG,SAAS,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACtC,IAAI,EAAE,KAAK,CAAC,CAAC;QACX,SAAS,GAAG,UAAG,2BAAmB,cAAI,SAAS,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAE,CAAC;IAElE,IAAM,YAAY,GAAG,IAAI;SACtB,OAAO,CAAC,YAAY,EAAE,YAAK,2BAAmB,CAAE,CAAC;SACjD,OAAO,CAAC,UAAU,EAAE,UAAC,KAAK;QACzB,OAAA,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,2BAAmB;IAApD,CAAoD,CACrD,CAAC;IACJ,OAAO,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,GAAG,SAAS,GAAG,YAAY,CAAC;AAC5D,CAAC,CAAC;AAEF;;;;GAIG;AACH,SAAgB,gBAAgB,CAAC,IAAY;IAC3C,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACvC,OAAO,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC;AAC9C,CAAC;AAHD,4CAGC;AAED,yEAAyE;AACzE,IAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;AAEtC;;;;;GAKG;AACH,SAAgB,uBAAuB,CAAmB,IAAO;IAC/D,IAAM,MAAM,GAAG,IAA+B,CAAC;IAC/C,IAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IAC7B,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,MAAM,CAAC,OAAO,CAAC,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;IACvE,OAAO,IAAI,CAAC;AACd,CAAC;AALD,0DAKC","sourcesContent":["// RPC providers carry the API key in the URL path (Conduit, Alchemy, Infura,\n// Tenderly, ...), and ethers v6 embeds the full request URL in a SERVER_ERROR's\n// message, stack and `info.requestUrl`. Every logged RPC failure therefore\n// wrote the keyed endpoint, which Datadog then pasted into alert samples. URL\n// credentials are masked here, host-agnostically, so a new provider is covered\n// without a list to keep in sync.\nexport const REDACTED_URL_SECRET = '<redacted>';\n\n// A URL runs from its scheme to the first character that cannot appear\n// unescaped in one: whitespace, a quote, a backslash (JSON escaping inside a\n// serialized log line) or an angle bracket.\nconst URL_IN_TEXT = /\\b[a-z][a-z0-9+.-]*:\\/\\/[^\\s\"'`\\\\<>]+/gi;\nconst AUTHORITY_END = /[/?#]/;\n// Provider keys are 22-40 characters. A run is matched wherever it sits in the\n// path or query, so `/bot<id>:<token>/` style segments are covered too.\nconst LONG_TOKEN = /[A-Za-z0-9_-]{20,}/g;\n// Long runs that are identifiers rather than keys: 0x hashes and addresses,\n// UUIDs, single-case words and slugs, camelCase words and numbers. A random\n// key is practically never any of these: sampled, about 1 in 6,000 random\n// 22-character base62 keys, and none in 400,000 at 33 (Conduit's length).\nconst IDENTIFIER =\n /^(?:0x[0-9a-fA-F]+|[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}|[a-z_-]+|[A-Z_-]+|[a-z]*(?:[A-Z][a-z]+)+|[0-9_-]+)$/;\n// Secret-bearing query parameters whose values may be shorter than LONG_TOKEN.\nconst SECRET_PARAM =\n /([?&;](?:api[-_]?key|key|token|access[-_]?token|auth|secret|client[-_]?secret|password|passwd|pwd|dkey|dd-api-key|x-api-key|signature|sig)=)[^&#;]*/gi;\n\nconst redactUrl = (url: string): string => {\n const schemeEnd = url.indexOf('://') + 3;\n const rest = url.slice(schemeEnd);\n const authorityLength = rest.search(AUTHORITY_END);\n let authority =\n authorityLength === -1 ? rest : rest.slice(0, authorityLength);\n const tail = authorityLength === -1 ? '' : rest.slice(authorityLength);\n\n // `user:pass@host`: the whole userinfo goes, since some providers put the\n // key in the username.\n const at = authority.lastIndexOf('@');\n if (at !== -1)\n authority = `${REDACTED_URL_SECRET}@${authority.slice(at + 1)}`;\n\n const redactedTail = tail\n .replace(SECRET_PARAM, `$1${REDACTED_URL_SECRET}`)\n .replace(LONG_TOKEN, (token) =>\n IDENTIFIER.test(token) ? token : REDACTED_URL_SECRET,\n );\n return url.slice(0, schemeEnd) + authority + redactedTail;\n};\n\n/**\n * Masks credentials inside every URL in `text`: key-shaped path and query\n * tokens, secret query parameters and userinfo. The scheme and host survive,\n * so a redacted line still says which endpoint failed.\n */\nexport function redactUrlSecrets(text: string): string {\n if (!text.includes('://')) return text;\n return text.replace(URL_IN_TEXT, redactUrl);\n}\n\n// triple-beam's MESSAGE: the serialized line a winston transport writes.\nconst MESSAGE = Symbol.for('message');\n\n/**\n * Pure winston-format body for the end of a transport's chain, after `json()`.\n * Redacting the serialized line covers every field (message, stack, a raw\n * error's `info.requestUrl`, nested causes, `toJSON` output) whatever its name\n * or depth, and runs only for lines that passed the transport's level gate.\n */\nexport function redactSerializedLogLine<T extends object>(info: T): T {\n const record = info as Record<symbol, unknown>;\n const line = record[MESSAGE];\n if (typeof line === 'string') record[MESSAGE] = redactUrlSecrets(line);\n return info;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reyaxyz/common",
3
- "version": "0.375.0",
3
+ "version": "0.375.1",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "registry": "https://registry.npmjs.org"
@@ -41,5 +41,5 @@
41
41
  "test:coverage": "npx jest test --coverage",
42
42
  "generate:coverage-badges": "npx istanbul-badges-readme --silent"
43
43
  },
44
- "gitHead": "a8533fadd939f92e45947d80028bdbc3d20bd8cb"
44
+ "gitHead": "791417881bf992324c38880a478e6cac391be7f9"
45
45
  }
@@ -17,6 +17,7 @@ export * from './transaction-receipt';
17
17
  export * from './layerzero';
18
18
  export * from './throttled-error';
19
19
  export * from './error-sanitizer';
20
+ export * from './url-secret-redaction';
20
21
  export * from './realized-balance';
21
22
  export * from './error-chain';
22
23
  export * from './receipt-rpc-error';
@@ -0,0 +1,73 @@
1
+ // RPC providers carry the API key in the URL path (Conduit, Alchemy, Infura,
2
+ // Tenderly, ...), and ethers v6 embeds the full request URL in a SERVER_ERROR's
3
+ // message, stack and `info.requestUrl`. Every logged RPC failure therefore
4
+ // wrote the keyed endpoint, which Datadog then pasted into alert samples. URL
5
+ // credentials are masked here, host-agnostically, so a new provider is covered
6
+ // without a list to keep in sync.
7
+ export const REDACTED_URL_SECRET = '<redacted>';
8
+
9
+ // A URL runs from its scheme to the first character that cannot appear
10
+ // unescaped in one: whitespace, a quote, a backslash (JSON escaping inside a
11
+ // serialized log line) or an angle bracket.
12
+ const URL_IN_TEXT = /\b[a-z][a-z0-9+.-]*:\/\/[^\s"'`\\<>]+/gi;
13
+ const AUTHORITY_END = /[/?#]/;
14
+ // Provider keys are 22-40 characters. A run is matched wherever it sits in the
15
+ // path or query, so `/bot<id>:<token>/` style segments are covered too.
16
+ const LONG_TOKEN = /[A-Za-z0-9_-]{20,}/g;
17
+ // Long runs that are identifiers rather than keys: 0x hashes and addresses,
18
+ // UUIDs, single-case words and slugs, camelCase words and numbers. A random
19
+ // key is practically never any of these: sampled, about 1 in 6,000 random
20
+ // 22-character base62 keys, and none in 400,000 at 33 (Conduit's length).
21
+ const IDENTIFIER =
22
+ /^(?:0x[0-9a-fA-F]+|[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}|[a-z_-]+|[A-Z_-]+|[a-z]*(?:[A-Z][a-z]+)+|[0-9_-]+)$/;
23
+ // Secret-bearing query parameters whose values may be shorter than LONG_TOKEN.
24
+ const SECRET_PARAM =
25
+ /([?&;](?:api[-_]?key|key|token|access[-_]?token|auth|secret|client[-_]?secret|password|passwd|pwd|dkey|dd-api-key|x-api-key|signature|sig)=)[^&#;]*/gi;
26
+
27
+ const redactUrl = (url: string): string => {
28
+ const schemeEnd = url.indexOf('://') + 3;
29
+ const rest = url.slice(schemeEnd);
30
+ const authorityLength = rest.search(AUTHORITY_END);
31
+ let authority =
32
+ authorityLength === -1 ? rest : rest.slice(0, authorityLength);
33
+ const tail = authorityLength === -1 ? '' : rest.slice(authorityLength);
34
+
35
+ // `user:pass@host`: the whole userinfo goes, since some providers put the
36
+ // key in the username.
37
+ const at = authority.lastIndexOf('@');
38
+ if (at !== -1)
39
+ authority = `${REDACTED_URL_SECRET}@${authority.slice(at + 1)}`;
40
+
41
+ const redactedTail = tail
42
+ .replace(SECRET_PARAM, `$1${REDACTED_URL_SECRET}`)
43
+ .replace(LONG_TOKEN, (token) =>
44
+ IDENTIFIER.test(token) ? token : REDACTED_URL_SECRET,
45
+ );
46
+ return url.slice(0, schemeEnd) + authority + redactedTail;
47
+ };
48
+
49
+ /**
50
+ * Masks credentials inside every URL in `text`: key-shaped path and query
51
+ * tokens, secret query parameters and userinfo. The scheme and host survive,
52
+ * so a redacted line still says which endpoint failed.
53
+ */
54
+ export function redactUrlSecrets(text: string): string {
55
+ if (!text.includes('://')) return text;
56
+ return text.replace(URL_IN_TEXT, redactUrl);
57
+ }
58
+
59
+ // triple-beam's MESSAGE: the serialized line a winston transport writes.
60
+ const MESSAGE = Symbol.for('message');
61
+
62
+ /**
63
+ * Pure winston-format body for the end of a transport's chain, after `json()`.
64
+ * Redacting the serialized line covers every field (message, stack, a raw
65
+ * error's `info.requestUrl`, nested causes, `toJSON` output) whatever its name
66
+ * or depth, and runs only for lines that passed the transport's level gate.
67
+ */
68
+ export function redactSerializedLogLine<T extends object>(info: T): T {
69
+ const record = info as Record<symbol, unknown>;
70
+ const line = record[MESSAGE];
71
+ if (typeof line === 'string') record[MESSAGE] = redactUrlSecrets(line);
72
+ return info;
73
+ }