@n8n/utils 1.44.0 → 1.46.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/dist/errors/error-chain.cjs +36 -0
- package/dist/errors/error-chain.cjs.map +1 -0
- package/dist/errors/error-chain.d.cts +7 -0
- package/dist/errors/error-chain.d.mts +7 -0
- package/dist/errors/error-chain.mjs +34 -0
- package/dist/errors/error-chain.mjs.map +1 -0
- package/dist/format-pem-block.cjs +5 -2
- package/dist/format-pem-block.cjs.map +1 -1
- package/dist/format-pem-block.mjs +5 -2
- package/dist/format-pem-block.mjs.map +1 -1
- package/dist/number/bytes.cjs +21 -0
- package/dist/number/bytes.cjs.map +1 -0
- package/dist/number/bytes.d.cts +6 -0
- package/dist/number/bytes.d.mts +6 -0
- package/dist/number/bytes.mjs +19 -0
- package/dist/number/bytes.mjs.map +1 -0
- package/dist/redaction/pii-patterns.cjs +188 -0
- package/dist/redaction/pii-patterns.cjs.map +1 -0
- package/dist/redaction/pii-patterns.d.cts +23 -0
- package/dist/redaction/pii-patterns.d.mts +23 -0
- package/dist/redaction/pii-patterns.mjs +180 -0
- package/dist/redaction/pii-patterns.mjs.map +1 -0
- package/dist/redaction/redact-text.cjs +152 -0
- package/dist/redaction/redact-text.cjs.map +1 -0
- package/dist/redaction/redact-text.d.cts +29 -0
- package/dist/redaction/redact-text.d.mts +29 -0
- package/dist/redaction/redact-text.mjs +148 -0
- package/dist/redaction/redact-text.mjs.map +1 -0
- package/dist/scrub-secrets.cjs +2 -0
- package/dist/scrub-secrets.cjs.map +1 -1
- package/dist/scrub-secrets.mjs +2 -0
- package/dist/scrub-secrets.mjs.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region src/errors/error-chain.ts
|
|
3
|
+
/** Any non-null object. Unlike is-record.ts this accepts arrays, so a wrapped array stays in the chain. */
|
|
4
|
+
const isObjectLike = (value) => typeof value === "object" && value !== null;
|
|
5
|
+
const MAX_CHAIN_DEPTH = 5;
|
|
6
|
+
/** The keys errors wrap each other under. */
|
|
7
|
+
const WRAPPING_KEYS = [
|
|
8
|
+
"cause",
|
|
9
|
+
"errorResponse",
|
|
10
|
+
"reason"
|
|
11
|
+
];
|
|
12
|
+
/** The error and the errors it wraps, shallowest first, each visited once. */
|
|
13
|
+
function errorChain(error) {
|
|
14
|
+
if (!isObjectLike(error)) return [];
|
|
15
|
+
const seen = /* @__PURE__ */ new Set([error]);
|
|
16
|
+
const chain = [error];
|
|
17
|
+
let generation = [error];
|
|
18
|
+
for (let depth = 0; depth < MAX_CHAIN_DEPTH && generation.length > 0; depth++) {
|
|
19
|
+
const next = [];
|
|
20
|
+
for (const level of generation) for (const key of WRAPPING_KEYS) {
|
|
21
|
+
const wrapped = level[key];
|
|
22
|
+
if (isObjectLike(wrapped) && !seen.has(wrapped)) {
|
|
23
|
+
seen.add(wrapped);
|
|
24
|
+
next.push(wrapped);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
chain.push(...next);
|
|
28
|
+
generation = next;
|
|
29
|
+
}
|
|
30
|
+
return chain;
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
exports.errorChain = errorChain;
|
|
34
|
+
exports.isObjectLike = isObjectLike;
|
|
35
|
+
|
|
36
|
+
//# sourceMappingURL=error-chain.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-chain.cjs","names":[],"sources":["../../src/errors/error-chain.ts"],"sourcesContent":["export type UnknownRecord = Readonly<Record<string, unknown>>;\n\n/** Any non-null object. Unlike is-record.ts this accepts arrays, so a wrapped array stays in the chain. */\nexport const isObjectLike = (value: unknown): value is UnknownRecord =>\n\ttypeof value === 'object' && value !== null;\n\nconst MAX_CHAIN_DEPTH = 5;\n\n/** The keys errors wrap each other under. */\nconst WRAPPING_KEYS = ['cause', 'errorResponse', 'reason'] as const;\n\n/** The error and the errors it wraps, shallowest first, each visited once. */\nexport function errorChain(error: unknown): UnknownRecord[] {\n\tif (!isObjectLike(error)) {\n\t\treturn [];\n\t}\n\n\tconst seen = new Set<UnknownRecord>([error]);\n\tconst chain: UnknownRecord[] = [error];\n\tlet generation: UnknownRecord[] = [error];\n\n\tfor (let depth = 0; depth < MAX_CHAIN_DEPTH && generation.length > 0; depth++) {\n\t\tconst next: UnknownRecord[] = [];\n\t\tfor (const level of generation) {\n\t\t\tfor (const key of WRAPPING_KEYS) {\n\t\t\t\tconst wrapped = level[key];\n\t\t\t\tif (isObjectLike(wrapped) && !seen.has(wrapped)) {\n\t\t\t\t\tseen.add(wrapped);\n\t\t\t\t\tnext.push(wrapped);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tchain.push(...next);\n\t\tgeneration = next;\n\t}\n\n\treturn chain;\n}\n"],"mappings":";;;AAGA,MAAa,gBAAgB,UAC5B,OAAO,UAAU,YAAY,UAAU;AAExC,MAAM,kBAAkB;;AAGxB,MAAM,gBAAgB;CAAC;CAAS;CAAiB;AAAQ;;AAGzD,SAAgB,WAAW,OAAiC;CAC3D,IAAI,CAAC,aAAa,KAAK,GACtB,OAAO,CAAC;CAGT,MAAM,uBAAO,IAAI,IAAmB,CAAC,KAAK,CAAC;CAC3C,MAAM,QAAyB,CAAC,KAAK;CACrC,IAAI,aAA8B,CAAC,KAAK;CAExC,KAAK,IAAI,QAAQ,GAAG,QAAQ,mBAAmB,WAAW,SAAS,GAAG,SAAS;EAC9E,MAAM,OAAwB,CAAC;EAC/B,KAAK,MAAM,SAAS,YACnB,KAAK,MAAM,OAAO,eAAe;GAChC,MAAM,UAAU,MAAM;GACtB,IAAI,aAAa,OAAO,KAAK,CAAC,KAAK,IAAI,OAAO,GAAG;IAChD,KAAK,IAAI,OAAO;IAChB,KAAK,KAAK,OAAO;GAClB;EACD;EAED,MAAM,KAAK,GAAG,IAAI;EAClB,aAAa;CACd;CAEA,OAAO;AACR"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
//#region src/errors/error-chain.d.ts
|
|
2
|
+
type UnknownRecord = Readonly<Record<string, unknown>>;
|
|
3
|
+
declare const isObjectLike: (value: unknown) => value is UnknownRecord;
|
|
4
|
+
declare function errorChain(error: unknown): UnknownRecord[];
|
|
5
|
+
//#endregion
|
|
6
|
+
export { UnknownRecord, errorChain, isObjectLike };
|
|
7
|
+
//# sourceMappingURL=error-chain.d.cts.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
//#region src/errors/error-chain.d.ts
|
|
2
|
+
type UnknownRecord = Readonly<Record<string, unknown>>;
|
|
3
|
+
declare const isObjectLike: (value: unknown) => value is UnknownRecord;
|
|
4
|
+
declare function errorChain(error: unknown): UnknownRecord[];
|
|
5
|
+
//#endregion
|
|
6
|
+
export { UnknownRecord, errorChain, isObjectLike };
|
|
7
|
+
//# sourceMappingURL=error-chain.d.mts.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
//#region src/errors/error-chain.ts
|
|
2
|
+
/** Any non-null object. Unlike is-record.ts this accepts arrays, so a wrapped array stays in the chain. */
|
|
3
|
+
const isObjectLike = (value) => typeof value === "object" && value !== null;
|
|
4
|
+
const MAX_CHAIN_DEPTH = 5;
|
|
5
|
+
/** The keys errors wrap each other under. */
|
|
6
|
+
const WRAPPING_KEYS = [
|
|
7
|
+
"cause",
|
|
8
|
+
"errorResponse",
|
|
9
|
+
"reason"
|
|
10
|
+
];
|
|
11
|
+
/** The error and the errors it wraps, shallowest first, each visited once. */
|
|
12
|
+
function errorChain(error) {
|
|
13
|
+
if (!isObjectLike(error)) return [];
|
|
14
|
+
const seen = /* @__PURE__ */ new Set([error]);
|
|
15
|
+
const chain = [error];
|
|
16
|
+
let generation = [error];
|
|
17
|
+
for (let depth = 0; depth < MAX_CHAIN_DEPTH && generation.length > 0; depth++) {
|
|
18
|
+
const next = [];
|
|
19
|
+
for (const level of generation) for (const key of WRAPPING_KEYS) {
|
|
20
|
+
const wrapped = level[key];
|
|
21
|
+
if (isObjectLike(wrapped) && !seen.has(wrapped)) {
|
|
22
|
+
seen.add(wrapped);
|
|
23
|
+
next.push(wrapped);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
chain.push(...next);
|
|
27
|
+
generation = next;
|
|
28
|
+
}
|
|
29
|
+
return chain;
|
|
30
|
+
}
|
|
31
|
+
//#endregion
|
|
32
|
+
export { errorChain, isObjectLike };
|
|
33
|
+
|
|
34
|
+
//# sourceMappingURL=error-chain.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-chain.mjs","names":[],"sources":["../../src/errors/error-chain.ts"],"sourcesContent":["export type UnknownRecord = Readonly<Record<string, unknown>>;\n\n/** Any non-null object. Unlike is-record.ts this accepts arrays, so a wrapped array stays in the chain. */\nexport const isObjectLike = (value: unknown): value is UnknownRecord =>\n\ttypeof value === 'object' && value !== null;\n\nconst MAX_CHAIN_DEPTH = 5;\n\n/** The keys errors wrap each other under. */\nconst WRAPPING_KEYS = ['cause', 'errorResponse', 'reason'] as const;\n\n/** The error and the errors it wraps, shallowest first, each visited once. */\nexport function errorChain(error: unknown): UnknownRecord[] {\n\tif (!isObjectLike(error)) {\n\t\treturn [];\n\t}\n\n\tconst seen = new Set<UnknownRecord>([error]);\n\tconst chain: UnknownRecord[] = [error];\n\tlet generation: UnknownRecord[] = [error];\n\n\tfor (let depth = 0; depth < MAX_CHAIN_DEPTH && generation.length > 0; depth++) {\n\t\tconst next: UnknownRecord[] = [];\n\t\tfor (const level of generation) {\n\t\t\tfor (const key of WRAPPING_KEYS) {\n\t\t\t\tconst wrapped = level[key];\n\t\t\t\tif (isObjectLike(wrapped) && !seen.has(wrapped)) {\n\t\t\t\t\tseen.add(wrapped);\n\t\t\t\t\tnext.push(wrapped);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tchain.push(...next);\n\t\tgeneration = next;\n\t}\n\n\treturn chain;\n}\n"],"mappings":";;AAGA,MAAa,gBAAgB,UAC5B,OAAO,UAAU,YAAY,UAAU;AAExC,MAAM,kBAAkB;;AAGxB,MAAM,gBAAgB;CAAC;CAAS;CAAiB;AAAQ;;AAGzD,SAAgB,WAAW,OAAiC;CAC3D,IAAI,CAAC,aAAa,KAAK,GACtB,OAAO,CAAC;CAGT,MAAM,uBAAO,IAAI,IAAmB,CAAC,KAAK,CAAC;CAC3C,MAAM,QAAyB,CAAC,KAAK;CACrC,IAAI,aAA8B,CAAC,KAAK;CAExC,KAAK,IAAI,QAAQ,GAAG,QAAQ,mBAAmB,WAAW,SAAS,GAAG,SAAS;EAC9E,MAAM,OAAwB,CAAC;EAC/B,KAAK,MAAM,SAAS,YACnB,KAAK,MAAM,OAAO,eAAe;GAChC,MAAM,UAAU,MAAM;GACtB,IAAI,aAAa,OAAO,KAAK,CAAC,KAAK,IAAI,OAAO,GAAG;IAChD,KAAK,IAAI,OAAO;IAChB,KAAK,KAAK,OAAO;GAClB;EACD;EAED,MAAM,KAAK,GAAG,IAAI;EAClB,aAAa;CACd;CAEA,OAAO;AACR"}
|
|
@@ -9,13 +9,16 @@ function formatCompactPem(pem, isPublic) {
|
|
|
9
9
|
if (!pemMatch) return void 0;
|
|
10
10
|
const [, label, body] = pemMatch;
|
|
11
11
|
const normalizedBody = body.replace(/\\n/g, "\n").trim();
|
|
12
|
-
return `-----BEGIN ${label}-----\n${/\s/.test(normalizedBody) ? normalizedBody.replace(/:\s+/g, ":").replace(/\s+/g, "\n") : (normalizedBody.match(new RegExp(`.{1,${PEM_BODY_LINE_LENGTH}}`, "g")) ?? []).join("\n")}\n-----END ${label}-----`;
|
|
12
|
+
return `-----BEGIN ${label}-----\n${/\s/.test(normalizedBody) ? normalizedBody.replace(/:\s+/g, ":").replace(/\s+/g, "\n").replace(/^(?:(?:Proc-Type|DEK-Info):\S+\n)+/, (headers) => `${headers.replace(/^(Proc-Type|DEK-Info):/gm, "$1: ")}\n`) : (normalizedBody.match(new RegExp(`.{1,${PEM_BODY_LINE_LENGTH}}`, "g")) ?? []).join("\n")}\n-----END ${label}-----`;
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
15
15
|
* Normalize a single PEM-encoded block (private key, public key, or certificate)
|
|
16
16
|
* by collapsing whitespace and wrapping the body at 64 chars. Multi-block PEM
|
|
17
17
|
* chains are returned unchanged.
|
|
18
18
|
*
|
|
19
|
+
* Input that is not a single-line PEM block is returned unchanged, so a plain
|
|
20
|
+
* secret (e.g. a key passphrase) passed here by mistake is not corrupted.
|
|
21
|
+
*
|
|
19
22
|
* @param pem - The PEM-encoded block to format.
|
|
20
23
|
* @param isPublic - When true, match `PUBLIC KEY` labels instead of the default `PRIVATE KEY` / `CERTIFICATE`.
|
|
21
24
|
* @returns The formatted PEM block.
|
|
@@ -23,7 +26,7 @@ function formatCompactPem(pem, isPublic) {
|
|
|
23
26
|
function formatPemBlock(pem, isPublic = false) {
|
|
24
27
|
let regex = /(PRIVATE KEY|CERTIFICATE)/;
|
|
25
28
|
if (isPublic) regex = /(PUBLIC KEY)/;
|
|
26
|
-
if (!pem || /\n/.test(pem)) return pem;
|
|
29
|
+
if (!pem || /\n/.test(pem) || !pem.includes("-----BEGIN ")) return pem;
|
|
27
30
|
const compactPem = formatCompactPem(pem, isPublic);
|
|
28
31
|
if (compactPem !== void 0) return compactPem;
|
|
29
32
|
let formattedPem = "";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format-pem-block.cjs","names":[],"sources":["../src/format-pem-block.ts"],"sourcesContent":["const PEM_BODY_LINE_LENGTH = 64;\n\nfunction formatCompactPem(pem: string, isPublic: boolean): string | undefined {\n\tconst trimmed = pem.trim();\n\tif ((trimmed.match(/-----BEGIN /g) ?? []).length !== 1) return undefined;\n\n\tconst labelPattern = isPublic ? '[A-Z0-9 ]*PUBLIC KEY' : '[A-Z0-9 ]*PRIVATE KEY|CERTIFICATE';\n\tconst pemMatch = trimmed.match(\n\t\tnew RegExp(`^-----BEGIN (${labelPattern})-----([\\\\s\\\\S]*?)-----END \\\\1-----$`),\n\t);\n\n\tif (!pemMatch) return undefined;\n\n\tconst [, label, body] = pemMatch;\n\tconst normalizedBody = body.replace(/\\\\n/g, '\\n').trim();\n\tconst formattedBody = /\\s/.test(normalizedBody)\n\t\t? normalizedBody.replace(/:\\s+/g, ':').replace(/\\s+/g, '\\n')\n\t\t: (normalizedBody.match(new RegExp(`.{1,${PEM_BODY_LINE_LENGTH}}`, 'g')) ?? []).join('\\n');\n\n\treturn `-----BEGIN ${label}-----\\n${formattedBody}\\n-----END ${label}-----`;\n}\n\n/**\n * Normalize a single PEM-encoded block (private key, public key, or certificate)\n * by collapsing whitespace and wrapping the body at 64 chars. Multi-block PEM\n * chains are returned unchanged.\n *\n * @param pem - The PEM-encoded block to format.\n * @param isPublic - When true, match `PUBLIC KEY` labels instead of the default `PRIVATE KEY` / `CERTIFICATE`.\n * @returns The formatted PEM block.\n */\nexport function formatPemBlock(pem: string, isPublic = false): string {\n\tlet regex = /(PRIVATE KEY|CERTIFICATE)/;\n\tif (isPublic) {\n\t\tregex = /(PUBLIC KEY)/;\n\t}\n\tif (!pem || /\\n/.test(pem)) {\n\t\treturn pem;\n\t}\n\tconst compactPem = formatCompactPem(pem, isPublic);\n\tif (compactPem !== undefined) {\n\t\treturn compactPem;\n\t}\n\n\tlet formattedPem = '';\n\tconst parts = pem.split('-----').filter((item) => item !== '');\n\tparts.forEach((part) => {\n\t\tif (regex.test(part)) {\n\t\t\tformattedPem += `-----${part}-----`;\n\t\t} else {\n\t\t\tconst passRegex = /Proc-Type|DEK-Info/;\n\t\t\tif (passRegex.test(part)) {\n\t\t\t\tpart = part.replace(/:\\s+/g, ':');\n\t\t\t\tformattedPem += part.replace(/\\\\n/g, '\\n').replace(/\\s+/g, '\\n');\n\t\t\t} else {\n\t\t\t\tformattedPem += part.replace(/\\\\n/g, '\\n').replace(/\\s+/g, '\\n');\n\t\t\t}\n\t\t}\n\t});\n\treturn formattedPem;\n}\n"],"mappings":";;AAAA,MAAM,uBAAuB;AAE7B,SAAS,iBAAiB,KAAa,UAAuC;CAC7E,MAAM,UAAU,IAAI,KAAK;CACzB,KAAK,QAAQ,MAAM,cAAc,KAAK,CAAC,EAAA,CAAG,WAAW,GAAG,OAAO,KAAA;CAE/D,MAAM,eAAe,WAAW,yBAAyB;CACzD,MAAM,WAAW,QAAQ,MACxB,IAAI,OAAO,gBAAgB,aAAa,qCAAqC,CAC9E;CAEA,IAAI,CAAC,UAAU,OAAO,KAAA;CAEtB,MAAM,GAAG,OAAO,QAAQ;CACxB,MAAM,iBAAiB,KAAK,QAAQ,QAAQ,IAAI,CAAC,CAAC,KAAK;
|
|
1
|
+
{"version":3,"file":"format-pem-block.cjs","names":[],"sources":["../src/format-pem-block.ts"],"sourcesContent":["const PEM_BODY_LINE_LENGTH = 64;\n\nfunction formatCompactPem(pem: string, isPublic: boolean): string | undefined {\n\tconst trimmed = pem.trim();\n\tif ((trimmed.match(/-----BEGIN /g) ?? []).length !== 1) return undefined;\n\n\tconst labelPattern = isPublic ? '[A-Z0-9 ]*PUBLIC KEY' : '[A-Z0-9 ]*PRIVATE KEY|CERTIFICATE';\n\tconst pemMatch = trimmed.match(\n\t\tnew RegExp(`^-----BEGIN (${labelPattern})-----([\\\\s\\\\S]*?)-----END \\\\1-----$`),\n\t);\n\n\tif (!pemMatch) return undefined;\n\n\tconst [, label, body] = pemMatch;\n\tconst normalizedBody = body.replace(/\\\\n/g, '\\n').trim();\n\tconst formattedBody = /\\s/.test(normalizedBody)\n\t\t? normalizedBody\n\t\t\t\t.replace(/:\\s+/g, ':')\n\t\t\t\t.replace(/\\s+/g, '\\n')\n\t\t\t\t// Restore RFC 1421 shape for a legacy encrypted key's headers. Both halves\n\t\t\t\t// are load-bearing: OpenSSL 3 matches no decoder without the blank line\n\t\t\t\t// before the body, and ssh2 reads the DEK-Info value at a fixed offset that\n\t\t\t\t// assumes a single space after the colon.\n\t\t\t\t.replace(\n\t\t\t\t\t/^(?:(?:Proc-Type|DEK-Info):\\S+\\n)+/,\n\t\t\t\t\t(headers) => `${headers.replace(/^(Proc-Type|DEK-Info):/gm, '$1: ')}\\n`,\n\t\t\t\t)\n\t\t: (normalizedBody.match(new RegExp(`.{1,${PEM_BODY_LINE_LENGTH}}`, 'g')) ?? []).join('\\n');\n\n\treturn `-----BEGIN ${label}-----\\n${formattedBody}\\n-----END ${label}-----`;\n}\n\n/**\n * Normalize a single PEM-encoded block (private key, public key, or certificate)\n * by collapsing whitespace and wrapping the body at 64 chars. Multi-block PEM\n * chains are returned unchanged.\n *\n * Input that is not a single-line PEM block is returned unchanged, so a plain\n * secret (e.g. a key passphrase) passed here by mistake is not corrupted.\n *\n * @param pem - The PEM-encoded block to format.\n * @param isPublic - When true, match `PUBLIC KEY` labels instead of the default `PRIVATE KEY` / `CERTIFICATE`.\n * @returns The formatted PEM block.\n */\nexport function formatPemBlock(pem: string, isPublic = false): string {\n\tlet regex = /(PRIVATE KEY|CERTIFICATE)/;\n\tif (isPublic) {\n\t\tregex = /(PUBLIC KEY)/;\n\t}\n\t// The fallback formatter below would collapse a non-PEM value's whitespace\n\t// into newlines, corrupting plain secrets such as key passphrases.\n\tif (!pem || /\\n/.test(pem) || !pem.includes('-----BEGIN ')) {\n\t\treturn pem;\n\t}\n\tconst compactPem = formatCompactPem(pem, isPublic);\n\tif (compactPem !== undefined) {\n\t\treturn compactPem;\n\t}\n\n\tlet formattedPem = '';\n\tconst parts = pem.split('-----').filter((item) => item !== '');\n\tparts.forEach((part) => {\n\t\tif (regex.test(part)) {\n\t\t\tformattedPem += `-----${part}-----`;\n\t\t} else {\n\t\t\tconst passRegex = /Proc-Type|DEK-Info/;\n\t\t\tif (passRegex.test(part)) {\n\t\t\t\tpart = part.replace(/:\\s+/g, ':');\n\t\t\t\tformattedPem += part.replace(/\\\\n/g, '\\n').replace(/\\s+/g, '\\n');\n\t\t\t} else {\n\t\t\t\tformattedPem += part.replace(/\\\\n/g, '\\n').replace(/\\s+/g, '\\n');\n\t\t\t}\n\t\t}\n\t});\n\treturn formattedPem;\n}\n"],"mappings":";;AAAA,MAAM,uBAAuB;AAE7B,SAAS,iBAAiB,KAAa,UAAuC;CAC7E,MAAM,UAAU,IAAI,KAAK;CACzB,KAAK,QAAQ,MAAM,cAAc,KAAK,CAAC,EAAA,CAAG,WAAW,GAAG,OAAO,KAAA;CAE/D,MAAM,eAAe,WAAW,yBAAyB;CACzD,MAAM,WAAW,QAAQ,MACxB,IAAI,OAAO,gBAAgB,aAAa,qCAAqC,CAC9E;CAEA,IAAI,CAAC,UAAU,OAAO,KAAA;CAEtB,MAAM,GAAG,OAAO,QAAQ;CACxB,MAAM,iBAAiB,KAAK,QAAQ,QAAQ,IAAI,CAAC,CAAC,KAAK;CAevD,OAAO,cAAc,MAAM,SAdL,KAAK,KAAK,cAAc,IAC3C,eACC,QAAQ,SAAS,GAAG,CAAC,CACrB,QAAQ,QAAQ,IAAI,CAAC,CAKrB,QACA,uCACC,YAAY,GAAG,QAAQ,QAAQ,4BAA4B,MAAM,EAAE,GACrE,KACC,eAAe,MAAM,IAAI,OAAO,OAAO,qBAAqB,IAAI,GAAG,CAAC,KAAK,CAAC,EAAA,CAAG,KAAK,IAAI,EAExC,aAAa,MAAM;AACtE;;;;;;;;;;;;;AAcA,SAAgB,eAAe,KAAa,WAAW,OAAe;CACrE,IAAI,QAAQ;CACZ,IAAI,UACH,QAAQ;CAIT,IAAI,CAAC,OAAO,KAAK,KAAK,GAAG,KAAK,CAAC,IAAI,SAAS,aAAa,GACxD,OAAO;CAER,MAAM,aAAa,iBAAiB,KAAK,QAAQ;CACjD,IAAI,eAAe,KAAA,GAClB,OAAO;CAGR,IAAI,eAAe;CAEnB,IADkB,MAAM,OAAO,CAAC,CAAC,QAAQ,SAAS,SAAS,EACvD,CAAC,CAAC,SAAS,SAAS;EACvB,IAAI,MAAM,KAAK,IAAI,GAClB,gBAAgB,QAAQ,KAAK;OAG7B,IAAI,qBAAU,KAAK,IAAI,GAAG;GACzB,OAAO,KAAK,QAAQ,SAAS,GAAG;GAChC,gBAAgB,KAAK,QAAQ,QAAQ,IAAI,CAAC,CAAC,QAAQ,QAAQ,IAAI;EAChE,OACC,gBAAgB,KAAK,QAAQ,QAAQ,IAAI,CAAC,CAAC,QAAQ,QAAQ,IAAI;CAGlE,CAAC;CACD,OAAO;AACR"}
|
|
@@ -8,13 +8,16 @@ function formatCompactPem(pem, isPublic) {
|
|
|
8
8
|
if (!pemMatch) return void 0;
|
|
9
9
|
const [, label, body] = pemMatch;
|
|
10
10
|
const normalizedBody = body.replace(/\\n/g, "\n").trim();
|
|
11
|
-
return `-----BEGIN ${label}-----\n${/\s/.test(normalizedBody) ? normalizedBody.replace(/:\s+/g, ":").replace(/\s+/g, "\n") : (normalizedBody.match(new RegExp(`.{1,${PEM_BODY_LINE_LENGTH}}`, "g")) ?? []).join("\n")}\n-----END ${label}-----`;
|
|
11
|
+
return `-----BEGIN ${label}-----\n${/\s/.test(normalizedBody) ? normalizedBody.replace(/:\s+/g, ":").replace(/\s+/g, "\n").replace(/^(?:(?:Proc-Type|DEK-Info):\S+\n)+/, (headers) => `${headers.replace(/^(Proc-Type|DEK-Info):/gm, "$1: ")}\n`) : (normalizedBody.match(new RegExp(`.{1,${PEM_BODY_LINE_LENGTH}}`, "g")) ?? []).join("\n")}\n-----END ${label}-----`;
|
|
12
12
|
}
|
|
13
13
|
/**
|
|
14
14
|
* Normalize a single PEM-encoded block (private key, public key, or certificate)
|
|
15
15
|
* by collapsing whitespace and wrapping the body at 64 chars. Multi-block PEM
|
|
16
16
|
* chains are returned unchanged.
|
|
17
17
|
*
|
|
18
|
+
* Input that is not a single-line PEM block is returned unchanged, so a plain
|
|
19
|
+
* secret (e.g. a key passphrase) passed here by mistake is not corrupted.
|
|
20
|
+
*
|
|
18
21
|
* @param pem - The PEM-encoded block to format.
|
|
19
22
|
* @param isPublic - When true, match `PUBLIC KEY` labels instead of the default `PRIVATE KEY` / `CERTIFICATE`.
|
|
20
23
|
* @returns The formatted PEM block.
|
|
@@ -22,7 +25,7 @@ function formatCompactPem(pem, isPublic) {
|
|
|
22
25
|
function formatPemBlock(pem, isPublic = false) {
|
|
23
26
|
let regex = /(PRIVATE KEY|CERTIFICATE)/;
|
|
24
27
|
if (isPublic) regex = /(PUBLIC KEY)/;
|
|
25
|
-
if (!pem || /\n/.test(pem)) return pem;
|
|
28
|
+
if (!pem || /\n/.test(pem) || !pem.includes("-----BEGIN ")) return pem;
|
|
26
29
|
const compactPem = formatCompactPem(pem, isPublic);
|
|
27
30
|
if (compactPem !== void 0) return compactPem;
|
|
28
31
|
let formattedPem = "";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format-pem-block.mjs","names":[],"sources":["../src/format-pem-block.ts"],"sourcesContent":["const PEM_BODY_LINE_LENGTH = 64;\n\nfunction formatCompactPem(pem: string, isPublic: boolean): string | undefined {\n\tconst trimmed = pem.trim();\n\tif ((trimmed.match(/-----BEGIN /g) ?? []).length !== 1) return undefined;\n\n\tconst labelPattern = isPublic ? '[A-Z0-9 ]*PUBLIC KEY' : '[A-Z0-9 ]*PRIVATE KEY|CERTIFICATE';\n\tconst pemMatch = trimmed.match(\n\t\tnew RegExp(`^-----BEGIN (${labelPattern})-----([\\\\s\\\\S]*?)-----END \\\\1-----$`),\n\t);\n\n\tif (!pemMatch) return undefined;\n\n\tconst [, label, body] = pemMatch;\n\tconst normalizedBody = body.replace(/\\\\n/g, '\\n').trim();\n\tconst formattedBody = /\\s/.test(normalizedBody)\n\t\t? normalizedBody.replace(/:\\s+/g, ':').replace(/\\s+/g, '\\n')\n\t\t: (normalizedBody.match(new RegExp(`.{1,${PEM_BODY_LINE_LENGTH}}`, 'g')) ?? []).join('\\n');\n\n\treturn `-----BEGIN ${label}-----\\n${formattedBody}\\n-----END ${label}-----`;\n}\n\n/**\n * Normalize a single PEM-encoded block (private key, public key, or certificate)\n * by collapsing whitespace and wrapping the body at 64 chars. Multi-block PEM\n * chains are returned unchanged.\n *\n * @param pem - The PEM-encoded block to format.\n * @param isPublic - When true, match `PUBLIC KEY` labels instead of the default `PRIVATE KEY` / `CERTIFICATE`.\n * @returns The formatted PEM block.\n */\nexport function formatPemBlock(pem: string, isPublic = false): string {\n\tlet regex = /(PRIVATE KEY|CERTIFICATE)/;\n\tif (isPublic) {\n\t\tregex = /(PUBLIC KEY)/;\n\t}\n\tif (!pem || /\\n/.test(pem)) {\n\t\treturn pem;\n\t}\n\tconst compactPem = formatCompactPem(pem, isPublic);\n\tif (compactPem !== undefined) {\n\t\treturn compactPem;\n\t}\n\n\tlet formattedPem = '';\n\tconst parts = pem.split('-----').filter((item) => item !== '');\n\tparts.forEach((part) => {\n\t\tif (regex.test(part)) {\n\t\t\tformattedPem += `-----${part}-----`;\n\t\t} else {\n\t\t\tconst passRegex = /Proc-Type|DEK-Info/;\n\t\t\tif (passRegex.test(part)) {\n\t\t\t\tpart = part.replace(/:\\s+/g, ':');\n\t\t\t\tformattedPem += part.replace(/\\\\n/g, '\\n').replace(/\\s+/g, '\\n');\n\t\t\t} else {\n\t\t\t\tformattedPem += part.replace(/\\\\n/g, '\\n').replace(/\\s+/g, '\\n');\n\t\t\t}\n\t\t}\n\t});\n\treturn formattedPem;\n}\n"],"mappings":";AAAA,MAAM,uBAAuB;AAE7B,SAAS,iBAAiB,KAAa,UAAuC;CAC7E,MAAM,UAAU,IAAI,KAAK;CACzB,KAAK,QAAQ,MAAM,cAAc,KAAK,CAAC,EAAA,CAAG,WAAW,GAAG,OAAO,KAAA;CAE/D,MAAM,eAAe,WAAW,yBAAyB;CACzD,MAAM,WAAW,QAAQ,MACxB,IAAI,OAAO,gBAAgB,aAAa,qCAAqC,CAC9E;CAEA,IAAI,CAAC,UAAU,OAAO,KAAA;CAEtB,MAAM,GAAG,OAAO,QAAQ;CACxB,MAAM,iBAAiB,KAAK,QAAQ,QAAQ,IAAI,CAAC,CAAC,KAAK;
|
|
1
|
+
{"version":3,"file":"format-pem-block.mjs","names":[],"sources":["../src/format-pem-block.ts"],"sourcesContent":["const PEM_BODY_LINE_LENGTH = 64;\n\nfunction formatCompactPem(pem: string, isPublic: boolean): string | undefined {\n\tconst trimmed = pem.trim();\n\tif ((trimmed.match(/-----BEGIN /g) ?? []).length !== 1) return undefined;\n\n\tconst labelPattern = isPublic ? '[A-Z0-9 ]*PUBLIC KEY' : '[A-Z0-9 ]*PRIVATE KEY|CERTIFICATE';\n\tconst pemMatch = trimmed.match(\n\t\tnew RegExp(`^-----BEGIN (${labelPattern})-----([\\\\s\\\\S]*?)-----END \\\\1-----$`),\n\t);\n\n\tif (!pemMatch) return undefined;\n\n\tconst [, label, body] = pemMatch;\n\tconst normalizedBody = body.replace(/\\\\n/g, '\\n').trim();\n\tconst formattedBody = /\\s/.test(normalizedBody)\n\t\t? normalizedBody\n\t\t\t\t.replace(/:\\s+/g, ':')\n\t\t\t\t.replace(/\\s+/g, '\\n')\n\t\t\t\t// Restore RFC 1421 shape for a legacy encrypted key's headers. Both halves\n\t\t\t\t// are load-bearing: OpenSSL 3 matches no decoder without the blank line\n\t\t\t\t// before the body, and ssh2 reads the DEK-Info value at a fixed offset that\n\t\t\t\t// assumes a single space after the colon.\n\t\t\t\t.replace(\n\t\t\t\t\t/^(?:(?:Proc-Type|DEK-Info):\\S+\\n)+/,\n\t\t\t\t\t(headers) => `${headers.replace(/^(Proc-Type|DEK-Info):/gm, '$1: ')}\\n`,\n\t\t\t\t)\n\t\t: (normalizedBody.match(new RegExp(`.{1,${PEM_BODY_LINE_LENGTH}}`, 'g')) ?? []).join('\\n');\n\n\treturn `-----BEGIN ${label}-----\\n${formattedBody}\\n-----END ${label}-----`;\n}\n\n/**\n * Normalize a single PEM-encoded block (private key, public key, or certificate)\n * by collapsing whitespace and wrapping the body at 64 chars. Multi-block PEM\n * chains are returned unchanged.\n *\n * Input that is not a single-line PEM block is returned unchanged, so a plain\n * secret (e.g. a key passphrase) passed here by mistake is not corrupted.\n *\n * @param pem - The PEM-encoded block to format.\n * @param isPublic - When true, match `PUBLIC KEY` labels instead of the default `PRIVATE KEY` / `CERTIFICATE`.\n * @returns The formatted PEM block.\n */\nexport function formatPemBlock(pem: string, isPublic = false): string {\n\tlet regex = /(PRIVATE KEY|CERTIFICATE)/;\n\tif (isPublic) {\n\t\tregex = /(PUBLIC KEY)/;\n\t}\n\t// The fallback formatter below would collapse a non-PEM value's whitespace\n\t// into newlines, corrupting plain secrets such as key passphrases.\n\tif (!pem || /\\n/.test(pem) || !pem.includes('-----BEGIN ')) {\n\t\treturn pem;\n\t}\n\tconst compactPem = formatCompactPem(pem, isPublic);\n\tif (compactPem !== undefined) {\n\t\treturn compactPem;\n\t}\n\n\tlet formattedPem = '';\n\tconst parts = pem.split('-----').filter((item) => item !== '');\n\tparts.forEach((part) => {\n\t\tif (regex.test(part)) {\n\t\t\tformattedPem += `-----${part}-----`;\n\t\t} else {\n\t\t\tconst passRegex = /Proc-Type|DEK-Info/;\n\t\t\tif (passRegex.test(part)) {\n\t\t\t\tpart = part.replace(/:\\s+/g, ':');\n\t\t\t\tformattedPem += part.replace(/\\\\n/g, '\\n').replace(/\\s+/g, '\\n');\n\t\t\t} else {\n\t\t\t\tformattedPem += part.replace(/\\\\n/g, '\\n').replace(/\\s+/g, '\\n');\n\t\t\t}\n\t\t}\n\t});\n\treturn formattedPem;\n}\n"],"mappings":";AAAA,MAAM,uBAAuB;AAE7B,SAAS,iBAAiB,KAAa,UAAuC;CAC7E,MAAM,UAAU,IAAI,KAAK;CACzB,KAAK,QAAQ,MAAM,cAAc,KAAK,CAAC,EAAA,CAAG,WAAW,GAAG,OAAO,KAAA;CAE/D,MAAM,eAAe,WAAW,yBAAyB;CACzD,MAAM,WAAW,QAAQ,MACxB,IAAI,OAAO,gBAAgB,aAAa,qCAAqC,CAC9E;CAEA,IAAI,CAAC,UAAU,OAAO,KAAA;CAEtB,MAAM,GAAG,OAAO,QAAQ;CACxB,MAAM,iBAAiB,KAAK,QAAQ,QAAQ,IAAI,CAAC,CAAC,KAAK;CAevD,OAAO,cAAc,MAAM,SAdL,KAAK,KAAK,cAAc,IAC3C,eACC,QAAQ,SAAS,GAAG,CAAC,CACrB,QAAQ,QAAQ,IAAI,CAAC,CAKrB,QACA,uCACC,YAAY,GAAG,QAAQ,QAAQ,4BAA4B,MAAM,EAAE,GACrE,KACC,eAAe,MAAM,IAAI,OAAO,OAAO,qBAAqB,IAAI,GAAG,CAAC,KAAK,CAAC,EAAA,CAAG,KAAK,IAAI,EAExC,aAAa,MAAM;AACtE;;;;;;;;;;;;;AAcA,SAAgB,eAAe,KAAa,WAAW,OAAe;CACrE,IAAI,QAAQ;CACZ,IAAI,UACH,QAAQ;CAIT,IAAI,CAAC,OAAO,KAAK,KAAK,GAAG,KAAK,CAAC,IAAI,SAAS,aAAa,GACxD,OAAO;CAER,MAAM,aAAa,iBAAiB,KAAK,QAAQ;CACjD,IAAI,eAAe,KAAA,GAClB,OAAO;CAGR,IAAI,eAAe;CAEnB,IADkB,MAAM,OAAO,CAAC,CAAC,QAAQ,SAAS,SAAS,EACvD,CAAC,CAAC,SAAS,SAAS;EACvB,IAAI,MAAM,KAAK,IAAI,GAClB,gBAAgB,QAAQ,KAAK;OAG7B,IAAI,qBAAU,KAAK,IAAI,GAAG;GACzB,OAAO,KAAK,QAAQ,SAAS,GAAG;GAChC,gBAAgB,KAAK,QAAQ,QAAQ,IAAI,CAAC,CAAC,QAAQ,QAAQ,IAAI;EAChE,OACC,gBAAgB,KAAK,QAAQ,QAAQ,IAAI,CAAC,CAAC,QAAQ,QAAQ,IAAI;CAGlE,CAAC;CACD,OAAO;AACR"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region src/number/bytes.ts
|
|
3
|
+
/**
|
|
4
|
+
* Convert bytes to megabytes (rounded to nearest integer)
|
|
5
|
+
*/
|
|
6
|
+
function toMb(sizeInBytes) {
|
|
7
|
+
return Math.round(sizeInBytes / (1024 * 1024));
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Format bytes to human-readable size with appropriate unit (B, KB, or MB)
|
|
11
|
+
*/
|
|
12
|
+
function formatBytes(sizeInBytes) {
|
|
13
|
+
if (sizeInBytes < 1024) return `${sizeInBytes}B`;
|
|
14
|
+
else if (sizeInBytes < 1024 * 1024) return `${Math.round(sizeInBytes / 1024)}KB`;
|
|
15
|
+
else return `${Math.round(sizeInBytes / (1024 * 1024))}MB`;
|
|
16
|
+
}
|
|
17
|
+
//#endregion
|
|
18
|
+
exports.formatBytes = formatBytes;
|
|
19
|
+
exports.toMb = toMb;
|
|
20
|
+
|
|
21
|
+
//# sourceMappingURL=bytes.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bytes.cjs","names":[],"sources":["../../src/number/bytes.ts"],"sourcesContent":["/**\n * Convert bytes to megabytes (rounded to nearest integer)\n */\nexport function toMb(sizeInBytes: number): number {\n\treturn Math.round(sizeInBytes / (1024 * 1024));\n}\n\n/**\n * Format bytes to human-readable size with appropriate unit (B, KB, or MB)\n */\nexport function formatBytes(sizeInBytes: number): string {\n\tif (sizeInBytes < 1024) {\n\t\treturn `${sizeInBytes}B`;\n\t} else if (sizeInBytes < 1024 * 1024) {\n\t\treturn `${Math.round(sizeInBytes / 1024)}KB`;\n\t} else {\n\t\treturn `${Math.round(sizeInBytes / (1024 * 1024))}MB`;\n\t}\n}\n"],"mappings":";;;;;AAGA,SAAgB,KAAK,aAA6B;CACjD,OAAO,KAAK,MAAM,eAAe,OAAO,KAAK;AAC9C;;;;AAKA,SAAgB,YAAY,aAA6B;CACxD,IAAI,cAAc,MACjB,OAAO,GAAG,YAAY;MAChB,IAAI,cAAc,OAAO,MAC/B,OAAO,GAAG,KAAK,MAAM,cAAc,IAAI,EAAE;MAEzC,OAAO,GAAG,KAAK,MAAM,eAAe,OAAO,KAAK,EAAE;AAEpD"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//#region src/number/bytes.ts
|
|
2
|
+
/**
|
|
3
|
+
* Convert bytes to megabytes (rounded to nearest integer)
|
|
4
|
+
*/
|
|
5
|
+
function toMb(sizeInBytes) {
|
|
6
|
+
return Math.round(sizeInBytes / (1024 * 1024));
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Format bytes to human-readable size with appropriate unit (B, KB, or MB)
|
|
10
|
+
*/
|
|
11
|
+
function formatBytes(sizeInBytes) {
|
|
12
|
+
if (sizeInBytes < 1024) return `${sizeInBytes}B`;
|
|
13
|
+
else if (sizeInBytes < 1024 * 1024) return `${Math.round(sizeInBytes / 1024)}KB`;
|
|
14
|
+
else return `${Math.round(sizeInBytes / (1024 * 1024))}MB`;
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
17
|
+
export { formatBytes, toMb };
|
|
18
|
+
|
|
19
|
+
//# sourceMappingURL=bytes.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bytes.mjs","names":[],"sources":["../../src/number/bytes.ts"],"sourcesContent":["/**\n * Convert bytes to megabytes (rounded to nearest integer)\n */\nexport function toMb(sizeInBytes: number): number {\n\treturn Math.round(sizeInBytes / (1024 * 1024));\n}\n\n/**\n * Format bytes to human-readable size with appropriate unit (B, KB, or MB)\n */\nexport function formatBytes(sizeInBytes: number): string {\n\tif (sizeInBytes < 1024) {\n\t\treturn `${sizeInBytes}B`;\n\t} else if (sizeInBytes < 1024 * 1024) {\n\t\treturn `${Math.round(sizeInBytes / 1024)}KB`;\n\t} else {\n\t\treturn `${Math.round(sizeInBytes / (1024 * 1024))}MB`;\n\t}\n}\n"],"mappings":";;;;AAGA,SAAgB,KAAK,aAA6B;CACjD,OAAO,KAAK,MAAM,eAAe,OAAO,KAAK;AAC9C;;;;AAKA,SAAgB,YAAY,aAA6B;CACxD,IAAI,cAAc,MACjB,OAAO,GAAG,YAAY;MAChB,IAAI,cAAc,OAAO,MAC/B,OAAO,GAAG,KAAK,MAAM,cAAc,IAAI,EAAE;MAEzC,OAAO,GAAG,KAAK,MAAM,eAAe,OAAO,KAAK,EAAE;AAEpD"}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_scrub_secrets = require("../scrub-secrets.cjs");
|
|
3
|
+
//#region src/redaction/pii-patterns.ts
|
|
4
|
+
/** Compile a global regex once, adding the `g` flag if the source omits it. */
|
|
5
|
+
function globalRegex(source, flags = "") {
|
|
6
|
+
return new RegExp(source, flags.includes("g") ? flags : `${flags}g`);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Secret/credential patterns, sourced from {@link SECRET_VALUE_PATTERNS} so
|
|
10
|
+
* there is a single place that defines what a credential looks like.
|
|
11
|
+
*/
|
|
12
|
+
const SECRET_PATTERNS = require_scrub_secrets.SECRET_VALUE_PATTERNS.map((re) => ({
|
|
13
|
+
category: "secret",
|
|
14
|
+
regex: globalRegex(re.source, re.flags)
|
|
15
|
+
}));
|
|
16
|
+
/** Luhn checksum — used to keep credit-card redaction from firing on any long digit run. */
|
|
17
|
+
function passesLuhn(candidate) {
|
|
18
|
+
const digits = candidate.replace(/\D/g, "");
|
|
19
|
+
if (digits.length < 13 || digits.length > 19) return false;
|
|
20
|
+
let sum = 0;
|
|
21
|
+
let double = false;
|
|
22
|
+
for (let i = digits.length - 1; i >= 0; i--) {
|
|
23
|
+
let digit = digits.charCodeAt(i) - 48;
|
|
24
|
+
if (double) {
|
|
25
|
+
digit *= 2;
|
|
26
|
+
if (digit > 9) digit -= 9;
|
|
27
|
+
}
|
|
28
|
+
sum += digit;
|
|
29
|
+
double = !double;
|
|
30
|
+
}
|
|
31
|
+
return sum % 10 === 0;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Confidence gate for phone candidates, encoding the **E.164** standard: a
|
|
35
|
+
* leading `+`, a non-zero country code, and 7–15 digits total. Runs on the
|
|
36
|
+
* digit/`+`-only normalized form (separators stripped).
|
|
37
|
+
*/
|
|
38
|
+
function passesE164(candidate) {
|
|
39
|
+
return /^\+[1-9]\d{6,14}$/.test(candidate.replace(/[^\d+]/g, ""));
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* IBAN mod-97 checksum (ISO 13616): drop spaces, move the first 4 chars to the
|
|
43
|
+
* end, map letters A–Z → 10–35, and confirm the big-integer value mod 97 === 1.
|
|
44
|
+
*/
|
|
45
|
+
function passesIbanChecksum(candidate) {
|
|
46
|
+
const compact = candidate.replace(/\s/g, "").toUpperCase();
|
|
47
|
+
if (!/^[A-Z]{2}\d{2}[A-Z0-9]{11,30}$/.test(compact)) return false;
|
|
48
|
+
const rearranged = compact.slice(4) + compact.slice(0, 4);
|
|
49
|
+
let remainder = 0;
|
|
50
|
+
for (let i = 0; i < rearranged.length; i++) {
|
|
51
|
+
const code = rearranged.charCodeAt(i);
|
|
52
|
+
const value = code >= 65 ? code - 55 : code - 48;
|
|
53
|
+
remainder = value > 9 ? (remainder * 100 + value) % 97 : (remainder * 10 + value) % 97;
|
|
54
|
+
}
|
|
55
|
+
return remainder === 1;
|
|
56
|
+
}
|
|
57
|
+
const BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
58
|
+
function base58Decode(input) {
|
|
59
|
+
const bytes = [];
|
|
60
|
+
for (let i = 0; i < input.length; i++) {
|
|
61
|
+
let carry = BASE58_ALPHABET.indexOf(input[i]);
|
|
62
|
+
if (carry === -1) return void 0;
|
|
63
|
+
for (let j = 0; j < bytes.length; j++) {
|
|
64
|
+
carry += bytes[j] * 58;
|
|
65
|
+
bytes[j] = carry & 255;
|
|
66
|
+
carry >>= 8;
|
|
67
|
+
}
|
|
68
|
+
while (carry > 0) {
|
|
69
|
+
bytes.push(carry & 255);
|
|
70
|
+
carry >>= 8;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
for (let i = 0; i < input.length && input[i] === "1"; i++) bytes.push(0);
|
|
74
|
+
return Uint8Array.from(bytes.reverse());
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Ethereum (`0x`+40 hex) or Bitcoin bech32 (`bc1`/`tb1`) — both distinctive
|
|
78
|
+
* enough to accept on shape alone.
|
|
79
|
+
*/
|
|
80
|
+
function isDistinctiveWalletShape(match) {
|
|
81
|
+
if (/^0x[0-9a-fA-F]{40}$/.test(match)) return true;
|
|
82
|
+
return /^(?:bc1|tb1)[023456789acdefghjklmnpqrstuvwxyz]{11,71}$/.test(match);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Default legacy-address gate: a Base58Check payload decodes to exactly 25
|
|
86
|
+
* bytes (1 version + 20 hash + 4 checksum). Verifying the checksum itself needs
|
|
87
|
+
* SHA-256, which has no synchronous cross-platform primitive — Node callers
|
|
88
|
+
* inject the stricter check via `createPiiPatterns`. Erring toward redaction is
|
|
89
|
+
* the safe direction: an unvalidated Base58 blob of that length is far more
|
|
90
|
+
* likely to be a credential than prose.
|
|
91
|
+
*/
|
|
92
|
+
function isLegacyWalletShape(match) {
|
|
93
|
+
return base58Decode(match)?.length === 25;
|
|
94
|
+
}
|
|
95
|
+
/** Ethereum, Bitcoin bech32, or a legacy Base58 address of plausible length. */
|
|
96
|
+
function isCryptoWalletShape(match) {
|
|
97
|
+
return isDistinctiveWalletShape(match) || isLegacyWalletShape(match);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Conservative, high-confidence PII patterns. Phone detection is best-effort:
|
|
101
|
+
* only well-structured (E.164) formats are matched. New {@link PiiDetectionType}
|
|
102
|
+
* categories slot in here; a category may map to `undefined` to declare it
|
|
103
|
+
* before a pattern exists, in which case it is excluded from detection.
|
|
104
|
+
*
|
|
105
|
+
* `overrides` swaps individual entries — used by `@n8n/agents` to layer its
|
|
106
|
+
* Node-only Base58Check validator onto `crypto-wallet`.
|
|
107
|
+
*/
|
|
108
|
+
function createPiiPatterns(overrides = {}) {
|
|
109
|
+
return {
|
|
110
|
+
email: {
|
|
111
|
+
category: "email",
|
|
112
|
+
regex: /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g
|
|
113
|
+
},
|
|
114
|
+
"credit-card": {
|
|
115
|
+
category: "credit-card",
|
|
116
|
+
regex: /\b\d(?:[ -]?\d){12,18}\b/g,
|
|
117
|
+
validate: passesLuhn
|
|
118
|
+
},
|
|
119
|
+
"ssn-us": {
|
|
120
|
+
category: "ssn-us",
|
|
121
|
+
regex: /\b\d{3}-\d{2}-\d{4}\b/g
|
|
122
|
+
},
|
|
123
|
+
phone: {
|
|
124
|
+
category: "phone",
|
|
125
|
+
regex: /\+\d(?:[\s().-]*\d){6,14}\b/g,
|
|
126
|
+
validate: passesE164
|
|
127
|
+
},
|
|
128
|
+
iban: {
|
|
129
|
+
category: "iban",
|
|
130
|
+
regex: /\b[A-Za-z]{2}\d{2}[A-Za-z0-9]{11,30}\b|\b[A-Z]{2}\d{2}(?: [A-Z0-9]{1,4}){2,8}\b/g,
|
|
131
|
+
validate: passesIbanChecksum
|
|
132
|
+
},
|
|
133
|
+
"crypto-wallet": {
|
|
134
|
+
category: "crypto-wallet",
|
|
135
|
+
regex: /\b(?:0x[0-9a-fA-F]{40}|(?:bc1|tb1)[023456789acdefghjklmnpqrstuvwxyz]{11,71}|[13][1-9A-HJ-NP-Za-km-z]{25,34})\b/g,
|
|
136
|
+
validate: isCryptoWalletShape
|
|
137
|
+
},
|
|
138
|
+
mac: {
|
|
139
|
+
category: "mac",
|
|
140
|
+
regex: /\b(?:[0-9A-Fa-f]{2}[:-]){5}[0-9A-Fa-f]{2}\b/g
|
|
141
|
+
},
|
|
142
|
+
ip: {
|
|
143
|
+
category: "ip",
|
|
144
|
+
regex: /\b(?:\d{1,3}\.){3}\d{1,3}\b|\b(?:[A-Fa-f0-9]{1,4}:){2,7}[A-Fa-f0-9]{1,4}\b|\b(?:[A-Fa-f0-9]{1,4}:){1,7}:(?:[A-Fa-f0-9]{1,4})?\b/g,
|
|
145
|
+
validate: isIpAddress
|
|
146
|
+
},
|
|
147
|
+
url: {
|
|
148
|
+
category: "url",
|
|
149
|
+
regex: /\bhttps?:\/\/[^\s<>"')\]}]+/g
|
|
150
|
+
},
|
|
151
|
+
...overrides
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
/** IPv4 with octets ≤ 255, or a colon-delimited IPv6 (shape already constrained by the regex). */
|
|
155
|
+
function isIpAddress(match) {
|
|
156
|
+
if (match.includes(":")) return true;
|
|
157
|
+
const octets = match.split(".");
|
|
158
|
+
return octets.length === 4 && octets.every((o) => /^\d{1,3}$/.test(o) && Number(o) <= 255);
|
|
159
|
+
}
|
|
160
|
+
/** Browser-safe default table. Node callers layer stricter validators on top. */
|
|
161
|
+
const PII_PATTERNS = createPiiPatterns();
|
|
162
|
+
/**
|
|
163
|
+
* PII categories that actually have a detection pattern today — the source of
|
|
164
|
+
* truth for what redaction can detect. Any {@link PiiDetectionType} mapped to
|
|
165
|
+
* `undefined` in the table (declared but not yet implemented) is excluded here.
|
|
166
|
+
*/
|
|
167
|
+
const SUPPORTED_PII_CATEGORIES = Object.keys(PII_PATTERNS).filter((type) => PII_PATTERNS[type] !== void 0);
|
|
168
|
+
/** Resolve the active pattern set for the given options. */
|
|
169
|
+
function resolvePatterns(opts, piiPatterns = PII_PATTERNS) {
|
|
170
|
+
const patterns = [];
|
|
171
|
+
if (opts.secrets) patterns.push(...SECRET_PATTERNS);
|
|
172
|
+
for (const type of opts.detect) {
|
|
173
|
+
const pattern = piiPatterns[type];
|
|
174
|
+
if (pattern) patterns.push(pattern);
|
|
175
|
+
}
|
|
176
|
+
return patterns;
|
|
177
|
+
}
|
|
178
|
+
//#endregion
|
|
179
|
+
exports.PII_PATTERNS = PII_PATTERNS;
|
|
180
|
+
exports.SUPPORTED_PII_CATEGORIES = SUPPORTED_PII_CATEGORIES;
|
|
181
|
+
exports.base58Decode = base58Decode;
|
|
182
|
+
exports.createPiiPatterns = createPiiPatterns;
|
|
183
|
+
exports.isCryptoWalletShape = isCryptoWalletShape;
|
|
184
|
+
exports.passesIbanChecksum = passesIbanChecksum;
|
|
185
|
+
exports.passesLuhn = passesLuhn;
|
|
186
|
+
exports.resolvePatterns = resolvePatterns;
|
|
187
|
+
|
|
188
|
+
//# sourceMappingURL=pii-patterns.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pii-patterns.cjs","names":["SECRET_VALUE_PATTERNS"],"sources":["../../src/redaction/pii-patterns.ts"],"sourcesContent":["import { SECRET_VALUE_PATTERNS } from '../scrub-secrets';\n\n/**\n * PII categories the detection vocabulary knows about. A category may be\n * declared here before a pattern exists for it — see {@link PII_PATTERNS}.\n */\nexport type PiiDetectionType =\n\t| 'email'\n\t| 'phone'\n\t| 'credit-card'\n\t| 'ssn-us'\n\t| 'iban'\n\t| 'crypto-wallet'\n\t| 'ip'\n\t| 'mac'\n\t| 'url';\n\n/**\n * A category attached to every redaction match so callers can log *what kind*\n * of sensitive content was removed without ever handling the value itself.\n * `'secret'` covers credential/token patterns; the rest mirror\n * {@link PiiDetectionType}.\n */\nexport type RedactionCategory = 'secret' | PiiDetectionType;\n\nexport interface RedactionPattern {\n\treadonly category: RedactionCategory;\n\t/**\n\t * Precompiled regex matching the sensitive value. Always global — the\n\t * redactor relies on `g` both for replace-all and for the `exec` scan loop.\n\t * Compiled once at module load; callers reset `lastIndex` before reuse.\n\t */\n\treadonly regex: RegExp;\n\t/**\n\t * Optional gate: a candidate match is only redacted when this returns\n\t * `true`. Used to suppress false positives (e.g. Luhn check for cards).\n\t */\n\treadonly validate?: (match: string) => boolean;\n}\n\nexport type PiiPatternTable = Readonly<Record<PiiDetectionType, RedactionPattern | undefined>>;\n\n/** Compile a global regex once, adding the `g` flag if the source omits it. */\nfunction globalRegex(source: string, flags = ''): RegExp {\n\treturn new RegExp(source, flags.includes('g') ? flags : `${flags}g`);\n}\n\n/**\n * Secret/credential patterns, sourced from {@link SECRET_VALUE_PATTERNS} so\n * there is a single place that defines what a credential looks like.\n */\nconst SECRET_PATTERNS: readonly RedactionPattern[] = SECRET_VALUE_PATTERNS.map((re) => ({\n\tcategory: 'secret',\n\tregex: globalRegex(re.source, re.flags),\n}));\n\n/** Luhn checksum — used to keep credit-card redaction from firing on any long digit run. */\nexport function passesLuhn(candidate: string): boolean {\n\tconst digits = candidate.replace(/\\D/g, '');\n\tif (digits.length < 13 || digits.length > 19) return false;\n\n\tlet sum = 0;\n\tlet double = false;\n\tfor (let i = digits.length - 1; i >= 0; i--) {\n\t\tlet digit = digits.charCodeAt(i) - 48;\n\t\tif (double) {\n\t\t\tdigit *= 2;\n\t\t\tif (digit > 9) digit -= 9;\n\t\t}\n\t\tsum += digit;\n\t\tdouble = !double;\n\t}\n\treturn sum % 10 === 0;\n}\n\n/**\n * Confidence gate for phone candidates, encoding the **E.164** standard: a\n * leading `+`, a non-zero country code, and 7–15 digits total. Runs on the\n * digit/`+`-only normalized form (separators stripped).\n */\nfunction passesE164(candidate: string): boolean {\n\treturn /^\\+[1-9]\\d{6,14}$/.test(candidate.replace(/[^\\d+]/g, ''));\n}\n\n/**\n * IBAN mod-97 checksum (ISO 13616): drop spaces, move the first 4 chars to the\n * end, map letters A–Z → 10–35, and confirm the big-integer value mod 97 === 1.\n */\nexport function passesIbanChecksum(candidate: string): boolean {\n\tconst compact = candidate.replace(/\\s/g, '').toUpperCase();\n\tif (!/^[A-Z]{2}\\d{2}[A-Z0-9]{11,30}$/.test(compact)) return false;\n\n\tconst rearranged = compact.slice(4) + compact.slice(0, 4);\n\tlet remainder = 0;\n\tfor (let i = 0; i < rearranged.length; i++) {\n\t\tconst code = rearranged.charCodeAt(i);\n\t\tconst value = code >= 65 ? code - 55 : code - 48; // 'A'→10 … 'Z'→35, '0'→0 … '9'→9\n\t\tremainder = value > 9 ? (remainder * 100 + value) % 97 : (remainder * 10 + value) % 97;\n\t}\n\treturn remainder === 1;\n}\n\nconst BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';\n\nexport function base58Decode(input: string): Uint8Array | undefined {\n\tconst bytes: number[] = [];\n\tfor (let i = 0; i < input.length; i++) {\n\t\tlet carry = BASE58_ALPHABET.indexOf(input[i]);\n\t\tif (carry === -1) return undefined;\n\t\tfor (let j = 0; j < bytes.length; j++) {\n\t\t\tcarry += bytes[j] * 58;\n\t\t\tbytes[j] = carry & 0xff;\n\t\t\tcarry >>= 8;\n\t\t}\n\t\twhile (carry > 0) {\n\t\t\tbytes.push(carry & 0xff);\n\t\t\tcarry >>= 8;\n\t\t}\n\t}\n\tfor (let i = 0; i < input.length && input[i] === '1'; i++) bytes.push(0);\n\treturn Uint8Array.from(bytes.reverse());\n}\n\n/**\n * Ethereum (`0x`+40 hex) or Bitcoin bech32 (`bc1`/`tb1`) — both distinctive\n * enough to accept on shape alone.\n */\nfunction isDistinctiveWalletShape(match: string): boolean {\n\tif (/^0x[0-9a-fA-F]{40}$/.test(match)) return true;\n\treturn /^(?:bc1|tb1)[023456789acdefghjklmnpqrstuvwxyz]{11,71}$/.test(match);\n}\n\n/**\n * Default legacy-address gate: a Base58Check payload decodes to exactly 25\n * bytes (1 version + 20 hash + 4 checksum). Verifying the checksum itself needs\n * SHA-256, which has no synchronous cross-platform primitive — Node callers\n * inject the stricter check via `createPiiPatterns`. Erring toward redaction is\n * the safe direction: an unvalidated Base58 blob of that length is far more\n * likely to be a credential than prose.\n */\nfunction isLegacyWalletShape(match: string): boolean {\n\treturn base58Decode(match)?.length === 25;\n}\n\n/** Ethereum, Bitcoin bech32, or a legacy Base58 address of plausible length. */\nexport function isCryptoWalletShape(match: string): boolean {\n\treturn isDistinctiveWalletShape(match) || isLegacyWalletShape(match);\n}\n\n/**\n * Conservative, high-confidence PII patterns. Phone detection is best-effort:\n * only well-structured (E.164) formats are matched. New {@link PiiDetectionType}\n * categories slot in here; a category may map to `undefined` to declare it\n * before a pattern exists, in which case it is excluded from detection.\n *\n * `overrides` swaps individual entries — used by `@n8n/agents` to layer its\n * Node-only Base58Check validator onto `crypto-wallet`.\n */\nexport function createPiiPatterns(\n\toverrides: Partial<Record<PiiDetectionType, RedactionPattern>> = {},\n): PiiPatternTable {\n\t/* eslint-disable @typescript-eslint/naming-convention -- category ids are the\n\t public `PiiDetectionType` vocabulary, which is kebab-case */\n\treturn {\n\t\temail: {\n\t\t\tcategory: 'email',\n\t\t\tregex: /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}/g,\n\t\t},\n\t\t'credit-card': {\n\t\t\tcategory: 'credit-card',\n\t\t\t// 13-19 digits, optionally grouped by single spaces or dashes.\n\t\t\tregex: /\\b\\d(?:[ -]?\\d){12,18}\\b/g,\n\t\t\tvalidate: passesLuhn,\n\t\t},\n\t\t'ssn-us': {\n\t\t\tcategory: 'ssn-us',\n\t\t\t// US Social Security Number, dashed form only (123-45-6789). Bare 9-digit\n\t\t\t// runs are intentionally not matched (too false-positive-prone). Per-country\n\t\t\t// national IDs each get their own `ssn-<cc>` category (e.g. a future `ssn-uk`).\n\t\t\tregex: /\\b\\d{3}-\\d{2}-\\d{4}\\b/g,\n\t\t},\n\t\tphone: {\n\t\t\tcategory: 'phone',\n\t\t\t// Best-effort, E.164 only: a leading `+` then 7–15 digits, tolerating\n\t\t\t// the spaces/parens/dots/dashes people write between groups\n\t\t\t// (e.g. `+1 (555) 123-4567`). Requiring the `+` keeps false positives\n\t\t\t// low — bare digit runs (IDs, dates, NANP without `+`) are not matched.\n\t\t\tregex: /\\+\\d(?:[\\s().-]*\\d){6,14}\\b/g,\n\t\t\tvalidate: passesE164,\n\t\t},\n\t\tiban: {\n\t\t\tcategory: 'iban',\n\t\t\t// Two forms: the compact (un-spaced) IBAN is matched case-insensitively so\n\t\t\t// lower/mixed-case IBANs are caught — with no internal spaces it can't bleed\n\t\t\t// into a following word. The spaced, group-of-4 form is matched upper-case\n\t\t\t// only: spaced IBANs are written upper-case by convention, and that keeps the\n\t\t\t// greedy body from swallowing following lower-case prose (which would fail the\n\t\t\t// checksum and suppress redaction, since the engine doesn't retry sub-matches).\n\t\t\t// `passesIbanChecksum` upper-cases, strips spaces, and verifies mod-97.\n\t\t\tregex: /\\b[A-Za-z]{2}\\d{2}[A-Za-z0-9]{11,30}\\b|\\b[A-Z]{2}\\d{2}(?: [A-Z0-9]{1,4}){2,8}\\b/g,\n\t\t\tvalidate: passesIbanChecksum,\n\t\t},\n\t\t'crypto-wallet': {\n\t\t\tcategory: 'crypto-wallet',\n\t\t\t// Ethereum `0x…40hex`, Bitcoin bech32 `bc1…`/`tb1…`, or Bitcoin Base58Check.\n\t\t\tregex:\n\t\t\t\t/\\b(?:0x[0-9a-fA-F]{40}|(?:bc1|tb1)[023456789acdefghjklmnpqrstuvwxyz]{11,71}|[13][1-9A-HJ-NP-Za-km-z]{25,34})\\b/g,\n\t\t\tvalidate: isCryptoWalletShape,\n\t\t},\n\t\t// `mac` is declared before `ip`: a MAC is colon-delimited hex and would also\n\t\t// match the IPv6 branch, so matching it as `mac` first keeps the category right.\n\t\tmac: {\n\t\t\tcategory: 'mac',\n\t\t\tregex: /\\b(?:[0-9A-Fa-f]{2}[:-]){5}[0-9A-Fa-f]{2}\\b/g,\n\t\t},\n\t\tip: {\n\t\t\tcategory: 'ip',\n\t\t\t// IPv4 (octets validated) or IPv6 (full and `::`-compressed forms).\n\t\t\tregex:\n\t\t\t\t/\\b(?:\\d{1,3}\\.){3}\\d{1,3}\\b|\\b(?:[A-Fa-f0-9]{1,4}:){2,7}[A-Fa-f0-9]{1,4}\\b|\\b(?:[A-Fa-f0-9]{1,4}:){1,7}:(?:[A-Fa-f0-9]{1,4})?\\b/g,\n\t\t\tvalidate: isIpAddress,\n\t\t},\n\t\turl: {\n\t\t\tcategory: 'url',\n\t\t\t// Whole http(s) URL. Stops at whitespace and common trailing delimiters.\n\t\t\tregex: /\\bhttps?:\\/\\/[^\\s<>\"')\\]}]+/g,\n\t\t},\n\t\t...overrides,\n\t};\n\t/* eslint-enable @typescript-eslint/naming-convention */\n}\n\n/** IPv4 with octets ≤ 255, or a colon-delimited IPv6 (shape already constrained by the regex). */\nfunction isIpAddress(match: string): boolean {\n\tif (match.includes(':')) return true;\n\tconst octets = match.split('.');\n\treturn octets.length === 4 && octets.every((o) => /^\\d{1,3}$/.test(o) && Number(o) <= 255);\n}\n\n/** Browser-safe default table. Node callers layer stricter validators on top. */\nexport const PII_PATTERNS = createPiiPatterns();\n\n/**\n * PII categories that actually have a detection pattern today — the source of\n * truth for what redaction can detect. Any {@link PiiDetectionType} mapped to\n * `undefined` in the table (declared but not yet implemented) is excluded here.\n */\nexport const SUPPORTED_PII_CATEGORIES: PiiDetectionType[] = (\n\tObject.keys(PII_PATTERNS) as PiiDetectionType[]\n).filter((type) => PII_PATTERNS[type] !== undefined);\n\n/** Resolve the active pattern set for the given options. */\nexport function resolvePatterns(\n\topts: {\n\t\tsecrets: boolean;\n\t\tdetect: readonly PiiDetectionType[];\n\t},\n\tpiiPatterns: PiiPatternTable = PII_PATTERNS,\n): RedactionPattern[] {\n\tconst patterns: RedactionPattern[] = [];\n\tif (opts.secrets) patterns.push(...SECRET_PATTERNS);\n\tfor (const type of opts.detect) {\n\t\tconst pattern = piiPatterns[type];\n\t\tif (pattern) patterns.push(pattern);\n\t}\n\treturn patterns;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//#region src/redaction/pii-patterns.d.ts
|
|
2
|
+
type PiiDetectionType = 'email' | 'phone' | 'credit-card' | 'ssn-us' | 'iban' | 'crypto-wallet' | 'ip' | 'mac' | 'url';
|
|
3
|
+
type RedactionCategory = 'secret' | PiiDetectionType;
|
|
4
|
+
interface RedactionPattern {
|
|
5
|
+
readonly category: RedactionCategory;
|
|
6
|
+
readonly regex: RegExp;
|
|
7
|
+
readonly validate?: (match: string) => boolean;
|
|
8
|
+
}
|
|
9
|
+
type PiiPatternTable = Readonly<Record<PiiDetectionType, RedactionPattern | undefined>>;
|
|
10
|
+
declare function passesLuhn(candidate: string): boolean;
|
|
11
|
+
declare function passesIbanChecksum(candidate: string): boolean;
|
|
12
|
+
declare function base58Decode(input: string): Uint8Array | undefined;
|
|
13
|
+
declare function isCryptoWalletShape(match: string): boolean;
|
|
14
|
+
declare function createPiiPatterns(overrides?: Partial<Record<PiiDetectionType, RedactionPattern>>): PiiPatternTable;
|
|
15
|
+
declare const PII_PATTERNS: Readonly<Record<PiiDetectionType, RedactionPattern | undefined>>;
|
|
16
|
+
declare const SUPPORTED_PII_CATEGORIES: PiiDetectionType[];
|
|
17
|
+
declare function resolvePatterns(opts: {
|
|
18
|
+
secrets: boolean;
|
|
19
|
+
detect: readonly PiiDetectionType[];
|
|
20
|
+
}, piiPatterns?: PiiPatternTable): RedactionPattern[];
|
|
21
|
+
//#endregion
|
|
22
|
+
export { PII_PATTERNS, PiiDetectionType, PiiPatternTable, RedactionCategory, RedactionPattern, SUPPORTED_PII_CATEGORIES, base58Decode, createPiiPatterns, isCryptoWalletShape, passesIbanChecksum, passesLuhn, resolvePatterns };
|
|
23
|
+
//# sourceMappingURL=pii-patterns.d.cts.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//#region src/redaction/pii-patterns.d.ts
|
|
2
|
+
type PiiDetectionType = 'email' | 'phone' | 'credit-card' | 'ssn-us' | 'iban' | 'crypto-wallet' | 'ip' | 'mac' | 'url';
|
|
3
|
+
type RedactionCategory = 'secret' | PiiDetectionType;
|
|
4
|
+
interface RedactionPattern {
|
|
5
|
+
readonly category: RedactionCategory;
|
|
6
|
+
readonly regex: RegExp;
|
|
7
|
+
readonly validate?: (match: string) => boolean;
|
|
8
|
+
}
|
|
9
|
+
type PiiPatternTable = Readonly<Record<PiiDetectionType, RedactionPattern | undefined>>;
|
|
10
|
+
declare function passesLuhn(candidate: string): boolean;
|
|
11
|
+
declare function passesIbanChecksum(candidate: string): boolean;
|
|
12
|
+
declare function base58Decode(input: string): Uint8Array | undefined;
|
|
13
|
+
declare function isCryptoWalletShape(match: string): boolean;
|
|
14
|
+
declare function createPiiPatterns(overrides?: Partial<Record<PiiDetectionType, RedactionPattern>>): PiiPatternTable;
|
|
15
|
+
declare const PII_PATTERNS: Readonly<Record<PiiDetectionType, RedactionPattern | undefined>>;
|
|
16
|
+
declare const SUPPORTED_PII_CATEGORIES: PiiDetectionType[];
|
|
17
|
+
declare function resolvePatterns(opts: {
|
|
18
|
+
secrets: boolean;
|
|
19
|
+
detect: readonly PiiDetectionType[];
|
|
20
|
+
}, piiPatterns?: PiiPatternTable): RedactionPattern[];
|
|
21
|
+
//#endregion
|
|
22
|
+
export { PII_PATTERNS, PiiDetectionType, PiiPatternTable, RedactionCategory, RedactionPattern, SUPPORTED_PII_CATEGORIES, base58Decode, createPiiPatterns, isCryptoWalletShape, passesIbanChecksum, passesLuhn, resolvePatterns };
|
|
23
|
+
//# sourceMappingURL=pii-patterns.d.mts.map
|