@socprime/logtotal-sanitizer 0.0.1-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/LICENSE +201 -0
- package/README.md +199 -0
- package/chunk-TAMK67JP.js +2205 -0
- package/chunk-TAMK67JP.js.map +1 -0
- package/chunk-U6SJR7S7.js +933 -0
- package/chunk-U6SJR7S7.js.map +1 -0
- package/chunk-VW3VVW6L.cjs +2218 -0
- package/chunk-VW3VVW6L.cjs.map +1 -0
- package/chunk-WISLDJT3.cjs +944 -0
- package/chunk-WISLDJT3.cjs.map +1 -0
- package/cli.js +3431 -0
- package/cli.js.map +1 -0
- package/index-CutSH_48.d.cts +409 -0
- package/index-CutSH_48.d.ts +409 -0
- package/index.cjs +93 -0
- package/index.cjs.map +1 -0
- package/index.d.cts +157 -0
- package/index.d.ts +157 -0
- package/index.js +4 -0
- package/index.js.map +1 -0
- package/node.cjs +147 -0
- package/node.cjs.map +1 -0
- package/node.d.cts +63 -0
- package/node.d.ts +63 -0
- package/node.js +58 -0
- package/node.js.map +1 -0
- package/package.json +75 -0
- package/rules.cjs +24 -0
- package/rules.cjs.map +1 -0
- package/rules.d.cts +1 -0
- package/rules.d.ts +1 -0
- package/rules.js +3 -0
- package/rules.js.map +1 -0
package/cli.js
ADDED
|
@@ -0,0 +1,3431 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { realpathSync, createReadStream, createWriteStream } from 'fs';
|
|
3
|
+
import { readFile, stat, writeFile } from 'fs/promises';
|
|
4
|
+
import { join, dirname, basename } from 'path';
|
|
5
|
+
import 'stream';
|
|
6
|
+
import { fileURLToPath, pathToFileURL } from 'url';
|
|
7
|
+
import { parseArgs } from 'util';
|
|
8
|
+
import { once } from 'events';
|
|
9
|
+
import { finished } from 'stream/promises';
|
|
10
|
+
|
|
11
|
+
// src/core/constants.ts
|
|
12
|
+
var DEFAULT_PREVIEW_BYTES = 256 * 1024;
|
|
13
|
+
var DEFAULT_MAX_LINE_CHARS = 1024 * 1024;
|
|
14
|
+
var DEFAULT_LINE_OVERLAP_CHARS = 1024;
|
|
15
|
+
var DEFAULT_KEY_BYTES = 32;
|
|
16
|
+
var MASK_TOKEN_PREFIX = "R";
|
|
17
|
+
var ALWAYS_REDACT_RULE_ID = "custom";
|
|
18
|
+
var ALWAYS_REDACT_TOKEN = "CUSTOM";
|
|
19
|
+
|
|
20
|
+
// src/core/errors.ts
|
|
21
|
+
var ERROR_BRAND = /* @__PURE__ */ Symbol.for("@socprime/logtotal-sanitizer/error");
|
|
22
|
+
var SanitizerError = class extends Error {
|
|
23
|
+
/** Machine-readable cause, stable across releases. */
|
|
24
|
+
code;
|
|
25
|
+
constructor(code, message) {
|
|
26
|
+
super(message);
|
|
27
|
+
this.name = "SanitizerError";
|
|
28
|
+
this.code = code;
|
|
29
|
+
Object.defineProperty(this, ERROR_BRAND, { value: true, enumerable: false });
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
var InvalidRuleError = class extends SanitizerError {
|
|
33
|
+
constructor(message) {
|
|
34
|
+
super("INVALID_RULE", message);
|
|
35
|
+
this.name = "InvalidRuleError";
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
var UnknownRuleError = class extends SanitizerError {
|
|
39
|
+
constructor(message) {
|
|
40
|
+
super("UNKNOWN_RULE", message);
|
|
41
|
+
this.name = "UnknownRuleError";
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
var InvalidKeyError = class extends SanitizerError {
|
|
45
|
+
constructor(message) {
|
|
46
|
+
super("INVALID_KEY", message);
|
|
47
|
+
this.name = "InvalidKeyError";
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
var InvalidOptionError = class extends SanitizerError {
|
|
51
|
+
constructor(message) {
|
|
52
|
+
super("INVALID_OPTION", message);
|
|
53
|
+
this.name = "InvalidOptionError";
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
var SanitizationAbortedError = class extends SanitizerError {
|
|
57
|
+
constructor(message = "Sanitization was aborted.") {
|
|
58
|
+
super("ABORTED", message);
|
|
59
|
+
this.name = "SanitizationAbortedError";
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// src/core/hmac.ts
|
|
64
|
+
var BLOCK_BYTES = 64;
|
|
65
|
+
var DIGEST_BYTES = 32;
|
|
66
|
+
var K = new Uint32Array([
|
|
67
|
+
1116352408,
|
|
68
|
+
1899447441,
|
|
69
|
+
3049323471,
|
|
70
|
+
3921009573,
|
|
71
|
+
961987163,
|
|
72
|
+
1508970993,
|
|
73
|
+
2453635748,
|
|
74
|
+
2870763221,
|
|
75
|
+
3624381080,
|
|
76
|
+
310598401,
|
|
77
|
+
607225278,
|
|
78
|
+
1426881987,
|
|
79
|
+
1925078388,
|
|
80
|
+
2162078206,
|
|
81
|
+
2614888103,
|
|
82
|
+
3248222580,
|
|
83
|
+
3835390401,
|
|
84
|
+
4022224774,
|
|
85
|
+
264347078,
|
|
86
|
+
604807628,
|
|
87
|
+
770255983,
|
|
88
|
+
1249150122,
|
|
89
|
+
1555081692,
|
|
90
|
+
1996064986,
|
|
91
|
+
2554220882,
|
|
92
|
+
2821834349,
|
|
93
|
+
2952996808,
|
|
94
|
+
3210313671,
|
|
95
|
+
3336571891,
|
|
96
|
+
3584528711,
|
|
97
|
+
113926993,
|
|
98
|
+
338241895,
|
|
99
|
+
666307205,
|
|
100
|
+
773529912,
|
|
101
|
+
1294757372,
|
|
102
|
+
1396182291,
|
|
103
|
+
1695183700,
|
|
104
|
+
1986661051,
|
|
105
|
+
2177026350,
|
|
106
|
+
2456956037,
|
|
107
|
+
2730485921,
|
|
108
|
+
2820302411,
|
|
109
|
+
3259730800,
|
|
110
|
+
3345764771,
|
|
111
|
+
3516065817,
|
|
112
|
+
3600352804,
|
|
113
|
+
4094571909,
|
|
114
|
+
275423344,
|
|
115
|
+
430227734,
|
|
116
|
+
506948616,
|
|
117
|
+
659060556,
|
|
118
|
+
883997877,
|
|
119
|
+
958139571,
|
|
120
|
+
1322822218,
|
|
121
|
+
1537002063,
|
|
122
|
+
1747873779,
|
|
123
|
+
1955562222,
|
|
124
|
+
2024104815,
|
|
125
|
+
2227730452,
|
|
126
|
+
2361852424,
|
|
127
|
+
2428436474,
|
|
128
|
+
2756734187,
|
|
129
|
+
3204031479,
|
|
130
|
+
3329325298
|
|
131
|
+
]);
|
|
132
|
+
var INITIAL_STATE = new Uint32Array([
|
|
133
|
+
1779033703,
|
|
134
|
+
3144134277,
|
|
135
|
+
1013904242,
|
|
136
|
+
2773480762,
|
|
137
|
+
1359893119,
|
|
138
|
+
2600822924,
|
|
139
|
+
528734635,
|
|
140
|
+
1541459225
|
|
141
|
+
]);
|
|
142
|
+
function rotr(value, bits) {
|
|
143
|
+
return (value >>> bits | value << 32 - bits) >>> 0;
|
|
144
|
+
}
|
|
145
|
+
function concatBytes(a, b) {
|
|
146
|
+
const out = new Uint8Array(a.length + b.length);
|
|
147
|
+
out.set(a, 0);
|
|
148
|
+
out.set(b, a.length);
|
|
149
|
+
return out;
|
|
150
|
+
}
|
|
151
|
+
function padMessage(message) {
|
|
152
|
+
const bitLength = message.length * 8;
|
|
153
|
+
const paddedLength = Math.ceil((message.length + 9) / BLOCK_BYTES) * BLOCK_BYTES;
|
|
154
|
+
const padded = new Uint8Array(paddedLength);
|
|
155
|
+
padded.set(message);
|
|
156
|
+
padded[message.length] = 128;
|
|
157
|
+
const view = new DataView(padded.buffer);
|
|
158
|
+
view.setUint32(paddedLength - 8, Math.floor(bitLength / 2 ** 32), false);
|
|
159
|
+
view.setUint32(paddedLength - 4, bitLength >>> 0, false);
|
|
160
|
+
return padded;
|
|
161
|
+
}
|
|
162
|
+
function sha256(message) {
|
|
163
|
+
const padded = padMessage(message);
|
|
164
|
+
const view = new DataView(padded.buffer);
|
|
165
|
+
const state = Uint32Array.from(INITIAL_STATE);
|
|
166
|
+
const w = new Uint32Array(64);
|
|
167
|
+
for (let offset = 0; offset < padded.length; offset += BLOCK_BYTES) {
|
|
168
|
+
for (let i = 0; i < 16; i += 1) {
|
|
169
|
+
w[i] = view.getUint32(offset + i * 4, false);
|
|
170
|
+
}
|
|
171
|
+
for (let i = 16; i < 64; i += 1) {
|
|
172
|
+
const s0 = rotr(w[i - 15], 7) ^ rotr(w[i - 15], 18) ^ w[i - 15] >>> 3;
|
|
173
|
+
const s1 = rotr(w[i - 2], 17) ^ rotr(w[i - 2], 19) ^ w[i - 2] >>> 10;
|
|
174
|
+
w[i] = w[i - 16] + s0 + w[i - 7] + s1 >>> 0;
|
|
175
|
+
}
|
|
176
|
+
let a = state[0];
|
|
177
|
+
let b = state[1];
|
|
178
|
+
let c = state[2];
|
|
179
|
+
let d = state[3];
|
|
180
|
+
let e = state[4];
|
|
181
|
+
let f = state[5];
|
|
182
|
+
let g = state[6];
|
|
183
|
+
let h = state[7];
|
|
184
|
+
for (let i = 0; i < 64; i += 1) {
|
|
185
|
+
const s1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
|
|
186
|
+
const ch = e & f ^ ~e & g;
|
|
187
|
+
const temp1 = h + s1 + ch + K[i] + w[i] >>> 0;
|
|
188
|
+
const s0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);
|
|
189
|
+
const maj = a & b ^ a & c ^ b & c;
|
|
190
|
+
const temp2 = s0 + maj >>> 0;
|
|
191
|
+
h = g;
|
|
192
|
+
g = f;
|
|
193
|
+
f = e;
|
|
194
|
+
e = d + temp1 >>> 0;
|
|
195
|
+
d = c;
|
|
196
|
+
c = b;
|
|
197
|
+
b = a;
|
|
198
|
+
a = temp1 + temp2 >>> 0;
|
|
199
|
+
}
|
|
200
|
+
state[0] = state[0] + a >>> 0;
|
|
201
|
+
state[1] = state[1] + b >>> 0;
|
|
202
|
+
state[2] = state[2] + c >>> 0;
|
|
203
|
+
state[3] = state[3] + d >>> 0;
|
|
204
|
+
state[4] = state[4] + e >>> 0;
|
|
205
|
+
state[5] = state[5] + f >>> 0;
|
|
206
|
+
state[6] = state[6] + g >>> 0;
|
|
207
|
+
state[7] = state[7] + h >>> 0;
|
|
208
|
+
}
|
|
209
|
+
const digest = new Uint8Array(DIGEST_BYTES);
|
|
210
|
+
const digestView = new DataView(digest.buffer);
|
|
211
|
+
for (let i = 0; i < 8; i += 1) {
|
|
212
|
+
digestView.setUint32(i * 4, state[i], false);
|
|
213
|
+
}
|
|
214
|
+
return digest;
|
|
215
|
+
}
|
|
216
|
+
function normalizeKey(key) {
|
|
217
|
+
const hashed = key.length > BLOCK_BYTES ? sha256(key) : key;
|
|
218
|
+
if (hashed.length === BLOCK_BYTES) {
|
|
219
|
+
return hashed;
|
|
220
|
+
}
|
|
221
|
+
const padded = new Uint8Array(BLOCK_BYTES);
|
|
222
|
+
padded.set(hashed);
|
|
223
|
+
return padded;
|
|
224
|
+
}
|
|
225
|
+
function createHmacSha256(key) {
|
|
226
|
+
const block = normalizeKey(key);
|
|
227
|
+
const ipad = new Uint8Array(BLOCK_BYTES);
|
|
228
|
+
const opad = new Uint8Array(BLOCK_BYTES);
|
|
229
|
+
for (let i = 0; i < BLOCK_BYTES; i += 1) {
|
|
230
|
+
ipad[i] = block[i] ^ 54;
|
|
231
|
+
opad[i] = block[i] ^ 92;
|
|
232
|
+
}
|
|
233
|
+
return (message) => sha256(concatBytes(opad, sha256(concatBytes(ipad, message))));
|
|
234
|
+
}
|
|
235
|
+
function hexToBytes(hex) {
|
|
236
|
+
const bytes = new Uint8Array(hex.length / 2);
|
|
237
|
+
for (let i = 0; i < bytes.length; i += 1) {
|
|
238
|
+
bytes[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
239
|
+
}
|
|
240
|
+
return bytes;
|
|
241
|
+
}
|
|
242
|
+
function bytesToHex(bytes) {
|
|
243
|
+
return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// src/core/key.ts
|
|
247
|
+
var HEX_PATTERN = /^(?:[0-9a-fA-F]{2})+$/;
|
|
248
|
+
function generateKey(byteLength = DEFAULT_KEY_BYTES) {
|
|
249
|
+
if (!Number.isInteger(byteLength) || byteLength < 16) {
|
|
250
|
+
throw new InvalidKeyError(`Key length must be an integer of at least 16 bytes, got ${byteLength}.`);
|
|
251
|
+
}
|
|
252
|
+
const source = globalThis.crypto;
|
|
253
|
+
if (typeof source?.getRandomValues !== "function") {
|
|
254
|
+
throw new InvalidKeyError(
|
|
255
|
+
"No secure random source available. Pass an explicit `key` or provide a Web Crypto implementation on globalThis.crypto."
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
const bytes = new Uint8Array(byteLength);
|
|
259
|
+
source.getRandomValues(bytes);
|
|
260
|
+
return bytesToHex(bytes);
|
|
261
|
+
}
|
|
262
|
+
function assertKey(key, encoding) {
|
|
263
|
+
if (key.length === 0) {
|
|
264
|
+
throw new InvalidKeyError("Key must not be empty.");
|
|
265
|
+
}
|
|
266
|
+
if (encoding === "hex" && !HEX_PATTERN.test(key)) {
|
|
267
|
+
throw new InvalidKeyError(
|
|
268
|
+
'A hex key must contain an even number of hexadecimal characters. Pass `keyEncoding: "utf8"` for a passphrase.'
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// src/rules/alwaysRedact.ts
|
|
274
|
+
function escapeRegExp(value) {
|
|
275
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
276
|
+
}
|
|
277
|
+
function normalizeValues(values) {
|
|
278
|
+
const unique = new Set(values.map((value) => value.trim()).filter((value) => value.length > 0));
|
|
279
|
+
return [...unique].sort((left, right) => right.length - left.length || left.localeCompare(right));
|
|
280
|
+
}
|
|
281
|
+
function normalizePatterns(patterns) {
|
|
282
|
+
return patterns.map((pattern) => {
|
|
283
|
+
const source = typeof pattern === "string" ? pattern : pattern.source;
|
|
284
|
+
try {
|
|
285
|
+
new RegExp(source, "u");
|
|
286
|
+
} catch (cause) {
|
|
287
|
+
throw new InvalidOptionError(
|
|
288
|
+
`alwaysRedact.patterns contains an invalid regular expression: ${String(cause)}`
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
return `(?:${source})`;
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
function createAlwaysRedactRule(options) {
|
|
295
|
+
if (!options) {
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
const patterns = [
|
|
299
|
+
...normalizeValues(options.values ?? []).map((value) => `(?:${escapeRegExp(value)})`),
|
|
300
|
+
...normalizePatterns(options.patterns ?? [])
|
|
301
|
+
];
|
|
302
|
+
if (patterns.length === 0) {
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
return {
|
|
306
|
+
id: options.ruleId ?? ALWAYS_REDACT_RULE_ID,
|
|
307
|
+
label: "Always redacted",
|
|
308
|
+
description: "Values and patterns the caller marked as always sensitive.",
|
|
309
|
+
mode: options.mode ?? "pseudo",
|
|
310
|
+
token: options.token ?? ALWAYS_REDACT_TOKEN,
|
|
311
|
+
patterns
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// src/rules/definitions/geoLocation.ts
|
|
316
|
+
function parseLatLonPair(match) {
|
|
317
|
+
const stripped = match.replace(/[()\s]/g, "");
|
|
318
|
+
if (!/^-?\d+\.\d+[,;]-?\d+\.\d+$/.test(stripped)) {
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
const [latStr, lonStr] = stripped.split(/[,;]/);
|
|
322
|
+
const lat = Number(latStr);
|
|
323
|
+
const lon = Number(lonStr);
|
|
324
|
+
if (!Number.isFinite(lat) || !Number.isFinite(lon)) {
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
return { lat, lon };
|
|
328
|
+
}
|
|
329
|
+
function isPlausibleLatLon(lat, lon) {
|
|
330
|
+
if (lat === 0 && lon === 0) {
|
|
331
|
+
return false;
|
|
332
|
+
}
|
|
333
|
+
return lat >= -90 && lat <= 90 && lon >= -180 && lon <= 180;
|
|
334
|
+
}
|
|
335
|
+
function validateGeo(match) {
|
|
336
|
+
const pair = parseLatLonPair(match);
|
|
337
|
+
if (pair) {
|
|
338
|
+
return isPlausibleLatLon(pair.lat, pair.lon);
|
|
339
|
+
}
|
|
340
|
+
return true;
|
|
341
|
+
}
|
|
342
|
+
var LAT_KEY = "(?:latitude|lat)";
|
|
343
|
+
var LON_KEY = "(?:longitude|long|lon|lng)";
|
|
344
|
+
var ZIP_KEY = "(?:zipCode|postalCode|postcode|zip)";
|
|
345
|
+
var ADDR_KEY = "(?:addressLine1|address|street)";
|
|
346
|
+
var OLC = "[23456789CFGHJMPQRVWXcfghjmpqrvwx]";
|
|
347
|
+
var GEOHASH = "[0-9b-hjkmnp-zB-HJKMNP-Z]";
|
|
348
|
+
var geoLocationRule = {
|
|
349
|
+
id: "geoLocation",
|
|
350
|
+
label: "Geo location",
|
|
351
|
+
description: "Coordinates, geohashes, plus codes, postcodes and context-anchored addresses become a stable <GEO:\u2026> token \u2014 the same value maps to the same token everywhere within a session.",
|
|
352
|
+
mode: "pseudo",
|
|
353
|
+
token: "GEO",
|
|
354
|
+
patterns: [
|
|
355
|
+
"(?:\\(\\s*-?\\d{1,3}\\.\\d{3,}\\s*[,;]\\s*-?\\d{1,3}\\.\\d{3,}\\s*\\))",
|
|
356
|
+
"(?:(?<![\\d.])-?\\d{1,3}\\.\\d{3,}\\s*[,;]\\s*-?\\d{1,3}\\.\\d{3,}(?!\\d))",
|
|
357
|
+
`(?:(?<=\\b${LAT_KEY}\\s*[=:]\\s*)-?\\d{1,3}\\.\\d+)`,
|
|
358
|
+
`(?:(?<=\\b${LON_KEY}\\s*[=:]\\s*)-?\\d{1,3}\\.\\d+)`,
|
|
359
|
+
`(?:(?<=\\bgeohash\\s*[=:]\\s*)${GEOHASH}{5,12}\\b)`,
|
|
360
|
+
`(?:(?<![\\w+])${OLC}{4,8}\\+${OLC}{2,3}(?![\\w+]))`,
|
|
361
|
+
`(?:\\b\\d{1,3}\xB0\\d{1,2}'\\d{1,2}(?:\\.\\d+)?"[NSEW]\\b)`,
|
|
362
|
+
"(?:\\b[A-Za-z]{1,2}\\d{1,2}[A-Za-z]?\\s\\d[A-Za-z]{2}\\b)",
|
|
363
|
+
`(?:(?<=\\b${ZIP_KEY}\\s*[=:]\\s*)\\d{5}(?:-\\d{4})?\\b)`
|
|
364
|
+
],
|
|
365
|
+
aggressivePatterns: [
|
|
366
|
+
"(?:\\(\\s*-?\\d{1,3}\\.\\d{2,}\\s*[,;]\\s*-?\\d{1,3}\\.\\d{2,}\\s*\\))",
|
|
367
|
+
"(?:(?<![\\d.])-?\\d{1,3}\\.\\d{2,}\\s*[,;]\\s*-?\\d{1,3}\\.\\d{2,}(?!\\d))",
|
|
368
|
+
`(?:(?<=\\b${ADDR_KEY}\\s*[=:]\\s*)[^\\n"'<>]{5,80})`
|
|
369
|
+
],
|
|
370
|
+
validate: validateGeo,
|
|
371
|
+
jsonKeys: [
|
|
372
|
+
"latitude",
|
|
373
|
+
"longitude",
|
|
374
|
+
"lat",
|
|
375
|
+
"lon",
|
|
376
|
+
"lng",
|
|
377
|
+
"long",
|
|
378
|
+
"coordinates",
|
|
379
|
+
"coords",
|
|
380
|
+
"geo",
|
|
381
|
+
"streetAddress",
|
|
382
|
+
"addressLine1",
|
|
383
|
+
"addressLine2",
|
|
384
|
+
"postalCode",
|
|
385
|
+
"postcode",
|
|
386
|
+
"zip",
|
|
387
|
+
"zipCode",
|
|
388
|
+
"geohash"
|
|
389
|
+
]
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
// src/rules/definitions/govIdentifiers.ts
|
|
393
|
+
function luhnCheck(digits) {
|
|
394
|
+
if (!/^\d+$/.test(digits) || /^0+$/.test(digits)) {
|
|
395
|
+
return false;
|
|
396
|
+
}
|
|
397
|
+
let sum = 0;
|
|
398
|
+
let doubleIt = false;
|
|
399
|
+
for (let i = digits.length - 1; i >= 0; i -= 1) {
|
|
400
|
+
let digit = Number(digits[i]);
|
|
401
|
+
if (doubleIt) {
|
|
402
|
+
digit *= 2;
|
|
403
|
+
if (digit > 9) {
|
|
404
|
+
digit -= 9;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
sum += digit;
|
|
408
|
+
doubleIt = !doubleIt;
|
|
409
|
+
}
|
|
410
|
+
return sum % 10 === 0;
|
|
411
|
+
}
|
|
412
|
+
function isValidSsn(area, group, serial) {
|
|
413
|
+
if (area === "000" || area === "666" || area.startsWith("9")) {
|
|
414
|
+
return false;
|
|
415
|
+
}
|
|
416
|
+
return group !== "00" && serial !== "0000";
|
|
417
|
+
}
|
|
418
|
+
function isValidItin(area, group, serial) {
|
|
419
|
+
if (!area.startsWith("9") || serial === "0000") {
|
|
420
|
+
return false;
|
|
421
|
+
}
|
|
422
|
+
const groupNum = Number(group);
|
|
423
|
+
return groupNum >= 70 && groupNum <= 88 || groupNum >= 90 && groupNum <= 92 || groupNum >= 94 && groupNum <= 99;
|
|
424
|
+
}
|
|
425
|
+
function isValidNhs(digits) {
|
|
426
|
+
if (!/^\d{10}$/.test(digits) || /^0+$/.test(digits)) {
|
|
427
|
+
return false;
|
|
428
|
+
}
|
|
429
|
+
let sum = 0;
|
|
430
|
+
for (let i = 0; i < 9; i += 1) {
|
|
431
|
+
sum += Number(digits[i]) * (10 - i);
|
|
432
|
+
}
|
|
433
|
+
const remainder = sum % 11;
|
|
434
|
+
let check = 11 - remainder;
|
|
435
|
+
if (check === 11) {
|
|
436
|
+
check = 0;
|
|
437
|
+
}
|
|
438
|
+
if (check === 10) {
|
|
439
|
+
return false;
|
|
440
|
+
}
|
|
441
|
+
return check === Number(digits[9]);
|
|
442
|
+
}
|
|
443
|
+
function validateGovId(match) {
|
|
444
|
+
const dashed = /^(\d{3})-(\d{2})-(\d{4})$/.exec(match);
|
|
445
|
+
if (dashed?.[1] && dashed[2] && dashed[3]) {
|
|
446
|
+
return isValidSsn(dashed[1], dashed[2], dashed[3]) || isValidItin(dashed[1], dashed[2], dashed[3]);
|
|
447
|
+
}
|
|
448
|
+
if (/^\d{3}-\d{3}-\d{3}$/.test(match)) {
|
|
449
|
+
return luhnCheck(match.replace(/-/g, ""));
|
|
450
|
+
}
|
|
451
|
+
if (/^\d{3} \d{3} \d{4}$/.test(match)) {
|
|
452
|
+
return isValidNhs(match.replace(/ /g, ""));
|
|
453
|
+
}
|
|
454
|
+
const nhsPrefixed = /(?:NHS|nhs)(?:[_-]?number)?\s*[=:]\s*(\d{10})$/.exec(match);
|
|
455
|
+
const nhsDigits = nhsPrefixed?.[1];
|
|
456
|
+
if (nhsDigits) {
|
|
457
|
+
return isValidNhs(nhsDigits);
|
|
458
|
+
}
|
|
459
|
+
return true;
|
|
460
|
+
}
|
|
461
|
+
var SSN_CTX = "(?:[Ss][Ss][Nn]|[Ee][Ii][Nn]|[Ss][Ii][Nn]|tax[_-]?id|social[_ -]?security(?:[_ -]?number)?)";
|
|
462
|
+
var ID_CTX = "(?:passport(?:_no|_number)?|driver[_-]?license|national[_-]?id|tax[_-]?id)";
|
|
463
|
+
var govIdentifiersRule = {
|
|
464
|
+
id: "govIds",
|
|
465
|
+
label: "Government identifiers",
|
|
466
|
+
description: "Passport numbers, tax numbers, SSNs and similar IDs are redacted outright.",
|
|
467
|
+
mode: "mask",
|
|
468
|
+
token: "GOV_ID",
|
|
469
|
+
patterns: [
|
|
470
|
+
"(?:(?<![A-Z0-9<])P<[A-Z0-9<]{42}(?![A-Z0-9<]))",
|
|
471
|
+
"(?:\\b\\d{3}-\\d{2}-\\d{4}\\b)",
|
|
472
|
+
"(?:\\b\\d{3}-\\d{3}-\\d{3}\\b)",
|
|
473
|
+
"(?:\\b\\d{3} \\d{3} \\d{4}\\b)",
|
|
474
|
+
"(?:\\b(?:NHS|nhs)(?:[_-]?number)?\\s*[=:]\\s*\\d{10}\\b)",
|
|
475
|
+
"(?:\\b(?!BG|GB|NK|KN|TN|NT|ZZ)[A-CEGHJ-PR-TW-Z][A-CEGHJ-NPR-TW-Z]\\d{6}\\s?[A-D]\\b)",
|
|
476
|
+
"(?:(?<![A-Za-z0-9\u0410-\u042F\u0406\u0407\u0404\u0490\u0430-\u044F\u0456\u0457\u0454\u0491])[\u0410-\u042F\u0406\u0407\u0404\u0490]{2}\\d{6}\\b)",
|
|
477
|
+
`(?:(?<=\\b${SSN_CTX}\\s*[=:]?\\s*)\\d{9}\\b)`,
|
|
478
|
+
"(?:(?<=(?:\u0406\u041F\u041D|\u0420\u041D\u041E\u041A\u041F\u041F|tax[_-]?id)\\s*[=:]?\\s*)\\d{10}\\b)",
|
|
479
|
+
`(?:(?<=\\b${ID_CTX}\\s*[=:]\\s*)[A-Za-z0-9<]{5,32})`
|
|
480
|
+
],
|
|
481
|
+
aggressivePatterns: [
|
|
482
|
+
"(?:\\b[A-Z]{1,2}\\d{6,9}\\b)",
|
|
483
|
+
"(?:\\b\\d{9,11}\\b)"
|
|
484
|
+
],
|
|
485
|
+
validate: validateGovId,
|
|
486
|
+
jsonKeys: [
|
|
487
|
+
"ssn",
|
|
488
|
+
"passportNumber",
|
|
489
|
+
"taxId",
|
|
490
|
+
"nationalId",
|
|
491
|
+
"socialSecurityNumber",
|
|
492
|
+
"passport",
|
|
493
|
+
"nino",
|
|
494
|
+
"nhsNumber",
|
|
495
|
+
"driverLicense",
|
|
496
|
+
"licenseNumber",
|
|
497
|
+
"personalId",
|
|
498
|
+
"dob",
|
|
499
|
+
"dateOfBirth"
|
|
500
|
+
]
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
// src/rules/definitions/healthInfo.ts
|
|
504
|
+
function isValidDea(match) {
|
|
505
|
+
if (!/^[ABFGMPRX][A-Z9]\d{7}$/.test(match)) {
|
|
506
|
+
return false;
|
|
507
|
+
}
|
|
508
|
+
const digits = match.slice(2);
|
|
509
|
+
const sum = Number(digits[0]) + Number(digits[2]) + Number(digits[4]) + 2 * (Number(digits[1]) + Number(digits[3]) + Number(digits[5]));
|
|
510
|
+
return sum % 10 === Number(digits[6]);
|
|
511
|
+
}
|
|
512
|
+
function validateHealth(match) {
|
|
513
|
+
if (/^[ABFGMPRX][A-Z9]\d{7}$/.test(match)) {
|
|
514
|
+
return isValidDea(match);
|
|
515
|
+
}
|
|
516
|
+
return true;
|
|
517
|
+
}
|
|
518
|
+
var MRN_KEY = "(?:[Mm][Rr][Nn]|medical[_-]?record(?:[_-]?number)?)";
|
|
519
|
+
var healthInfoRule = {
|
|
520
|
+
id: "healthInfo",
|
|
521
|
+
label: "Protected health information",
|
|
522
|
+
description: "PHI in SOC logs is almost always structured: JSON field names (diagnosis, medication, MRN, \u2026) carry most of the coverage. Freeform patterns only match context-anchored medical codes (ICD, SNOMED, LOINC, NDC, DEA, MRN).",
|
|
523
|
+
mode: "mask",
|
|
524
|
+
token: "PHI",
|
|
525
|
+
patterns: [
|
|
526
|
+
"(?:\\b(?:[Ii][Cc][Dd]-10|[Ii][Cc][Dd]10):?\\s*[A-Z]\\d{2}(?:\\.\\d{1,4})?\\b)",
|
|
527
|
+
"(?:\\b(?:[Ii][Cc][Dd]-9|[Ii][Cc][Dd]9):?\\s*(?:[VE]\\d{2,3}|\\d{3})(?:\\.\\d{1,2})?\\b)",
|
|
528
|
+
"(?:\\b(?:SNOMED(?:\\s*CT)?|[Ss]nomed(?:\\s*CT)?)[:\\s]+\\d{6,18}\\b)",
|
|
529
|
+
"(?:\\b(?:LOINC|[Ll]oinc)[:\\s]+\\d{1,5}-\\d\\b)",
|
|
530
|
+
"(?:\\b(?:NDC|[Nn]dc)[:\\s]+\\d{4,5}-\\d{3,4}-\\d{1,2}\\b)",
|
|
531
|
+
"(?:\\b[ABFGMPRX][A-Z9]\\d{7}\\b)",
|
|
532
|
+
"(?:\\b(?:[Nn][Pp][Ii])\\s*[=:]\\s*\\d{10}\\b)",
|
|
533
|
+
`(?:(?<=\\b${MRN_KEY}\\s*[=:]\\s*)[A-Za-z0-9-]{4,}\\b)`
|
|
534
|
+
],
|
|
535
|
+
aggressivePatterns: [
|
|
536
|
+
"(?:(?<![A-Za-z0-9])[A-TV-Z]\\d{2}(?:\\.\\w{1,4})?(?![A-Za-z0-9:]))"
|
|
537
|
+
],
|
|
538
|
+
validate: validateHealth,
|
|
539
|
+
jsonKeys: [
|
|
540
|
+
"diagnosis",
|
|
541
|
+
"prescription",
|
|
542
|
+
"medication",
|
|
543
|
+
"icd10",
|
|
544
|
+
"icd9",
|
|
545
|
+
"snomed",
|
|
546
|
+
"condition",
|
|
547
|
+
"allergy",
|
|
548
|
+
"allergies",
|
|
549
|
+
"bloodType",
|
|
550
|
+
"patientId",
|
|
551
|
+
"mrn",
|
|
552
|
+
"medicalRecordNumber",
|
|
553
|
+
"insuranceNumber",
|
|
554
|
+
"labResult",
|
|
555
|
+
"observation",
|
|
556
|
+
"vitalSigns"
|
|
557
|
+
]
|
|
558
|
+
};
|
|
559
|
+
|
|
560
|
+
// src/rules/definitions/homePaths.ts
|
|
561
|
+
var USER_SEG = `[^/\\\\\\s"'<>]+`;
|
|
562
|
+
function validateHomeUser(match) {
|
|
563
|
+
return match !== "." && match !== "..";
|
|
564
|
+
}
|
|
565
|
+
var homePathsRule = {
|
|
566
|
+
id: "paths",
|
|
567
|
+
label: "Home-directory usernames",
|
|
568
|
+
description: "Only the username segment of a home-directory path is redacted (e.g. /home/jdoe/app \u2192 /home/<R:\u2026>/app); the rest of the path stays visible.",
|
|
569
|
+
mode: "mask",
|
|
570
|
+
token: "PATH",
|
|
571
|
+
patterns: [
|
|
572
|
+
`(?:(?<=/export/home/)${USER_SEG})`,
|
|
573
|
+
`(?:(?<=/usr/home/)${USER_SEG})`,
|
|
574
|
+
`(?:(?<=/var/home/)${USER_SEG})`,
|
|
575
|
+
`(?:(?<=/home/)${USER_SEG})`,
|
|
576
|
+
`(?:(?<=/Users/)${USER_SEG})`,
|
|
577
|
+
`(?:(?<=[A-Za-z]:\\\\Users\\\\)${USER_SEG})`,
|
|
578
|
+
`(?:(?<=[A-Za-z]:/Users/)${USER_SEG})`,
|
|
579
|
+
`(?:(?<=\\\\\\\\\\?\\\\[A-Za-z]:\\\\Users\\\\)${USER_SEG})`,
|
|
580
|
+
`(?:(?<=[A-Za-z]:\\\\Documents and Settings\\\\)${USER_SEG})`,
|
|
581
|
+
`(?:(?<=[A-Za-z]:\\\\\\\\Users\\\\\\\\)${USER_SEG})`,
|
|
582
|
+
`(?:(?<=/mnt/[A-Za-z]/Users/)${USER_SEG})`,
|
|
583
|
+
`(?:(?<=//wsl\\$/(?:[A-Za-z0-9_-]+)/home/)${USER_SEG})`
|
|
584
|
+
],
|
|
585
|
+
validate: validateHomeUser
|
|
586
|
+
};
|
|
587
|
+
|
|
588
|
+
// src/rules/definitions/hostnames.ts
|
|
589
|
+
var GTLD = [
|
|
590
|
+
"abbott",
|
|
591
|
+
"abogado",
|
|
592
|
+
"ac",
|
|
593
|
+
"academy",
|
|
594
|
+
"accountant",
|
|
595
|
+
"accountants",
|
|
596
|
+
"active",
|
|
597
|
+
"actor",
|
|
598
|
+
"ad",
|
|
599
|
+
"ads",
|
|
600
|
+
"adult",
|
|
601
|
+
"ae",
|
|
602
|
+
"aero",
|
|
603
|
+
"af",
|
|
604
|
+
"afl",
|
|
605
|
+
"ag",
|
|
606
|
+
"agency",
|
|
607
|
+
"ai",
|
|
608
|
+
"airforce",
|
|
609
|
+
"al",
|
|
610
|
+
"allfinanz",
|
|
611
|
+
"alsace",
|
|
612
|
+
"am",
|
|
613
|
+
"amsterdam",
|
|
614
|
+
"an",
|
|
615
|
+
"android",
|
|
616
|
+
"ao",
|
|
617
|
+
"apartments",
|
|
618
|
+
"aq",
|
|
619
|
+
"aquarelle",
|
|
620
|
+
"ar",
|
|
621
|
+
"archi",
|
|
622
|
+
"army",
|
|
623
|
+
"arpa",
|
|
624
|
+
"as",
|
|
625
|
+
"asia",
|
|
626
|
+
"associates",
|
|
627
|
+
"at",
|
|
628
|
+
"attorney",
|
|
629
|
+
"au",
|
|
630
|
+
"auction",
|
|
631
|
+
"audio",
|
|
632
|
+
"autos",
|
|
633
|
+
"aw",
|
|
634
|
+
"ax",
|
|
635
|
+
"axa",
|
|
636
|
+
"az",
|
|
637
|
+
"ba",
|
|
638
|
+
"band",
|
|
639
|
+
"bank",
|
|
640
|
+
"bar",
|
|
641
|
+
"barclaycard",
|
|
642
|
+
"barclays",
|
|
643
|
+
"bargains",
|
|
644
|
+
"bauhaus",
|
|
645
|
+
"bayern",
|
|
646
|
+
"bb",
|
|
647
|
+
"bbc",
|
|
648
|
+
"bd",
|
|
649
|
+
"be",
|
|
650
|
+
"beer",
|
|
651
|
+
"berlin",
|
|
652
|
+
"best",
|
|
653
|
+
"bf",
|
|
654
|
+
"bg",
|
|
655
|
+
"bh",
|
|
656
|
+
"bi",
|
|
657
|
+
"bid",
|
|
658
|
+
"bike",
|
|
659
|
+
"bingo",
|
|
660
|
+
"bio",
|
|
661
|
+
"biz",
|
|
662
|
+
"bj",
|
|
663
|
+
"bl",
|
|
664
|
+
"black",
|
|
665
|
+
"blackfriday",
|
|
666
|
+
"bloomberg",
|
|
667
|
+
"blue",
|
|
668
|
+
"bm",
|
|
669
|
+
"bmw",
|
|
670
|
+
"bn",
|
|
671
|
+
"bnpparibas",
|
|
672
|
+
"bo",
|
|
673
|
+
"boats",
|
|
674
|
+
"bond",
|
|
675
|
+
"boo",
|
|
676
|
+
"boutique",
|
|
677
|
+
"bq",
|
|
678
|
+
"br",
|
|
679
|
+
"brussels",
|
|
680
|
+
"bs",
|
|
681
|
+
"bt",
|
|
682
|
+
"budapest",
|
|
683
|
+
"build",
|
|
684
|
+
"builders",
|
|
685
|
+
"business",
|
|
686
|
+
"buzz",
|
|
687
|
+
"bv",
|
|
688
|
+
"bw",
|
|
689
|
+
"by",
|
|
690
|
+
"bz",
|
|
691
|
+
"bzh",
|
|
692
|
+
"ca",
|
|
693
|
+
"cab",
|
|
694
|
+
"cafe",
|
|
695
|
+
"cal",
|
|
696
|
+
"camera",
|
|
697
|
+
"camp",
|
|
698
|
+
"cancerresearch",
|
|
699
|
+
"canon",
|
|
700
|
+
"capetown",
|
|
701
|
+
"capital",
|
|
702
|
+
"caravan",
|
|
703
|
+
"cards",
|
|
704
|
+
"care",
|
|
705
|
+
"career",
|
|
706
|
+
"careers",
|
|
707
|
+
"cartier",
|
|
708
|
+
"casa",
|
|
709
|
+
"cash",
|
|
710
|
+
"casino",
|
|
711
|
+
"cat",
|
|
712
|
+
"catering",
|
|
713
|
+
"cbn",
|
|
714
|
+
"cc",
|
|
715
|
+
"cd",
|
|
716
|
+
"center",
|
|
717
|
+
"ceo",
|
|
718
|
+
"cern",
|
|
719
|
+
"cf",
|
|
720
|
+
"cfd",
|
|
721
|
+
"cg",
|
|
722
|
+
"ch",
|
|
723
|
+
"channel",
|
|
724
|
+
"chat",
|
|
725
|
+
"cheap",
|
|
726
|
+
"chloe",
|
|
727
|
+
"christmas",
|
|
728
|
+
"chrome",
|
|
729
|
+
"church",
|
|
730
|
+
"ci",
|
|
731
|
+
"citic",
|
|
732
|
+
"city",
|
|
733
|
+
"ck",
|
|
734
|
+
"cl",
|
|
735
|
+
"claims",
|
|
736
|
+
"cleaning",
|
|
737
|
+
"click",
|
|
738
|
+
"clinic",
|
|
739
|
+
"clothing",
|
|
740
|
+
"club",
|
|
741
|
+
"cm",
|
|
742
|
+
"cn",
|
|
743
|
+
"co",
|
|
744
|
+
"coach",
|
|
745
|
+
"codes",
|
|
746
|
+
"coffee",
|
|
747
|
+
"college",
|
|
748
|
+
"cologne",
|
|
749
|
+
"com",
|
|
750
|
+
"community",
|
|
751
|
+
"company",
|
|
752
|
+
"computer",
|
|
753
|
+
"condos",
|
|
754
|
+
"construction",
|
|
755
|
+
"consulting",
|
|
756
|
+
"contractors",
|
|
757
|
+
"cooking",
|
|
758
|
+
"cool",
|
|
759
|
+
"coop",
|
|
760
|
+
"country",
|
|
761
|
+
"courses",
|
|
762
|
+
"cr",
|
|
763
|
+
"credit",
|
|
764
|
+
"creditcard",
|
|
765
|
+
"cricket",
|
|
766
|
+
"crs",
|
|
767
|
+
"cruises",
|
|
768
|
+
"cu",
|
|
769
|
+
"cuisinella",
|
|
770
|
+
"cv",
|
|
771
|
+
"cw",
|
|
772
|
+
"cx",
|
|
773
|
+
"cy",
|
|
774
|
+
"cymru",
|
|
775
|
+
"cyou",
|
|
776
|
+
"cz",
|
|
777
|
+
"dabur",
|
|
778
|
+
"dad",
|
|
779
|
+
"dance",
|
|
780
|
+
"date",
|
|
781
|
+
"dating",
|
|
782
|
+
"datsun",
|
|
783
|
+
"day",
|
|
784
|
+
"dclk",
|
|
785
|
+
"de",
|
|
786
|
+
"deals",
|
|
787
|
+
"degree",
|
|
788
|
+
"delivery",
|
|
789
|
+
"democrat",
|
|
790
|
+
"dental",
|
|
791
|
+
"dentist",
|
|
792
|
+
"desi",
|
|
793
|
+
"design",
|
|
794
|
+
"dev",
|
|
795
|
+
"diamonds",
|
|
796
|
+
"diet",
|
|
797
|
+
"digital",
|
|
798
|
+
"direct",
|
|
799
|
+
"directory",
|
|
800
|
+
"discount",
|
|
801
|
+
"dj",
|
|
802
|
+
"dk",
|
|
803
|
+
"dm",
|
|
804
|
+
"dnp",
|
|
805
|
+
"do",
|
|
806
|
+
"docs",
|
|
807
|
+
"doha",
|
|
808
|
+
"domains",
|
|
809
|
+
"doosan",
|
|
810
|
+
"download",
|
|
811
|
+
"durban",
|
|
812
|
+
"dvag",
|
|
813
|
+
"dz",
|
|
814
|
+
"eat",
|
|
815
|
+
"ec",
|
|
816
|
+
"edu",
|
|
817
|
+
"education",
|
|
818
|
+
"ee",
|
|
819
|
+
"eg",
|
|
820
|
+
"eh",
|
|
821
|
+
"email",
|
|
822
|
+
"emerck",
|
|
823
|
+
"energy",
|
|
824
|
+
"engineer",
|
|
825
|
+
"engineering",
|
|
826
|
+
"enterprises",
|
|
827
|
+
"epson",
|
|
828
|
+
"equipment",
|
|
829
|
+
"er",
|
|
830
|
+
"erni",
|
|
831
|
+
"es",
|
|
832
|
+
"esq",
|
|
833
|
+
"estate",
|
|
834
|
+
"et",
|
|
835
|
+
"eu",
|
|
836
|
+
"eurovision",
|
|
837
|
+
"eus",
|
|
838
|
+
"events",
|
|
839
|
+
"everbank",
|
|
840
|
+
"exchange",
|
|
841
|
+
"expert",
|
|
842
|
+
"exposed",
|
|
843
|
+
"express",
|
|
844
|
+
"fail",
|
|
845
|
+
"faith",
|
|
846
|
+
"fan",
|
|
847
|
+
"fans",
|
|
848
|
+
"farm",
|
|
849
|
+
"fashion",
|
|
850
|
+
"feedback",
|
|
851
|
+
"fi",
|
|
852
|
+
"film",
|
|
853
|
+
"finance",
|
|
854
|
+
"financial",
|
|
855
|
+
"firmdale",
|
|
856
|
+
"fish",
|
|
857
|
+
"fishing",
|
|
858
|
+
"fit",
|
|
859
|
+
"fitness",
|
|
860
|
+
"fj",
|
|
861
|
+
"fk",
|
|
862
|
+
"flights",
|
|
863
|
+
"florist",
|
|
864
|
+
"flowers",
|
|
865
|
+
"flsmidth",
|
|
866
|
+
"fly",
|
|
867
|
+
"fm",
|
|
868
|
+
"fo",
|
|
869
|
+
"foo",
|
|
870
|
+
"football",
|
|
871
|
+
"forex",
|
|
872
|
+
"forsale",
|
|
873
|
+
"foundation",
|
|
874
|
+
"fr",
|
|
875
|
+
"frl",
|
|
876
|
+
"frogans",
|
|
877
|
+
"fund",
|
|
878
|
+
"furniture",
|
|
879
|
+
"futbol",
|
|
880
|
+
"ga",
|
|
881
|
+
"gal",
|
|
882
|
+
"gallery",
|
|
883
|
+
"garden",
|
|
884
|
+
"gb",
|
|
885
|
+
"gbiz",
|
|
886
|
+
"gd",
|
|
887
|
+
"gdn",
|
|
888
|
+
"ge",
|
|
889
|
+
"gent",
|
|
890
|
+
"gf",
|
|
891
|
+
"gg",
|
|
892
|
+
"ggee",
|
|
893
|
+
"gh",
|
|
894
|
+
"gi",
|
|
895
|
+
"gift",
|
|
896
|
+
"gifts",
|
|
897
|
+
"gives",
|
|
898
|
+
"gl",
|
|
899
|
+
"glass",
|
|
900
|
+
"gle",
|
|
901
|
+
"global",
|
|
902
|
+
"globo",
|
|
903
|
+
"gm",
|
|
904
|
+
"gmail",
|
|
905
|
+
"gmo",
|
|
906
|
+
"gmx",
|
|
907
|
+
"gn",
|
|
908
|
+
"gold",
|
|
909
|
+
"goldpoint",
|
|
910
|
+
"golf",
|
|
911
|
+
"goo",
|
|
912
|
+
"goog",
|
|
913
|
+
"google",
|
|
914
|
+
"gop",
|
|
915
|
+
"gov",
|
|
916
|
+
"gp",
|
|
917
|
+
"gq",
|
|
918
|
+
"gr",
|
|
919
|
+
"graphics",
|
|
920
|
+
"gratis",
|
|
921
|
+
"green",
|
|
922
|
+
"gripe",
|
|
923
|
+
"gs",
|
|
924
|
+
"gt",
|
|
925
|
+
"gu",
|
|
926
|
+
"guge",
|
|
927
|
+
"guide",
|
|
928
|
+
"guitars",
|
|
929
|
+
"guru",
|
|
930
|
+
"gw",
|
|
931
|
+
"gy",
|
|
932
|
+
"hamburg",
|
|
933
|
+
"hangout",
|
|
934
|
+
"haus",
|
|
935
|
+
"healthcare",
|
|
936
|
+
"help",
|
|
937
|
+
"here",
|
|
938
|
+
"hermes",
|
|
939
|
+
"hiphop",
|
|
940
|
+
"hiv",
|
|
941
|
+
"hk",
|
|
942
|
+
"hm",
|
|
943
|
+
"hn",
|
|
944
|
+
"holdings",
|
|
945
|
+
"holiday",
|
|
946
|
+
"homes",
|
|
947
|
+
"horse",
|
|
948
|
+
"host",
|
|
949
|
+
"hosting",
|
|
950
|
+
"house",
|
|
951
|
+
"how",
|
|
952
|
+
"hr",
|
|
953
|
+
"ht",
|
|
954
|
+
"hu",
|
|
955
|
+
"ibm",
|
|
956
|
+
"id",
|
|
957
|
+
"ie",
|
|
958
|
+
"ifm",
|
|
959
|
+
"il",
|
|
960
|
+
"im",
|
|
961
|
+
"immo",
|
|
962
|
+
"immobilien",
|
|
963
|
+
"in",
|
|
964
|
+
"industries",
|
|
965
|
+
"infiniti",
|
|
966
|
+
"info",
|
|
967
|
+
"ing",
|
|
968
|
+
"ink",
|
|
969
|
+
"institute",
|
|
970
|
+
"insure",
|
|
971
|
+
"int",
|
|
972
|
+
"international",
|
|
973
|
+
"investments",
|
|
974
|
+
"io",
|
|
975
|
+
"iq",
|
|
976
|
+
"ir",
|
|
977
|
+
"irish",
|
|
978
|
+
"is",
|
|
979
|
+
"it",
|
|
980
|
+
"iwc",
|
|
981
|
+
"java",
|
|
982
|
+
"jcb",
|
|
983
|
+
"je",
|
|
984
|
+
"jetzt",
|
|
985
|
+
"jm",
|
|
986
|
+
"jo",
|
|
987
|
+
"jobs",
|
|
988
|
+
"joburg",
|
|
989
|
+
"jp",
|
|
990
|
+
"juegos",
|
|
991
|
+
"kaufen",
|
|
992
|
+
"kddi",
|
|
993
|
+
"ke",
|
|
994
|
+
"kg",
|
|
995
|
+
"kh",
|
|
996
|
+
"ki",
|
|
997
|
+
"kim",
|
|
998
|
+
"kitchen",
|
|
999
|
+
"kiwi",
|
|
1000
|
+
"km",
|
|
1001
|
+
"kn",
|
|
1002
|
+
"koeln",
|
|
1003
|
+
"komatsu",
|
|
1004
|
+
"kp",
|
|
1005
|
+
"kr",
|
|
1006
|
+
"krd",
|
|
1007
|
+
"kred",
|
|
1008
|
+
"kw",
|
|
1009
|
+
"ky",
|
|
1010
|
+
"kyoto",
|
|
1011
|
+
"kz",
|
|
1012
|
+
"la",
|
|
1013
|
+
"lacaixa",
|
|
1014
|
+
"land",
|
|
1015
|
+
"lat",
|
|
1016
|
+
"latrobe",
|
|
1017
|
+
"lawyer",
|
|
1018
|
+
"lb",
|
|
1019
|
+
"lc",
|
|
1020
|
+
"lds",
|
|
1021
|
+
"lease",
|
|
1022
|
+
"leclerc",
|
|
1023
|
+
"legal",
|
|
1024
|
+
"lgbt",
|
|
1025
|
+
"li",
|
|
1026
|
+
"lidl",
|
|
1027
|
+
"life",
|
|
1028
|
+
"lighting",
|
|
1029
|
+
"limited",
|
|
1030
|
+
"limo",
|
|
1031
|
+
"link",
|
|
1032
|
+
"lk",
|
|
1033
|
+
"loan",
|
|
1034
|
+
"loans",
|
|
1035
|
+
"london",
|
|
1036
|
+
"lotte",
|
|
1037
|
+
"lotto",
|
|
1038
|
+
"love",
|
|
1039
|
+
"lr",
|
|
1040
|
+
"ls",
|
|
1041
|
+
"lt",
|
|
1042
|
+
"ltda",
|
|
1043
|
+
"lu",
|
|
1044
|
+
"luxe",
|
|
1045
|
+
"luxury",
|
|
1046
|
+
"lv",
|
|
1047
|
+
"ly",
|
|
1048
|
+
"ma",
|
|
1049
|
+
"madrid",
|
|
1050
|
+
"maif",
|
|
1051
|
+
"maison",
|
|
1052
|
+
"management",
|
|
1053
|
+
"mango",
|
|
1054
|
+
"market",
|
|
1055
|
+
"marketing",
|
|
1056
|
+
"markets",
|
|
1057
|
+
"marriott",
|
|
1058
|
+
"mc",
|
|
1059
|
+
"md",
|
|
1060
|
+
"me",
|
|
1061
|
+
"media",
|
|
1062
|
+
"meet",
|
|
1063
|
+
"melbourne",
|
|
1064
|
+
"meme",
|
|
1065
|
+
"memorial",
|
|
1066
|
+
"menu",
|
|
1067
|
+
"mf",
|
|
1068
|
+
"mg",
|
|
1069
|
+
"mh",
|
|
1070
|
+
"miami",
|
|
1071
|
+
"mil",
|
|
1072
|
+
"mini",
|
|
1073
|
+
"mk",
|
|
1074
|
+
"ml",
|
|
1075
|
+
"mm",
|
|
1076
|
+
"mma",
|
|
1077
|
+
"mn",
|
|
1078
|
+
"mo",
|
|
1079
|
+
"mobi",
|
|
1080
|
+
"moda",
|
|
1081
|
+
"moe",
|
|
1082
|
+
"monash",
|
|
1083
|
+
"money",
|
|
1084
|
+
"mormon",
|
|
1085
|
+
"mortgage",
|
|
1086
|
+
"moscow",
|
|
1087
|
+
"motorcycles",
|
|
1088
|
+
"mov",
|
|
1089
|
+
"movie",
|
|
1090
|
+
"mp",
|
|
1091
|
+
"mq",
|
|
1092
|
+
"mr",
|
|
1093
|
+
"ms",
|
|
1094
|
+
"mt",
|
|
1095
|
+
"mtn",
|
|
1096
|
+
"mtpc",
|
|
1097
|
+
"mu",
|
|
1098
|
+
"museum",
|
|
1099
|
+
"mv",
|
|
1100
|
+
"mw",
|
|
1101
|
+
"mx",
|
|
1102
|
+
"my",
|
|
1103
|
+
"mz",
|
|
1104
|
+
"na",
|
|
1105
|
+
"nagoya",
|
|
1106
|
+
"name",
|
|
1107
|
+
"navy",
|
|
1108
|
+
"nc",
|
|
1109
|
+
"ne",
|
|
1110
|
+
"net",
|
|
1111
|
+
"network",
|
|
1112
|
+
"neustar",
|
|
1113
|
+
"new",
|
|
1114
|
+
"news",
|
|
1115
|
+
"nexus",
|
|
1116
|
+
"nf",
|
|
1117
|
+
"ng",
|
|
1118
|
+
"ngo",
|
|
1119
|
+
"nhk",
|
|
1120
|
+
"ni",
|
|
1121
|
+
"nico",
|
|
1122
|
+
"ninja",
|
|
1123
|
+
"nissan",
|
|
1124
|
+
"nl",
|
|
1125
|
+
"no",
|
|
1126
|
+
"np",
|
|
1127
|
+
"nr",
|
|
1128
|
+
"nra",
|
|
1129
|
+
"nrw",
|
|
1130
|
+
"ntt",
|
|
1131
|
+
"nu",
|
|
1132
|
+
"nyc",
|
|
1133
|
+
"nz",
|
|
1134
|
+
"okinawa",
|
|
1135
|
+
"om",
|
|
1136
|
+
"one",
|
|
1137
|
+
"ong",
|
|
1138
|
+
"onl",
|
|
1139
|
+
"online",
|
|
1140
|
+
"ooo",
|
|
1141
|
+
"org",
|
|
1142
|
+
"organic",
|
|
1143
|
+
"osaka",
|
|
1144
|
+
"otsuka",
|
|
1145
|
+
"ovh",
|
|
1146
|
+
"pa",
|
|
1147
|
+
"page",
|
|
1148
|
+
"panerai",
|
|
1149
|
+
"paris",
|
|
1150
|
+
"partners",
|
|
1151
|
+
"parts",
|
|
1152
|
+
"party",
|
|
1153
|
+
"pe",
|
|
1154
|
+
"pf",
|
|
1155
|
+
"pg",
|
|
1156
|
+
"ph",
|
|
1157
|
+
"pharmacy",
|
|
1158
|
+
"photo",
|
|
1159
|
+
"photography",
|
|
1160
|
+
"photos",
|
|
1161
|
+
"physio",
|
|
1162
|
+
"piaget",
|
|
1163
|
+
"pics",
|
|
1164
|
+
"pictet",
|
|
1165
|
+
"pictures",
|
|
1166
|
+
"pink",
|
|
1167
|
+
"pizza",
|
|
1168
|
+
"pk",
|
|
1169
|
+
"pl",
|
|
1170
|
+
"place",
|
|
1171
|
+
"plumbing",
|
|
1172
|
+
"plus",
|
|
1173
|
+
"pm",
|
|
1174
|
+
"pn",
|
|
1175
|
+
"pohl",
|
|
1176
|
+
"poker",
|
|
1177
|
+
"porn",
|
|
1178
|
+
"post",
|
|
1179
|
+
"pr",
|
|
1180
|
+
"praxi",
|
|
1181
|
+
"press",
|
|
1182
|
+
"pro",
|
|
1183
|
+
"prod",
|
|
1184
|
+
"productions",
|
|
1185
|
+
"prof",
|
|
1186
|
+
"properties",
|
|
1187
|
+
"property",
|
|
1188
|
+
"ps",
|
|
1189
|
+
"pt",
|
|
1190
|
+
"pub",
|
|
1191
|
+
"pw",
|
|
1192
|
+
"py",
|
|
1193
|
+
"qa",
|
|
1194
|
+
"qpon",
|
|
1195
|
+
"quebec",
|
|
1196
|
+
"racing",
|
|
1197
|
+
"re",
|
|
1198
|
+
"realtor",
|
|
1199
|
+
"recipes",
|
|
1200
|
+
"red",
|
|
1201
|
+
"redstone",
|
|
1202
|
+
"rehab",
|
|
1203
|
+
"reise",
|
|
1204
|
+
"reisen",
|
|
1205
|
+
"reit",
|
|
1206
|
+
"ren",
|
|
1207
|
+
"rentals",
|
|
1208
|
+
"repair",
|
|
1209
|
+
"report",
|
|
1210
|
+
"republican",
|
|
1211
|
+
"rest",
|
|
1212
|
+
"restaurant",
|
|
1213
|
+
"review",
|
|
1214
|
+
"reviews",
|
|
1215
|
+
"rich",
|
|
1216
|
+
"rio",
|
|
1217
|
+
"rip",
|
|
1218
|
+
"ro",
|
|
1219
|
+
"rocks",
|
|
1220
|
+
"rodeo",
|
|
1221
|
+
"rs",
|
|
1222
|
+
"rsvp",
|
|
1223
|
+
"ru",
|
|
1224
|
+
"ruhr",
|
|
1225
|
+
"rw",
|
|
1226
|
+
"ryukyu",
|
|
1227
|
+
"sa",
|
|
1228
|
+
"saarland",
|
|
1229
|
+
"sale",
|
|
1230
|
+
"samsung",
|
|
1231
|
+
"sap",
|
|
1232
|
+
"sarl",
|
|
1233
|
+
"saxo",
|
|
1234
|
+
"sb",
|
|
1235
|
+
"sc",
|
|
1236
|
+
"sca",
|
|
1237
|
+
"scb",
|
|
1238
|
+
"schmidt",
|
|
1239
|
+
"scholarships",
|
|
1240
|
+
"school",
|
|
1241
|
+
"schule",
|
|
1242
|
+
"schwarz",
|
|
1243
|
+
"science",
|
|
1244
|
+
"scot",
|
|
1245
|
+
"sd",
|
|
1246
|
+
"se",
|
|
1247
|
+
"services",
|
|
1248
|
+
"sew",
|
|
1249
|
+
"sexy",
|
|
1250
|
+
"sg",
|
|
1251
|
+
"sh",
|
|
1252
|
+
"shiksha",
|
|
1253
|
+
"shoes",
|
|
1254
|
+
"shriram",
|
|
1255
|
+
"si",
|
|
1256
|
+
"singles",
|
|
1257
|
+
"site",
|
|
1258
|
+
"sj",
|
|
1259
|
+
"sk",
|
|
1260
|
+
"sky",
|
|
1261
|
+
"sl",
|
|
1262
|
+
"sm",
|
|
1263
|
+
"sn",
|
|
1264
|
+
"so",
|
|
1265
|
+
"social",
|
|
1266
|
+
"software",
|
|
1267
|
+
"sohu",
|
|
1268
|
+
"solar",
|
|
1269
|
+
"solutions",
|
|
1270
|
+
"soy",
|
|
1271
|
+
"space",
|
|
1272
|
+
"spiegel",
|
|
1273
|
+
"spreadbetting",
|
|
1274
|
+
"sr",
|
|
1275
|
+
"ss",
|
|
1276
|
+
"st",
|
|
1277
|
+
"study",
|
|
1278
|
+
"style",
|
|
1279
|
+
"su",
|
|
1280
|
+
"sucks",
|
|
1281
|
+
"supplies",
|
|
1282
|
+
"supply",
|
|
1283
|
+
"support",
|
|
1284
|
+
"surf",
|
|
1285
|
+
"surgery",
|
|
1286
|
+
"suzuki",
|
|
1287
|
+
"sv",
|
|
1288
|
+
"sx",
|
|
1289
|
+
"sy",
|
|
1290
|
+
"sydney",
|
|
1291
|
+
"systems",
|
|
1292
|
+
"sz",
|
|
1293
|
+
"taipei",
|
|
1294
|
+
"tatar",
|
|
1295
|
+
"tattoo",
|
|
1296
|
+
"tax",
|
|
1297
|
+
"tc",
|
|
1298
|
+
"td",
|
|
1299
|
+
"tech",
|
|
1300
|
+
"technology",
|
|
1301
|
+
"tel",
|
|
1302
|
+
"temasek",
|
|
1303
|
+
"tennis",
|
|
1304
|
+
"tf",
|
|
1305
|
+
"tg",
|
|
1306
|
+
"th",
|
|
1307
|
+
"tickets",
|
|
1308
|
+
"tienda",
|
|
1309
|
+
"tips",
|
|
1310
|
+
"tires",
|
|
1311
|
+
"tirol",
|
|
1312
|
+
"tj",
|
|
1313
|
+
"tk",
|
|
1314
|
+
"tl",
|
|
1315
|
+
"tm",
|
|
1316
|
+
"tn",
|
|
1317
|
+
"to",
|
|
1318
|
+
"today",
|
|
1319
|
+
"tokyo",
|
|
1320
|
+
"tools",
|
|
1321
|
+
"top",
|
|
1322
|
+
"toshiba",
|
|
1323
|
+
"tours",
|
|
1324
|
+
"town",
|
|
1325
|
+
"toys",
|
|
1326
|
+
"tp",
|
|
1327
|
+
"tr",
|
|
1328
|
+
"trade",
|
|
1329
|
+
"trading",
|
|
1330
|
+
"training",
|
|
1331
|
+
"travel",
|
|
1332
|
+
"trust",
|
|
1333
|
+
"tt",
|
|
1334
|
+
"tui",
|
|
1335
|
+
"tv",
|
|
1336
|
+
"tw",
|
|
1337
|
+
"tz",
|
|
1338
|
+
"ua",
|
|
1339
|
+
"ug",
|
|
1340
|
+
"uk",
|
|
1341
|
+
"um",
|
|
1342
|
+
"university",
|
|
1343
|
+
"uno",
|
|
1344
|
+
"uol",
|
|
1345
|
+
"us",
|
|
1346
|
+
"uy",
|
|
1347
|
+
"uz",
|
|
1348
|
+
"va",
|
|
1349
|
+
"vacations",
|
|
1350
|
+
"vc",
|
|
1351
|
+
"ve",
|
|
1352
|
+
"vegas",
|
|
1353
|
+
"ventures",
|
|
1354
|
+
"versicherung",
|
|
1355
|
+
"vet",
|
|
1356
|
+
"vg",
|
|
1357
|
+
"vi",
|
|
1358
|
+
"viajes",
|
|
1359
|
+
"video",
|
|
1360
|
+
"villas",
|
|
1361
|
+
"vision",
|
|
1362
|
+
"vlaanderen",
|
|
1363
|
+
"vn",
|
|
1364
|
+
"vodka",
|
|
1365
|
+
"vote",
|
|
1366
|
+
"voting",
|
|
1367
|
+
"voto",
|
|
1368
|
+
"voyage",
|
|
1369
|
+
"vu",
|
|
1370
|
+
"wales",
|
|
1371
|
+
"wang",
|
|
1372
|
+
"watch",
|
|
1373
|
+
"webcam",
|
|
1374
|
+
"website",
|
|
1375
|
+
"wed",
|
|
1376
|
+
"wedding",
|
|
1377
|
+
"wf",
|
|
1378
|
+
"whoswho",
|
|
1379
|
+
"wien",
|
|
1380
|
+
"wiki",
|
|
1381
|
+
"williamhill",
|
|
1382
|
+
"win",
|
|
1383
|
+
"wme",
|
|
1384
|
+
"work",
|
|
1385
|
+
"works",
|
|
1386
|
+
"world",
|
|
1387
|
+
"ws",
|
|
1388
|
+
"wtc",
|
|
1389
|
+
"wtf",
|
|
1390
|
+
"xin",
|
|
1391
|
+
"\u6D4B\u8BD5",
|
|
1392
|
+
"\u092A\u0930\u0940\u0915\u094D\u0937\u093E",
|
|
1393
|
+
"\u4F5B\u5C71",
|
|
1394
|
+
"\u6148\u5584",
|
|
1395
|
+
"\u96C6\u56E2",
|
|
1396
|
+
"\u5728\u7EBF",
|
|
1397
|
+
"\uD55C\uAD6D",
|
|
1398
|
+
"\u09AD\u09BE\u09B0\u09A4",
|
|
1399
|
+
"\u516B\u5366",
|
|
1400
|
+
"\u0645\u0648\u0642\u0639",
|
|
1401
|
+
"\u09AC\u09BE\u0982\u09B2\u09BE",
|
|
1402
|
+
"\u516C\u76CA",
|
|
1403
|
+
"\u516C\u53F8",
|
|
1404
|
+
"\u79FB\u52A8",
|
|
1405
|
+
"\u6211\u7231\u4F60",
|
|
1406
|
+
"\u043C\u043E\u0441\u043A\u0432\u0430",
|
|
1407
|
+
"\u0438\u0441\u043F\u044B\u0442\u0430\u043D\u0438\u0435",
|
|
1408
|
+
"\u049B\u0430\u0437",
|
|
1409
|
+
"\u043E\u043D\u043B\u0430\u0439\u043D",
|
|
1410
|
+
"\u0441\u0430\u0439\u0442",
|
|
1411
|
+
"\u0441\u0440\u0431",
|
|
1412
|
+
"\u0431\u0435\u043B",
|
|
1413
|
+
"\u65F6\u5C1A",
|
|
1414
|
+
"\uD14C\uC2A4\uD2B8",
|
|
1415
|
+
"\u6DE1\u9A6C\u9521",
|
|
1416
|
+
"\u043E\u0440\u0433",
|
|
1417
|
+
"\uC0BC\uC131",
|
|
1418
|
+
"\u0B9A\u0BBF\u0B99\u0BCD\u0B95\u0BAA\u0BCD\u0BAA\u0BC2\u0BB0\u0BCD",
|
|
1419
|
+
"\u5546\u6807",
|
|
1420
|
+
"\u5546\u5E97",
|
|
1421
|
+
"\u5546\u57CE",
|
|
1422
|
+
"\u0434\u0435\u0442\u0438",
|
|
1423
|
+
"\u043C\u043A\u0434",
|
|
1424
|
+
"\u05D8\u05E2\u05E1\u05D8",
|
|
1425
|
+
"\u4E2D\u6587\u7F51",
|
|
1426
|
+
"\u4E2D\u4FE1",
|
|
1427
|
+
"\u4E2D\u56FD",
|
|
1428
|
+
"\u4E2D\u570B",
|
|
1429
|
+
"\u8C37\u6B4C",
|
|
1430
|
+
"\u0C2D\u0C3E\u0C30\u0C24\u0C4D",
|
|
1431
|
+
"\u0DBD\u0D82\u0D9A\u0DCF",
|
|
1432
|
+
"\u6E2C\u8A66",
|
|
1433
|
+
"\u0AAD\u0ABE\u0AB0\u0AA4",
|
|
1434
|
+
"\u092D\u093E\u0930\u0924",
|
|
1435
|
+
"\u0622\u0632\u0645\u0627\u06CC\u0634\u06CC",
|
|
1436
|
+
"\u0BAA\u0BB0\u0BBF\u0B9F\u0BCD\u0B9A\u0BC8",
|
|
1437
|
+
"\u7F51\u5E97",
|
|
1438
|
+
"\u0938\u0902\u0917\u0920\u0928",
|
|
1439
|
+
"\u7F51\u7EDC",
|
|
1440
|
+
"\u0443\u043A\u0440",
|
|
1441
|
+
"\u9999\u6E2F",
|
|
1442
|
+
"\u03B4\u03BF\u03BA\u03B9\u03BC\u03AE",
|
|
1443
|
+
"\u98DE\u5229\u6D66",
|
|
1444
|
+
"\u0625\u062E\u062A\u0628\u0627\u0631",
|
|
1445
|
+
"\u53F0\u6E7E",
|
|
1446
|
+
"\u53F0\u7063",
|
|
1447
|
+
"\u624B\u673A",
|
|
1448
|
+
"\u043C\u043E\u043D",
|
|
1449
|
+
"\u0627\u0644\u062C\u0632\u0627\u0626\u0631",
|
|
1450
|
+
"\u0639\u0645\u0627\u0646",
|
|
1451
|
+
"\u0627\u06CC\u0631\u0627\u0646",
|
|
1452
|
+
"\u0627\u0645\u0627\u0631\u0627\u062A",
|
|
1453
|
+
"\u0628\u0627\u0632\u0627\u0631",
|
|
1454
|
+
"\u067E\u0627\u06A9\u0633\u062A\u0627\u0646",
|
|
1455
|
+
"\u0627\u0644\u0627\u0631\u062F\u0646",
|
|
1456
|
+
"\u0628\u06BE\u0627\u0631\u062A",
|
|
1457
|
+
"\u0627\u0644\u0645\u063A\u0631\u0628",
|
|
1458
|
+
"\u0627\u0644\u0633\u0639\u0648\u062F\u064A\u0629",
|
|
1459
|
+
"\u0633\u0648\u062F\u0627\u0646",
|
|
1460
|
+
"\u0639\u0631\u0627\u0642",
|
|
1461
|
+
"\u0645\u0644\u064A\u0633\u064A\u0627",
|
|
1462
|
+
"\u653F\u5E9C",
|
|
1463
|
+
"\u0634\u0628\u0643\u0629",
|
|
1464
|
+
"\u10D2\u10D4",
|
|
1465
|
+
"\u673A\u6784",
|
|
1466
|
+
"\u7EC4\u7EC7\u673A\u6784",
|
|
1467
|
+
"\u5065\u5EB7",
|
|
1468
|
+
"\u0E44\u0E17\u0E22",
|
|
1469
|
+
"\u0633\u0648\u0631\u064A\u0629",
|
|
1470
|
+
"\u0440\u0443\u0441",
|
|
1471
|
+
"\u0440\u0444",
|
|
1472
|
+
"\u062A\u0648\u0646\u0633",
|
|
1473
|
+
"\u307F\u3093\u306A",
|
|
1474
|
+
"\u30B0\u30FC\u30B0\u30EB",
|
|
1475
|
+
"\u4E16\u754C",
|
|
1476
|
+
"\u0A2D\u0A3E\u0A30\u0A24",
|
|
1477
|
+
"\u7F51\u5740",
|
|
1478
|
+
"\u6E38\u620F",
|
|
1479
|
+
"verm\xF6gensberater",
|
|
1480
|
+
"verm\xF6gensberatung",
|
|
1481
|
+
"\u4F01\u4E1A",
|
|
1482
|
+
"\u4FE1\u606F",
|
|
1483
|
+
"\u0645\u0635\u0631",
|
|
1484
|
+
"\u0642\u0637\u0631",
|
|
1485
|
+
"\u5E7F\u4E1C",
|
|
1486
|
+
"\u0B87\u0BB2\u0B99\u0BCD\u0B95\u0BC8",
|
|
1487
|
+
"\u0B87\u0BA8\u0BCD\u0BA4\u0BBF\u0BAF\u0BBE",
|
|
1488
|
+
"\u0570\u0561\u0575",
|
|
1489
|
+
"\u65B0\u52A0\u5761",
|
|
1490
|
+
"\u0641\u0644\u0633\u0637\u064A\u0646",
|
|
1491
|
+
"\u30C6\u30B9\u30C8",
|
|
1492
|
+
"\u653F\u52A1",
|
|
1493
|
+
"xxx",
|
|
1494
|
+
"xyz",
|
|
1495
|
+
"yachts",
|
|
1496
|
+
"yandex",
|
|
1497
|
+
"ye",
|
|
1498
|
+
"yodobashi",
|
|
1499
|
+
"yoga",
|
|
1500
|
+
"yokohama",
|
|
1501
|
+
"youtube",
|
|
1502
|
+
"yt",
|
|
1503
|
+
"za",
|
|
1504
|
+
"zm",
|
|
1505
|
+
"zone",
|
|
1506
|
+
"zuerich",
|
|
1507
|
+
"zw"
|
|
1508
|
+
];
|
|
1509
|
+
var INTERNAL_SUFFIXES = [
|
|
1510
|
+
"local",
|
|
1511
|
+
"internal",
|
|
1512
|
+
"intranet",
|
|
1513
|
+
"corp",
|
|
1514
|
+
"lan",
|
|
1515
|
+
"home",
|
|
1516
|
+
"localdomain",
|
|
1517
|
+
"arpa"
|
|
1518
|
+
];
|
|
1519
|
+
var INTERNAL_COMPOUND_SUFFIXES = [
|
|
1520
|
+
"svc\\.cluster\\.local",
|
|
1521
|
+
"ec2\\.internal",
|
|
1522
|
+
"compute\\.internal"
|
|
1523
|
+
];
|
|
1524
|
+
var FILE_EXT_COLLISION_TLDS = [
|
|
1525
|
+
"ac",
|
|
1526
|
+
"am",
|
|
1527
|
+
"cc",
|
|
1528
|
+
"gs",
|
|
1529
|
+
"hiv",
|
|
1530
|
+
"in",
|
|
1531
|
+
"is",
|
|
1532
|
+
"it",
|
|
1533
|
+
"java",
|
|
1534
|
+
"la",
|
|
1535
|
+
"md",
|
|
1536
|
+
"ml",
|
|
1537
|
+
"pl",
|
|
1538
|
+
"py",
|
|
1539
|
+
"rs",
|
|
1540
|
+
"sh",
|
|
1541
|
+
"so",
|
|
1542
|
+
"tf"
|
|
1543
|
+
];
|
|
1544
|
+
var COLLISION_TLD_SET = new Set(FILE_EXT_COLLISION_TLDS);
|
|
1545
|
+
var GTLD_UNRESTRICTED = GTLD.filter((tld) => !COLLISION_TLD_SET.has(tld));
|
|
1546
|
+
var TLD_EXACT = [...INTERNAL_COMPOUND_SUFFIXES, ...GTLD_UNRESTRICTED, ...INTERNAL_SUFFIXES].join(
|
|
1547
|
+
"|"
|
|
1548
|
+
);
|
|
1549
|
+
var TLD_COLLISION = FILE_EXT_COLLISION_TLDS.join("|");
|
|
1550
|
+
var LABEL = "[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?";
|
|
1551
|
+
var HOST_EXACT = `(?:(?<![\\w.$-])(?<![^:/\\\\][/\\\\])(?:${LABEL}\\.)+(?:${TLD_EXACT})(?!\\.?\\w))`;
|
|
1552
|
+
var HOST_COLLISION = `(?:(?<=(?:https?|sftp|ftps?|wss?)://(?:[^/@\\s]+@)?)(?:${LABEL}\\.)+(?:${TLD_COLLISION})(?!\\.?\\w))`;
|
|
1553
|
+
var UNC_COMPUTER = "(?:(?<=\\\\\\\\)[A-Za-z][A-Za-z0-9-]{0,14}(?=\\\\[A-Za-z0-9$]))";
|
|
1554
|
+
var SPN_HOST = "(?:(?<=\\b[A-Za-z][A-Za-z0-9]*/)[A-Za-z0-9][A-Za-z0-9.-]*(?=@[A-Z0-9.-]+\\b))";
|
|
1555
|
+
var HOST_CTX_KEY = "(?:host|hostname|computer)";
|
|
1556
|
+
var HOST_CTX = `(?:(?<=\\b${HOST_CTX_KEY}\\s*[=:]\\s*)[A-Za-z0-9](?:[A-Za-z0-9.-]*[A-Za-z0-9])?)`;
|
|
1557
|
+
var NETBIOS_AGGRESSIVE = "(?:\\b(?:WIN|DESKTOP)-[A-Z0-9]{7,10}\\b)";
|
|
1558
|
+
var hostnamesRule = {
|
|
1559
|
+
id: "hosts",
|
|
1560
|
+
label: "Hostnames & domains",
|
|
1561
|
+
description: "FQDNs, UNC computer names and Kerberos SPN hosts become the same token everywhere within a session.",
|
|
1562
|
+
mode: "pseudo",
|
|
1563
|
+
token: "HOST",
|
|
1564
|
+
patterns: [HOST_EXACT, HOST_COLLISION, UNC_COMPUTER, SPN_HOST, HOST_CTX],
|
|
1565
|
+
aggressivePatterns: [NETBIOS_AGGRESSIVE],
|
|
1566
|
+
jsonKeys: [
|
|
1567
|
+
"hostname",
|
|
1568
|
+
"host",
|
|
1569
|
+
"fqdn",
|
|
1570
|
+
"domain",
|
|
1571
|
+
"computerName",
|
|
1572
|
+
"serverName",
|
|
1573
|
+
"nodeName",
|
|
1574
|
+
"dnsName",
|
|
1575
|
+
"machineName",
|
|
1576
|
+
"targetHost"
|
|
1577
|
+
]
|
|
1578
|
+
};
|
|
1579
|
+
|
|
1580
|
+
// src/rules/definitions/ipAddresses.ts
|
|
1581
|
+
var IPV4_OCTET = "(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)";
|
|
1582
|
+
var IPV4 = `(?:(?<![\\d.])\\b${IPV4_OCTET}(?:\\.${IPV4_OCTET}){3}\\b(?!\\.\\d))`;
|
|
1583
|
+
var IPV6_SEG = "[0-9a-fA-F]{1,4}";
|
|
1584
|
+
var IPV6_FULL = `(?:${IPV6_SEG}:){7}${IPV6_SEG}`;
|
|
1585
|
+
var IPV6_COMPRESSED = `(?:(?:${IPV6_SEG}:){1,7}:|(?:${IPV6_SEG}:){1,6}:${IPV6_SEG}|(?:${IPV6_SEG}:){1,5}(?::${IPV6_SEG}){1,2}|(?:${IPV6_SEG}:){1,4}(?::${IPV6_SEG}){1,3}|(?:${IPV6_SEG}:){1,3}(?::${IPV6_SEG}){1,4}|(?:${IPV6_SEG}:){1,2}(?::${IPV6_SEG}){1,5}|${IPV6_SEG}:(?::${IPV6_SEG}){1,6}|:(?:(?::${IPV6_SEG}){1,7}|:))`;
|
|
1586
|
+
var IPV4_MAPPED = `(?:::ffff:${IPV4})`;
|
|
1587
|
+
var IPV6 = `(?:(?<![A-Za-z0-9])(?:${IPV4_MAPPED}|${IPV6_FULL}|${IPV6_COMPRESSED})(?![A-Za-z0-9:])(?!\\.\\d)(?:%[A-Za-z0-9_.-]+)?)`;
|
|
1588
|
+
var PTR_V4 = "(?:\\b(?:\\d{1,3}\\.){4}in-addr\\.arpa\\b)";
|
|
1589
|
+
var PTR_V6 = "(?:\\b(?:[0-9a-fA-F]\\.){4,32}ip6\\.arpa\\b)";
|
|
1590
|
+
var MAC_COLON = "(?:(?<!&)\\b[0-9A-Fa-f]{2}(?::[0-9A-Fa-f]{2}){5}\\b)";
|
|
1591
|
+
var MAC_DASH = "(?:\\b[0-9A-Fa-f]{2}(?:-[0-9A-Fa-f]{2}){5}\\b)";
|
|
1592
|
+
var MAC_CISCO = "(?:\\b[0-9A-Fa-f]{4}(?:\\.[0-9A-Fa-f]{4}){2}\\b)";
|
|
1593
|
+
function ipv4ToOctets(ip) {
|
|
1594
|
+
return ip.split(".").map(Number);
|
|
1595
|
+
}
|
|
1596
|
+
function isNetmaskShape(octets) {
|
|
1597
|
+
const bin = octets.map((octet) => octet.toString(2).padStart(8, "0")).join("");
|
|
1598
|
+
return /^1*0*$/.test(bin);
|
|
1599
|
+
}
|
|
1600
|
+
function isReservedIpv4(ip) {
|
|
1601
|
+
const octets = ipv4ToOctets(ip);
|
|
1602
|
+
if (octets.length !== 4 || octets.some((octet) => Number.isNaN(octet))) {
|
|
1603
|
+
return false;
|
|
1604
|
+
}
|
|
1605
|
+
if (octets[0] === 127) {
|
|
1606
|
+
return true;
|
|
1607
|
+
}
|
|
1608
|
+
if (octets[0] === 0) {
|
|
1609
|
+
return true;
|
|
1610
|
+
}
|
|
1611
|
+
if (octets[0] === 169 && octets[1] === 254) {
|
|
1612
|
+
return true;
|
|
1613
|
+
}
|
|
1614
|
+
if (octets[0] >= 224 && octets[0] <= 239) {
|
|
1615
|
+
return true;
|
|
1616
|
+
}
|
|
1617
|
+
return isNetmaskShape(octets);
|
|
1618
|
+
}
|
|
1619
|
+
function validateIp(match) {
|
|
1620
|
+
if (/^\d{1,3}(?:\.\d{1,3}){3}$/.test(match)) {
|
|
1621
|
+
return !isReservedIpv4(match);
|
|
1622
|
+
}
|
|
1623
|
+
const withoutZone = match.split("%")[0] ?? match;
|
|
1624
|
+
if (withoutZone === "::" || withoutZone === "::1") {
|
|
1625
|
+
return false;
|
|
1626
|
+
}
|
|
1627
|
+
const mapped = /^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/.exec(withoutZone);
|
|
1628
|
+
if (mapped?.[1]) {
|
|
1629
|
+
return !isReservedIpv4(mapped[1]);
|
|
1630
|
+
}
|
|
1631
|
+
return true;
|
|
1632
|
+
}
|
|
1633
|
+
var ipAddressesRule = {
|
|
1634
|
+
id: "ips",
|
|
1635
|
+
label: "Network addresses",
|
|
1636
|
+
description: "IPv4/IPv6 addresses, MAC addresses and reverse-DNS (PTR) names become the same token everywhere within a session.",
|
|
1637
|
+
mode: "pseudo",
|
|
1638
|
+
token: "IP",
|
|
1639
|
+
patterns: [PTR_V4, PTR_V6, IPV6, IPV4, MAC_COLON, MAC_DASH, MAC_CISCO],
|
|
1640
|
+
validate: validateIp,
|
|
1641
|
+
jsonKeys: [
|
|
1642
|
+
"ip",
|
|
1643
|
+
"ipAddress",
|
|
1644
|
+
"ip_address",
|
|
1645
|
+
"clientIp",
|
|
1646
|
+
"remoteAddr",
|
|
1647
|
+
"srcIp",
|
|
1648
|
+
"dstIp",
|
|
1649
|
+
"sourceIp",
|
|
1650
|
+
"destinationIp",
|
|
1651
|
+
"xForwardedFor",
|
|
1652
|
+
"mac",
|
|
1653
|
+
"macAddress"
|
|
1654
|
+
]
|
|
1655
|
+
};
|
|
1656
|
+
|
|
1657
|
+
// src/rules/shared/hmacSha256.ts
|
|
1658
|
+
var BLOCK_BYTES2 = 64;
|
|
1659
|
+
var DIGEST_BYTES2 = 32;
|
|
1660
|
+
var K2 = new Uint32Array([
|
|
1661
|
+
1116352408,
|
|
1662
|
+
1899447441,
|
|
1663
|
+
3049323471,
|
|
1664
|
+
3921009573,
|
|
1665
|
+
961987163,
|
|
1666
|
+
1508970993,
|
|
1667
|
+
2453635748,
|
|
1668
|
+
2870763221,
|
|
1669
|
+
3624381080,
|
|
1670
|
+
310598401,
|
|
1671
|
+
607225278,
|
|
1672
|
+
1426881987,
|
|
1673
|
+
1925078388,
|
|
1674
|
+
2162078206,
|
|
1675
|
+
2614888103,
|
|
1676
|
+
3248222580,
|
|
1677
|
+
3835390401,
|
|
1678
|
+
4022224774,
|
|
1679
|
+
264347078,
|
|
1680
|
+
604807628,
|
|
1681
|
+
770255983,
|
|
1682
|
+
1249150122,
|
|
1683
|
+
1555081692,
|
|
1684
|
+
1996064986,
|
|
1685
|
+
2554220882,
|
|
1686
|
+
2821834349,
|
|
1687
|
+
2952996808,
|
|
1688
|
+
3210313671,
|
|
1689
|
+
3336571891,
|
|
1690
|
+
3584528711,
|
|
1691
|
+
113926993,
|
|
1692
|
+
338241895,
|
|
1693
|
+
666307205,
|
|
1694
|
+
773529912,
|
|
1695
|
+
1294757372,
|
|
1696
|
+
1396182291,
|
|
1697
|
+
1695183700,
|
|
1698
|
+
1986661051,
|
|
1699
|
+
2177026350,
|
|
1700
|
+
2456956037,
|
|
1701
|
+
2730485921,
|
|
1702
|
+
2820302411,
|
|
1703
|
+
3259730800,
|
|
1704
|
+
3345764771,
|
|
1705
|
+
3516065817,
|
|
1706
|
+
3600352804,
|
|
1707
|
+
4094571909,
|
|
1708
|
+
275423344,
|
|
1709
|
+
430227734,
|
|
1710
|
+
506948616,
|
|
1711
|
+
659060556,
|
|
1712
|
+
883997877,
|
|
1713
|
+
958139571,
|
|
1714
|
+
1322822218,
|
|
1715
|
+
1537002063,
|
|
1716
|
+
1747873779,
|
|
1717
|
+
1955562222,
|
|
1718
|
+
2024104815,
|
|
1719
|
+
2227730452,
|
|
1720
|
+
2361852424,
|
|
1721
|
+
2428436474,
|
|
1722
|
+
2756734187,
|
|
1723
|
+
3204031479,
|
|
1724
|
+
3329325298
|
|
1725
|
+
]);
|
|
1726
|
+
var INITIAL_STATE2 = new Uint32Array([
|
|
1727
|
+
1779033703,
|
|
1728
|
+
3144134277,
|
|
1729
|
+
1013904242,
|
|
1730
|
+
2773480762,
|
|
1731
|
+
1359893119,
|
|
1732
|
+
2600822924,
|
|
1733
|
+
528734635,
|
|
1734
|
+
1541459225
|
|
1735
|
+
]);
|
|
1736
|
+
function rotr2(value, bits) {
|
|
1737
|
+
return (value >>> bits | value << 32 - bits) >>> 0;
|
|
1738
|
+
}
|
|
1739
|
+
function padMessage2(message) {
|
|
1740
|
+
const bitLength = message.length * 8;
|
|
1741
|
+
const paddedLength = Math.ceil((message.length + 9) / BLOCK_BYTES2) * BLOCK_BYTES2;
|
|
1742
|
+
const padded = new Uint8Array(paddedLength);
|
|
1743
|
+
padded.set(message);
|
|
1744
|
+
padded[message.length] = 128;
|
|
1745
|
+
const view = new DataView(padded.buffer);
|
|
1746
|
+
view.setUint32(paddedLength - 8, Math.floor(bitLength / 2 ** 32), false);
|
|
1747
|
+
view.setUint32(paddedLength - 4, bitLength >>> 0, false);
|
|
1748
|
+
return padded;
|
|
1749
|
+
}
|
|
1750
|
+
function sha2562(message) {
|
|
1751
|
+
const padded = padMessage2(message);
|
|
1752
|
+
const view = new DataView(padded.buffer);
|
|
1753
|
+
const state = Uint32Array.from(INITIAL_STATE2);
|
|
1754
|
+
const w = new Uint32Array(64);
|
|
1755
|
+
for (let offset = 0; offset < padded.length; offset += BLOCK_BYTES2) {
|
|
1756
|
+
for (let i = 0; i < 16; i += 1) {
|
|
1757
|
+
w[i] = view.getUint32(offset + i * 4, false);
|
|
1758
|
+
}
|
|
1759
|
+
for (let i = 16; i < 64; i += 1) {
|
|
1760
|
+
const s0 = rotr2(w[i - 15], 7) ^ rotr2(w[i - 15], 18) ^ w[i - 15] >>> 3;
|
|
1761
|
+
const s1 = rotr2(w[i - 2], 17) ^ rotr2(w[i - 2], 19) ^ w[i - 2] >>> 10;
|
|
1762
|
+
w[i] = w[i - 16] + s0 + w[i - 7] + s1 >>> 0;
|
|
1763
|
+
}
|
|
1764
|
+
let [a, b, c, d, e, f, g, h] = state;
|
|
1765
|
+
for (let i = 0; i < 64; i += 1) {
|
|
1766
|
+
const s1 = rotr2(e, 6) ^ rotr2(e, 11) ^ rotr2(e, 25);
|
|
1767
|
+
const ch = e & f ^ ~e & g;
|
|
1768
|
+
const temp1 = h + s1 + ch + K2[i] + w[i] >>> 0;
|
|
1769
|
+
const s0 = rotr2(a, 2) ^ rotr2(a, 13) ^ rotr2(a, 22);
|
|
1770
|
+
const maj = a & b ^ a & c ^ b & c;
|
|
1771
|
+
const temp2 = s0 + maj >>> 0;
|
|
1772
|
+
h = g;
|
|
1773
|
+
g = f;
|
|
1774
|
+
f = e;
|
|
1775
|
+
e = d + temp1 >>> 0;
|
|
1776
|
+
d = c;
|
|
1777
|
+
c = b;
|
|
1778
|
+
b = a;
|
|
1779
|
+
a = temp1 + temp2 >>> 0;
|
|
1780
|
+
}
|
|
1781
|
+
state[0] = state[0] + a >>> 0;
|
|
1782
|
+
state[1] = state[1] + b >>> 0;
|
|
1783
|
+
state[2] = state[2] + c >>> 0;
|
|
1784
|
+
state[3] = state[3] + d >>> 0;
|
|
1785
|
+
state[4] = state[4] + e >>> 0;
|
|
1786
|
+
state[5] = state[5] + f >>> 0;
|
|
1787
|
+
state[6] = state[6] + g >>> 0;
|
|
1788
|
+
state[7] = state[7] + h >>> 0;
|
|
1789
|
+
}
|
|
1790
|
+
const digest = new Uint8Array(DIGEST_BYTES2);
|
|
1791
|
+
const digestView = new DataView(digest.buffer);
|
|
1792
|
+
for (let i = 0; i < 8; i += 1) {
|
|
1793
|
+
digestView.setUint32(i * 4, state[i], false);
|
|
1794
|
+
}
|
|
1795
|
+
return digest;
|
|
1796
|
+
}
|
|
1797
|
+
|
|
1798
|
+
// src/rules/definitions/paymentInfo.ts
|
|
1799
|
+
var BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
1800
|
+
function luhnCheck2(digits) {
|
|
1801
|
+
if (!/^\d{13,19}$/.test(digits) || /^0+$/.test(digits)) {
|
|
1802
|
+
return false;
|
|
1803
|
+
}
|
|
1804
|
+
let sum = 0;
|
|
1805
|
+
let doubleIt = false;
|
|
1806
|
+
for (let i = digits.length - 1; i >= 0; i -= 1) {
|
|
1807
|
+
let digit = Number(digits[i]);
|
|
1808
|
+
if (doubleIt) {
|
|
1809
|
+
digit *= 2;
|
|
1810
|
+
if (digit > 9) {
|
|
1811
|
+
digit -= 9;
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
sum += digit;
|
|
1815
|
+
doubleIt = !doubleIt;
|
|
1816
|
+
}
|
|
1817
|
+
return sum % 10 === 0;
|
|
1818
|
+
}
|
|
1819
|
+
function ibanMod97(iban) {
|
|
1820
|
+
const compact = iban.replace(/\s/g, "").toUpperCase();
|
|
1821
|
+
if (!/^[A-Z]{2}\d{2}[A-Z0-9]{10,30}$/.test(compact)) {
|
|
1822
|
+
return false;
|
|
1823
|
+
}
|
|
1824
|
+
const rearranged = compact.slice(4) + compact.slice(0, 4);
|
|
1825
|
+
const numeric = rearranged.replace(/[A-Z]/g, (char) => String(char.charCodeAt(0) - 55));
|
|
1826
|
+
let remainder = 0;
|
|
1827
|
+
for (let i = 0; i < numeric.length; i += 1) {
|
|
1828
|
+
remainder = (remainder * 10 + Number(numeric[i])) % 97;
|
|
1829
|
+
}
|
|
1830
|
+
return remainder === 1;
|
|
1831
|
+
}
|
|
1832
|
+
var IBAN_COUNTRY_CODES = /* @__PURE__ */ new Set([
|
|
1833
|
+
"AD",
|
|
1834
|
+
"AE",
|
|
1835
|
+
"AL",
|
|
1836
|
+
"AO",
|
|
1837
|
+
"AT",
|
|
1838
|
+
"AZ",
|
|
1839
|
+
"BA",
|
|
1840
|
+
"BE",
|
|
1841
|
+
"BF",
|
|
1842
|
+
"BG",
|
|
1843
|
+
"BH",
|
|
1844
|
+
"BI",
|
|
1845
|
+
"BJ",
|
|
1846
|
+
"BR",
|
|
1847
|
+
"BY",
|
|
1848
|
+
"CF",
|
|
1849
|
+
"CG",
|
|
1850
|
+
"CH",
|
|
1851
|
+
"CI",
|
|
1852
|
+
"CM",
|
|
1853
|
+
"CR",
|
|
1854
|
+
"CV",
|
|
1855
|
+
"CY",
|
|
1856
|
+
"CZ",
|
|
1857
|
+
"DE",
|
|
1858
|
+
"DJ",
|
|
1859
|
+
"DK",
|
|
1860
|
+
"DO",
|
|
1861
|
+
"DZ",
|
|
1862
|
+
"EE",
|
|
1863
|
+
"EG",
|
|
1864
|
+
"ES",
|
|
1865
|
+
"FI",
|
|
1866
|
+
"FO",
|
|
1867
|
+
"FR",
|
|
1868
|
+
"GA",
|
|
1869
|
+
"GB",
|
|
1870
|
+
"GE",
|
|
1871
|
+
"GI",
|
|
1872
|
+
"GL",
|
|
1873
|
+
"GQ",
|
|
1874
|
+
"GR",
|
|
1875
|
+
"GT",
|
|
1876
|
+
"GW",
|
|
1877
|
+
"HN",
|
|
1878
|
+
"HR",
|
|
1879
|
+
"HU",
|
|
1880
|
+
"IE",
|
|
1881
|
+
"IL",
|
|
1882
|
+
"IQ",
|
|
1883
|
+
"IR",
|
|
1884
|
+
"IS",
|
|
1885
|
+
"IT",
|
|
1886
|
+
"JO",
|
|
1887
|
+
"KM",
|
|
1888
|
+
"KW",
|
|
1889
|
+
"KZ",
|
|
1890
|
+
"LB",
|
|
1891
|
+
"LC",
|
|
1892
|
+
"LI",
|
|
1893
|
+
"LT",
|
|
1894
|
+
"LU",
|
|
1895
|
+
"LV",
|
|
1896
|
+
"LY",
|
|
1897
|
+
"MA",
|
|
1898
|
+
"MC",
|
|
1899
|
+
"MD",
|
|
1900
|
+
"ME",
|
|
1901
|
+
"MG",
|
|
1902
|
+
"MK",
|
|
1903
|
+
"ML",
|
|
1904
|
+
"MR",
|
|
1905
|
+
"MT",
|
|
1906
|
+
"MU",
|
|
1907
|
+
"MZ",
|
|
1908
|
+
"NE",
|
|
1909
|
+
"NI",
|
|
1910
|
+
"NL",
|
|
1911
|
+
"NO",
|
|
1912
|
+
"PK",
|
|
1913
|
+
"PL",
|
|
1914
|
+
"PS",
|
|
1915
|
+
"PT",
|
|
1916
|
+
"QA",
|
|
1917
|
+
"RO",
|
|
1918
|
+
"RS",
|
|
1919
|
+
"RU",
|
|
1920
|
+
"SA",
|
|
1921
|
+
"SC",
|
|
1922
|
+
"SD",
|
|
1923
|
+
"SE",
|
|
1924
|
+
"SI",
|
|
1925
|
+
"SK",
|
|
1926
|
+
"SM",
|
|
1927
|
+
"SN",
|
|
1928
|
+
"ST",
|
|
1929
|
+
"SV",
|
|
1930
|
+
"TD",
|
|
1931
|
+
"TG",
|
|
1932
|
+
"TL",
|
|
1933
|
+
"TN",
|
|
1934
|
+
"TR",
|
|
1935
|
+
"UA",
|
|
1936
|
+
"VA",
|
|
1937
|
+
"VG",
|
|
1938
|
+
"XK"
|
|
1939
|
+
]);
|
|
1940
|
+
function isIbanCountry(compact) {
|
|
1941
|
+
return IBAN_COUNTRY_CODES.has(compact.slice(0, 2).toUpperCase());
|
|
1942
|
+
}
|
|
1943
|
+
function isHexHashLength(value) {
|
|
1944
|
+
return /^(?:[A-Fa-f0-9]{32}|[A-Fa-f0-9]{40}|[A-Fa-f0-9]{64})$/.test(value);
|
|
1945
|
+
}
|
|
1946
|
+
function isCardSeparatorLayout(match) {
|
|
1947
|
+
if (!/[ -]/.test(match)) {
|
|
1948
|
+
return true;
|
|
1949
|
+
}
|
|
1950
|
+
const groups = match.split(/[ -]/).map((group) => group.length);
|
|
1951
|
+
if (groups.some((length) => length === 0)) {
|
|
1952
|
+
return false;
|
|
1953
|
+
}
|
|
1954
|
+
if (groups.length === 3 && groups[0] === 4 && groups[1] === 6 && (groups[2] === 4 || groups[2] === 5)) {
|
|
1955
|
+
return true;
|
|
1956
|
+
}
|
|
1957
|
+
const last = groups[groups.length - 1];
|
|
1958
|
+
return groups.length >= 2 && groups.slice(0, -1).every((length) => length === 4) && last >= 1 && last <= 7;
|
|
1959
|
+
}
|
|
1960
|
+
function bitcoinBase58Check(address) {
|
|
1961
|
+
const decoded = decodeBase58(address);
|
|
1962
|
+
if (!decoded || decoded.length < 5) {
|
|
1963
|
+
return false;
|
|
1964
|
+
}
|
|
1965
|
+
const payload = decoded.subarray(0, decoded.length - 4);
|
|
1966
|
+
const checksum = decoded.subarray(decoded.length - 4);
|
|
1967
|
+
const digest = sha2562(sha2562(payload));
|
|
1968
|
+
return digest[0] === checksum[0] && digest[1] === checksum[1] && digest[2] === checksum[2] && digest[3] === checksum[3];
|
|
1969
|
+
}
|
|
1970
|
+
function decodeBase58(input) {
|
|
1971
|
+
const bytes = [0];
|
|
1972
|
+
for (let i = 0; i < input.length; i += 1) {
|
|
1973
|
+
let carry = BASE58_ALPHABET.indexOf(input[i]);
|
|
1974
|
+
if (carry === -1) {
|
|
1975
|
+
return null;
|
|
1976
|
+
}
|
|
1977
|
+
for (let j = 0; j < bytes.length; j += 1) {
|
|
1978
|
+
carry += bytes[j] * 58;
|
|
1979
|
+
bytes[j] = carry % 256;
|
|
1980
|
+
carry = Math.floor(carry / 256);
|
|
1981
|
+
}
|
|
1982
|
+
while (carry > 0) {
|
|
1983
|
+
bytes.push(carry % 256);
|
|
1984
|
+
carry = Math.floor(carry / 256);
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
let leadingOnes = 0;
|
|
1988
|
+
while (leadingOnes < input.length && input[leadingOnes] === "1") {
|
|
1989
|
+
leadingOnes += 1;
|
|
1990
|
+
}
|
|
1991
|
+
let end = bytes.length;
|
|
1992
|
+
while (end > 0 && bytes[end - 1] === 0) {
|
|
1993
|
+
end -= 1;
|
|
1994
|
+
}
|
|
1995
|
+
const out = new Uint8Array(leadingOnes + end);
|
|
1996
|
+
for (let i = 0; i < end; i += 1) {
|
|
1997
|
+
out[leadingOnes + end - 1 - i] = bytes[i];
|
|
1998
|
+
}
|
|
1999
|
+
return out;
|
|
2000
|
+
}
|
|
2001
|
+
function isKnownCardIin(digits) {
|
|
2002
|
+
const { length } = digits;
|
|
2003
|
+
const d2 = Number(digits.slice(0, 2));
|
|
2004
|
+
const d4 = Number(digits.slice(0, 4));
|
|
2005
|
+
switch (digits[0]) {
|
|
2006
|
+
case "4":
|
|
2007
|
+
return length === 13 || length === 16 || length === 19;
|
|
2008
|
+
case "5":
|
|
2009
|
+
return length === 16 && d2 >= 51 && d2 <= 55;
|
|
2010
|
+
case "2":
|
|
2011
|
+
return length === 16 && d4 >= 2221 && d4 <= 2720;
|
|
2012
|
+
case "3":
|
|
2013
|
+
if (length === 15) {
|
|
2014
|
+
return d2 === 34 || d2 === 37;
|
|
2015
|
+
}
|
|
2016
|
+
if (length === 14) {
|
|
2017
|
+
return d2 === 36 || d2 === 38 || d2 === 39 || d4 >= 3e3 && d4 <= 3059 || d4 === 3095;
|
|
2018
|
+
}
|
|
2019
|
+
return length >= 16 && length <= 19 && d4 >= 3528 && d4 <= 3589;
|
|
2020
|
+
case "6":
|
|
2021
|
+
return length >= 16 && length <= 19 && (digits.startsWith("6011") || digits.startsWith("62") || digits.startsWith("64") || digits.startsWith("65"));
|
|
2022
|
+
default:
|
|
2023
|
+
return false;
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
function validatePayment(match) {
|
|
2027
|
+
const compactIban = match.replace(/\s/g, "");
|
|
2028
|
+
if (/^[A-Za-z]{2}\d{2}[A-Za-z0-9]+$/.test(compactIban)) {
|
|
2029
|
+
return !isHexHashLength(compactIban) && isIbanCountry(compactIban) && ibanMod97(match);
|
|
2030
|
+
}
|
|
2031
|
+
const compactPan = match.replace(/[ -]/g, "");
|
|
2032
|
+
if (/^[\d -]+$/.test(match) && /^\d{13,19}$/.test(compactPan)) {
|
|
2033
|
+
const grouped = /[ -]/.test(match);
|
|
2034
|
+
if (!grouped && compactPan.length > 16) {
|
|
2035
|
+
return false;
|
|
2036
|
+
}
|
|
2037
|
+
return isCardSeparatorLayout(match) && isKnownCardIin(compactPan) && luhnCheck2(compactPan);
|
|
2038
|
+
}
|
|
2039
|
+
if (/^[13][A-HJ-NP-Za-km-z1-9]{25,34}$/.test(match)) {
|
|
2040
|
+
return bitcoinBase58Check(match);
|
|
2041
|
+
}
|
|
2042
|
+
return true;
|
|
2043
|
+
}
|
|
2044
|
+
var CVV_KEY = "(?:[Cc][Vv][Vv]2?|[Cc][Vv][Cc]2?|[Cc][Ii][Dd])";
|
|
2045
|
+
var EXP_KEY = "(?:[Ee]xp(?:iry|iration)?|[Ee]xp[_-]?[Dd]ate)";
|
|
2046
|
+
var ROUTING_KEY = "(?:[Rr]outing(?:[_-]?[Nn]umber)?|[Aa][Bb][Aa])";
|
|
2047
|
+
var ACCOUNT_KEY = "(?:[Aa]ccount[_-]?[Nn]umber|[Aa]cct)";
|
|
2048
|
+
var paymentInfoRule = {
|
|
2049
|
+
id: "paymentInfo",
|
|
2050
|
+
label: "Payment info",
|
|
2051
|
+
description: "IBANs, card numbers (PAN, Luhn-checked), CVV/CVC, expiry dates and crypto addresses are redacted outright.",
|
|
2052
|
+
mode: "mask",
|
|
2053
|
+
token: "PAYMENT",
|
|
2054
|
+
patterns: [
|
|
2055
|
+
"(?:(?<![.\\d])\\b\\d(?:[ -]?\\d){12,18}\\b)",
|
|
2056
|
+
"(?:\\b[A-Z]{2}\\d{2}[A-Z0-9]{10,30}\\b)",
|
|
2057
|
+
"(?:\\b[A-Z]{2}\\d{2}(?: [A-Z0-9]{4}){2,7}(?: [A-Z0-9]{1,4})?\\b)",
|
|
2058
|
+
`(?:(?<=\\b${CVV_KEY}\\s*[=:]\\s*)\\d{3,4}\\b)`,
|
|
2059
|
+
`(?:(?<=\\b${EXP_KEY}\\s*[=:]\\s*)(?:0[1-9]|1[0-2])[/-](?:\\d{2}|\\d{4})\\b)`,
|
|
2060
|
+
"(?:\\bbc1[a-z0-9]{25,39}\\b)",
|
|
2061
|
+
"(?:(?<![A-Za-z0-9])[13][A-HJ-NP-Za-km-z1-9]{25,34}(?![A-Za-z0-9]))",
|
|
2062
|
+
"(?:\\b0x[A-Fa-f0-9]{40}\\b)"
|
|
2063
|
+
],
|
|
2064
|
+
aggressivePatterns: [
|
|
2065
|
+
"(?:(?<![\\\\/])\\b[A-Z]{6}[A-Z0-9]{2}(?:[A-Z0-9]{3})?\\b(?![\\\\/]))",
|
|
2066
|
+
`(?:(?<=\\b${ROUTING_KEY}\\s*[=:]\\s*)\\d{9}\\b)`,
|
|
2067
|
+
`(?:(?<=\\b${ACCOUNT_KEY}\\s*[=:]\\s*)\\d{8,12}\\b)`
|
|
2068
|
+
],
|
|
2069
|
+
validate: validatePayment,
|
|
2070
|
+
jsonKeys: [
|
|
2071
|
+
"iban",
|
|
2072
|
+
"cardNumber",
|
|
2073
|
+
"pan",
|
|
2074
|
+
"cvv",
|
|
2075
|
+
"cvc",
|
|
2076
|
+
"card_number",
|
|
2077
|
+
"creditCard",
|
|
2078
|
+
"cvv2",
|
|
2079
|
+
"accountNumber",
|
|
2080
|
+
"routingNumber",
|
|
2081
|
+
"bic",
|
|
2082
|
+
"swift",
|
|
2083
|
+
"expiryDate"
|
|
2084
|
+
]
|
|
2085
|
+
};
|
|
2086
|
+
|
|
2087
|
+
// src/rules/definitions/phoneNumbers.ts
|
|
2088
|
+
function validatePhone(match) {
|
|
2089
|
+
const digits = match.replace(/\D/g, "");
|
|
2090
|
+
const national = digits.startsWith("00") ? digits.slice(2) : digits;
|
|
2091
|
+
return national.length >= 7 && national.length <= 15;
|
|
2092
|
+
}
|
|
2093
|
+
var PHONE_KEY = "(?:telephone|phone|mobile|msisdn|cell|fax|tel)";
|
|
2094
|
+
var phoneNumbersRule = {
|
|
2095
|
+
id: "phoneNumbers",
|
|
2096
|
+
label: "Phone numbers",
|
|
2097
|
+
description: "Phone numbers become a stable <PHONE:\u2026> token \u2014 the same number maps to the same token everywhere within a session.",
|
|
2098
|
+
mode: "pseudo",
|
|
2099
|
+
token: "PHONE",
|
|
2100
|
+
patterns: [
|
|
2101
|
+
"(?:(?<![A-Za-z0-9])\\+[1-9](?:[\\s().-]*\\d){6,14}(?!\\d))",
|
|
2102
|
+
"(?:\\(\\d{3}\\)\\s?\\d{3}-\\d{4}(?!\\d))",
|
|
2103
|
+
"(?:\\b\\d{3}-\\d{3}-\\d{4}\\b)",
|
|
2104
|
+
"(?:\\b1-\\d{3}-\\d{3}-\\d{4}\\b)",
|
|
2105
|
+
"(?:\\b0\\d{2}-\\d{3}-\\d{2}-\\d{2}\\b)",
|
|
2106
|
+
"(?:\\b0\\d{2}\\s\\d{3}\\s\\d{2}\\s\\d{2}\\b)",
|
|
2107
|
+
"(?:(?<![.\\d])\\b00[1-9](?:[\\s().-]*\\d){7,13}(?!\\d))",
|
|
2108
|
+
"(?:(?<=\\b(?:tel|sms):)\\+?[1-9](?:[\\s().-]*\\d){6,14}(?!\\d))",
|
|
2109
|
+
`(?:(?<=\\b${PHONE_KEY}\\s*[=:]\\s*)\\+?[0-9](?:[\\s().-]*\\d){6,14}(?!\\d))`
|
|
2110
|
+
],
|
|
2111
|
+
validate: validatePhone,
|
|
2112
|
+
jsonKeys: [
|
|
2113
|
+
"phone",
|
|
2114
|
+
"phoneNumber",
|
|
2115
|
+
"phone_number",
|
|
2116
|
+
"mobile",
|
|
2117
|
+
"mobileNumber",
|
|
2118
|
+
"cell",
|
|
2119
|
+
"telephone",
|
|
2120
|
+
"tel",
|
|
2121
|
+
"fax",
|
|
2122
|
+
"msisdn",
|
|
2123
|
+
"contactNumber"
|
|
2124
|
+
]
|
|
2125
|
+
};
|
|
2126
|
+
|
|
2127
|
+
// src/rules/definitions/secrets.ts
|
|
2128
|
+
var ENTROPY_THRESHOLD = 3.5;
|
|
2129
|
+
function shannonEntropy(value) {
|
|
2130
|
+
const { length } = value;
|
|
2131
|
+
if (length === 0) {
|
|
2132
|
+
return 0;
|
|
2133
|
+
}
|
|
2134
|
+
const freq = {};
|
|
2135
|
+
for (let i = 0; i < length; i += 1) {
|
|
2136
|
+
const char = value[i];
|
|
2137
|
+
freq[char] = (freq[char] ?? 0) + 1;
|
|
2138
|
+
}
|
|
2139
|
+
return Object.values(freq).reduce((entropy, count) => {
|
|
2140
|
+
const p = count / length;
|
|
2141
|
+
return entropy - p * Math.log2(p);
|
|
2142
|
+
}, 0);
|
|
2143
|
+
}
|
|
2144
|
+
function isHexBlob(match) {
|
|
2145
|
+
return match.length >= 32 && /^[A-Fa-f0-9]+$/.test(match);
|
|
2146
|
+
}
|
|
2147
|
+
function isBase64Blob(match) {
|
|
2148
|
+
return match.length >= 40 && /^[A-Za-z0-9+/]+=*$/.test(match);
|
|
2149
|
+
}
|
|
2150
|
+
function validateSecret(match) {
|
|
2151
|
+
if (isHexBlob(match)) {
|
|
2152
|
+
return shannonEntropy(match) > ENTROPY_THRESHOLD;
|
|
2153
|
+
}
|
|
2154
|
+
if (isBase64Blob(match)) {
|
|
2155
|
+
return shannonEntropy(match) > ENTROPY_THRESHOLD && /[A-Za-z]/.test(match) && /[0-9+/]/.test(match);
|
|
2156
|
+
}
|
|
2157
|
+
return true;
|
|
2158
|
+
}
|
|
2159
|
+
var CONTEXT_KEYS = "(?:api[_-]?key|apikey|access[_-]?token|refresh[_-]?token|id[_-]?token|client[_-]?secret|secret[_-]?key|private[_-]?key|password|passwd|pwd|pass|auth[_-]?token|signing[_-]?key)";
|
|
2160
|
+
var CONTEXT_VALUE = `[^\\s,;}"']{4,}`;
|
|
2161
|
+
var CLI_FLAGS = "(?:password|secret|token|api-key|apikey|access-token|client-secret)";
|
|
2162
|
+
var secretsRule = {
|
|
2163
|
+
id: "secrets",
|
|
2164
|
+
label: "Tokens & credentials",
|
|
2165
|
+
description: "Bearer tokens, JWTs and API keys are redacted outright \u2014 they are not meant to be traced.",
|
|
2166
|
+
mode: "mask",
|
|
2167
|
+
token: "SECRET",
|
|
2168
|
+
patterns: [
|
|
2169
|
+
"(?:(?<=(?:Bearer|bearer|BEARER)\\s+)[A-Za-z0-9._\\-+/=]{8,})",
|
|
2170
|
+
"(?:(?<=(?:Basic|basic|BASIC)\\s+)[A-Za-z0-9+/]{8,}={0,2})",
|
|
2171
|
+
"(?:(?<![A-Za-z0-9_-])eyJ[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}(?:\\.[A-Za-z0-9_-]{8,})?)",
|
|
2172
|
+
"(?:-----BEGIN (?:(?:RSA |EC |DSA |OPENSSH |ENCRYPTED )?PRIVATE KEY|CERTIFICATE)-----[A-Za-z0-9+/=\\s\\\\]+?-----END (?:(?:RSA |EC |DSA |OPENSSH |ENCRYPTED )?PRIVATE KEY|CERTIFICATE)-----)",
|
|
2173
|
+
"(?:\\b(?:AKIA|ASIA|ABIA|ACCA)[A-Z0-9]{16}\\b)",
|
|
2174
|
+
"(?:(?<=(?:aws_secret_access_key|AWS_SECRET_ACCESS_KEY)\\s*[=:]\\s*)[A-Za-z0-9/+=]{30,})",
|
|
2175
|
+
"(?:github_pat_[A-Za-z0-9_]{22,})",
|
|
2176
|
+
"(?:gh[pousr]_[A-Za-z0-9]{36,})",
|
|
2177
|
+
"(?:xox[abprs]-[A-Za-z0-9-]{10,})",
|
|
2178
|
+
"(?:hooks\\.slack\\.com/services/[A-Za-z0-9/_-]{16,})",
|
|
2179
|
+
"(?:AIza[A-Za-z0-9_-]{35})",
|
|
2180
|
+
"(?:(?:sk_live_|sk_test_|rk_live_|rk_test_)[A-Za-z0-9]{16,})",
|
|
2181
|
+
"(?:sk-proj-[A-Za-z0-9_-]{16,})",
|
|
2182
|
+
"(?:sk-[A-Za-z0-9]{20,})",
|
|
2183
|
+
"(?:(?<=(?:[Tt]wilio.{0,60}|\\b(?:[Aa]ccount[Ss]id|[Aa]uth[Tt]oken|[Ss]id)\\s*[=:]\\s*))(?:AC|SK)[0-9A-Fa-f]{32})",
|
|
2184
|
+
"(?:SG\\.[A-Za-z0-9_-]{10,}\\.[A-Za-z0-9_-]{10,})",
|
|
2185
|
+
"(?:npm_[A-Za-z0-9]{36,})",
|
|
2186
|
+
`(?:(?<=[?&][Ss]ig=)[^&\\s"']{16,})`,
|
|
2187
|
+
"(?:(?<=[a-zA-Z][a-zA-Z0-9+.-]*://)[^/@\\s:]+:[^/@\\s]+@)",
|
|
2188
|
+
`(?:(?<=--${CLI_FLAGS}(?:\\s+|=)"?)[^\\s"']{4,})`,
|
|
2189
|
+
`(?:(?<=\\b${CONTEXT_KEYS}"?\\s*[=:]\\s*"?)${CONTEXT_VALUE})`
|
|
2190
|
+
],
|
|
2191
|
+
aggressivePatterns: [
|
|
2192
|
+
"(?:(?<![A-Za-z0-9+/])[A-Za-z0-9+/]{40,}={0,2}(?![A-Za-z0-9+/=]))",
|
|
2193
|
+
"(?:\\b[A-Fa-f0-9]{32,}\\b)"
|
|
2194
|
+
],
|
|
2195
|
+
validate: validateSecret,
|
|
2196
|
+
jsonKeys: [
|
|
2197
|
+
"password",
|
|
2198
|
+
"token",
|
|
2199
|
+
"authorization",
|
|
2200
|
+
"apiKey",
|
|
2201
|
+
"secret",
|
|
2202
|
+
"accessToken",
|
|
2203
|
+
"refreshToken",
|
|
2204
|
+
"passwd",
|
|
2205
|
+
"pwd",
|
|
2206
|
+
"pass",
|
|
2207
|
+
"clientSecret",
|
|
2208
|
+
"privateKey",
|
|
2209
|
+
"secretKey",
|
|
2210
|
+
"apiSecret",
|
|
2211
|
+
"credentials",
|
|
2212
|
+
"auth",
|
|
2213
|
+
"xApiKey",
|
|
2214
|
+
"idToken",
|
|
2215
|
+
"awsSecretAccessKey",
|
|
2216
|
+
"signature"
|
|
2217
|
+
]
|
|
2218
|
+
};
|
|
2219
|
+
|
|
2220
|
+
// src/rules/definitions/sessionCookies.ts
|
|
2221
|
+
var COOKIE_VALUE = "[^;\\s]{8,}";
|
|
2222
|
+
var sessionCookiesRule = {
|
|
2223
|
+
id: "sessionCookies",
|
|
2224
|
+
label: "Session cookies",
|
|
2225
|
+
description: "Session identifiers (sessionid, sid, JSESSIONID, PHPSESSID) are redacted outright.",
|
|
2226
|
+
mode: "mask",
|
|
2227
|
+
token: "SESSION",
|
|
2228
|
+
patterns: [
|
|
2229
|
+
`(?:(?<![\\w-])(?:[Ss]essionid|[Ss]ession_id|sessionId|SESSIONID)=${COOKIE_VALUE})`,
|
|
2230
|
+
`(?:(?<![\\w-])sid=${COOKIE_VALUE})`,
|
|
2231
|
+
`(?:PHPSESSID=${COOKIE_VALUE})`,
|
|
2232
|
+
`(?:JSESSIONID=${COOKIE_VALUE})`,
|
|
2233
|
+
`(?:ASP\\.NET_SessionId=${COOKIE_VALUE})`,
|
|
2234
|
+
`(?:ASPSESSIONID[A-Za-z0-9]*=${COOKIE_VALUE})`,
|
|
2235
|
+
`(?:connect\\.sid=${COOKIE_VALUE})`,
|
|
2236
|
+
`(?:laravel_session=${COOKIE_VALUE})`,
|
|
2237
|
+
`(?:_session_id=${COOKIE_VALUE})`,
|
|
2238
|
+
`(?:(?:csrftoken|csrf_token|XSRF-TOKEN|xsrf-token)=${COOKIE_VALUE})`,
|
|
2239
|
+
`(?:remember_token=${COOKIE_VALUE})`,
|
|
2240
|
+
`(?:(?:SSESS|SESS)[A-Fa-f0-9]{32}=${COOKIE_VALUE})`,
|
|
2241
|
+
`(?:(?<=[Ss]et-[Cc]ookie:\\s*)[^;=\\s]+=${COOKIE_VALUE})`
|
|
2242
|
+
],
|
|
2243
|
+
aggressivePatterns: [
|
|
2244
|
+
`(?:(?<![\\w-])[\\w-]*session[\\w-]*=${COOKIE_VALUE})`,
|
|
2245
|
+
`(?:(?<![\\w-])[\\w-]*token=${COOKIE_VALUE})`
|
|
2246
|
+
],
|
|
2247
|
+
jsonKeys: ["sessionId", "sid", "cookie", "cookies", "setCookie", "jsessionid", "csrfToken"]
|
|
2248
|
+
};
|
|
2249
|
+
|
|
2250
|
+
// src/rules/definitions/users.ts
|
|
2251
|
+
var EMAIL_LOCAL = "[A-Za-z0-9._%+-]+";
|
|
2252
|
+
var EMAIL_DOMAIN = "[A-Za-z0-9.-]+\\.[A-Za-z]{2,}";
|
|
2253
|
+
var EMAIL = `(?:\\b${EMAIL_LOCAL}@${EMAIL_DOMAIN})`;
|
|
2254
|
+
var EMAIL_IP_LITERAL = `(?:\\b${EMAIL_LOCAL}@\\[[0-9a-fA-F.:]+\\])`;
|
|
2255
|
+
var WIN_DOMAIN_EXCLUDE = "(?:BUILTIN|NT SERVICE|NT AUTHORITY|Users|Settings|Files)\\\\";
|
|
2256
|
+
var USER_SEG_SIMPLE = "[A-Za-z0-9_](?:[A-Za-z0-9_.$-]*[A-Za-z0-9$])";
|
|
2257
|
+
var FILE_EXT = "exe|dll|sys|bat|cmd|ps1|vbs|msi|tmp|hiv|log|dat|txt|ini|xml|json|lnk|cpl|ocx|drv";
|
|
2258
|
+
var NOT_FILENAME = `(?![A-Za-z0-9_$.-]*\\.(?:${FILE_EXT})(?![A-Za-z0-9]))`;
|
|
2259
|
+
var NOT_GUID = "(?![0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-)";
|
|
2260
|
+
var ACCOUNT_END = "(?![A-Za-z0-9_$\\\\/-])";
|
|
2261
|
+
var PATH_ROOT = "(?:\\b(?:HKLM|HKCU|HKU|HKCR|HKEY_[A-Z_]+)|\\b[A-Za-z]:)\\\\";
|
|
2262
|
+
var NOT_UNDER_PATH_ROOT = `(?<!${PATH_ROOT}(?:(?! [-/])[^<>'"\\n])*)`;
|
|
2263
|
+
var DOMAIN_USER = `(?:(?<!${WIN_DOMAIN_EXCLUDE})(?<=(?:^|[^\\\\.-])\\b[A-Za-z0-9](?:[A-Za-z0-9.-]{0,13}[A-Za-z0-9])?\\\\)${NOT_FILENAME}${NOT_GUID}${USER_SEG_SIMPLE}${ACCOUNT_END}${NOT_UNDER_PATH_ROOT})`;
|
|
2264
|
+
var SID = "(?:\\bS-1-(?:\\d+-){1,14}\\d+\\b)";
|
|
2265
|
+
var VALUE_WITH_LETTER = "[\\w.@+-]*[A-Za-z][\\w.@+-]*";
|
|
2266
|
+
var LDAP_DN_SINGLE = `(?:(?<=\\b(?:uid|sAMAccountName)=)${VALUE_WITH_LETTER})`;
|
|
2267
|
+
var CN_VALUE = "(?:[A-Za-z0-9.'-]+(?: [A-Za-z0-9.'-]+){0,3})";
|
|
2268
|
+
var LDAP_DN_CN = `(?:(?<=\\bcn=)${CN_VALUE})`;
|
|
2269
|
+
var ARN_USER = "(?:(?<=:user/)[\\w.@+=,-]+)";
|
|
2270
|
+
var ARN_ASSUMED_ROLE_SESSION = "(?:(?<=:assumed-role/[\\w+=,.@-]+/)[\\w.@+=,-]+)";
|
|
2271
|
+
var K8S_SERVICE_ACCOUNT = "(?:(?<=\\bsystem:serviceaccount:[A-Za-z0-9-]+:)[A-Za-z0-9-]+)";
|
|
2272
|
+
var SSHD_FOR_USER = "(?:(?<=(?:\\bfor (?:invalid )?user|\\b[Ii]nvalid user)\\s+)[A-Za-z0-9._-]+)";
|
|
2273
|
+
var SSHD_RUSER = "(?:(?<=\\bruser=)[A-Za-z0-9._-]+)";
|
|
2274
|
+
var SSHD_LOGNAME = "(?:(?<=\\blogname=)[A-Za-z0-9._-]+)";
|
|
2275
|
+
var SUDO_USER = "(?:(?<=\\bsudo:\\s+)[A-Za-z0-9._-]+(?=\\s+:))";
|
|
2276
|
+
var GENERIC_KEY = "(?:user|username|login|account|owner|actor|principal|requester|createdBy|modifiedBy|assignee|operator)";
|
|
2277
|
+
var GENERIC_CTX = `(?:(?<=\\b${GENERIC_KEY}\\s*[=:]\\s*)${VALUE_WITH_LETTER})`;
|
|
2278
|
+
var SLACK_USER_ID_AGGRESSIVE = "(?:\\bU[A-Z0-9]{8,10}\\b)";
|
|
2279
|
+
var SYSTEM_ACCOUNTS = /* @__PURE__ */ new Set([
|
|
2280
|
+
"root",
|
|
2281
|
+
"system",
|
|
2282
|
+
"daemon",
|
|
2283
|
+
"nobody",
|
|
2284
|
+
"www-data",
|
|
2285
|
+
"nginx",
|
|
2286
|
+
"sshd",
|
|
2287
|
+
"syslog"
|
|
2288
|
+
]);
|
|
2289
|
+
function isWellKnownWindowsAccount(match) {
|
|
2290
|
+
const upper = match.toUpperCase();
|
|
2291
|
+
if (upper === "SYSTEM" || upper === "LOCAL" || upper === "NETWORK" || upper === "ANONYMOUS") {
|
|
2292
|
+
return true;
|
|
2293
|
+
}
|
|
2294
|
+
return match.endsWith("$");
|
|
2295
|
+
}
|
|
2296
|
+
function isWellKnownSid(match) {
|
|
2297
|
+
if (match === "S-1-1-0") {
|
|
2298
|
+
return true;
|
|
2299
|
+
}
|
|
2300
|
+
if (/^S-1-5-(?:18|19|20)$/.test(match)) {
|
|
2301
|
+
return true;
|
|
2302
|
+
}
|
|
2303
|
+
return /^S-1-5-32-\d+$/.test(match);
|
|
2304
|
+
}
|
|
2305
|
+
function validateUser(match) {
|
|
2306
|
+
if (isWellKnownWindowsAccount(match)) {
|
|
2307
|
+
return false;
|
|
2308
|
+
}
|
|
2309
|
+
if (isWellKnownSid(match)) {
|
|
2310
|
+
return false;
|
|
2311
|
+
}
|
|
2312
|
+
return !SYSTEM_ACCOUNTS.has(match.toLowerCase());
|
|
2313
|
+
}
|
|
2314
|
+
var usersRule = {
|
|
2315
|
+
id: "users",
|
|
2316
|
+
label: "Usernames & emails",
|
|
2317
|
+
description: "Email addresses and account identifiers (DOMAIN\\user, SIDs, ARNs, LDAP DNs, sshd/PAM context) become the same token everywhere within a session.",
|
|
2318
|
+
mode: "pseudo",
|
|
2319
|
+
token: "USER",
|
|
2320
|
+
patterns: [
|
|
2321
|
+
EMAIL_IP_LITERAL,
|
|
2322
|
+
EMAIL,
|
|
2323
|
+
DOMAIN_USER,
|
|
2324
|
+
SID,
|
|
2325
|
+
LDAP_DN_SINGLE,
|
|
2326
|
+
LDAP_DN_CN,
|
|
2327
|
+
ARN_USER,
|
|
2328
|
+
ARN_ASSUMED_ROLE_SESSION,
|
|
2329
|
+
K8S_SERVICE_ACCOUNT,
|
|
2330
|
+
SSHD_FOR_USER,
|
|
2331
|
+
SSHD_RUSER,
|
|
2332
|
+
SSHD_LOGNAME,
|
|
2333
|
+
SUDO_USER,
|
|
2334
|
+
GENERIC_CTX
|
|
2335
|
+
],
|
|
2336
|
+
aggressivePatterns: [SLACK_USER_ID_AGGRESSIVE],
|
|
2337
|
+
validate: validateUser,
|
|
2338
|
+
jsonKeys: [
|
|
2339
|
+
"email",
|
|
2340
|
+
"username",
|
|
2341
|
+
"user",
|
|
2342
|
+
"emailAddress",
|
|
2343
|
+
"mail",
|
|
2344
|
+
"userEmail",
|
|
2345
|
+
"user_name",
|
|
2346
|
+
"login",
|
|
2347
|
+
"account",
|
|
2348
|
+
"samAccountName",
|
|
2349
|
+
"userPrincipalName",
|
|
2350
|
+
"upn",
|
|
2351
|
+
"actor",
|
|
2352
|
+
"principal",
|
|
2353
|
+
"owner",
|
|
2354
|
+
"createdBy",
|
|
2355
|
+
"modifiedBy",
|
|
2356
|
+
"firstName",
|
|
2357
|
+
"lastName",
|
|
2358
|
+
"fullName",
|
|
2359
|
+
"displayName",
|
|
2360
|
+
"givenName",
|
|
2361
|
+
"surname",
|
|
2362
|
+
"userId",
|
|
2363
|
+
"uid"
|
|
2364
|
+
]
|
|
2365
|
+
};
|
|
2366
|
+
|
|
2367
|
+
// src/rules/registry.ts
|
|
2368
|
+
var builtinRules = [
|
|
2369
|
+
secretsRule,
|
|
2370
|
+
sessionCookiesRule,
|
|
2371
|
+
paymentInfoRule,
|
|
2372
|
+
govIdentifiersRule,
|
|
2373
|
+
healthInfoRule,
|
|
2374
|
+
phoneNumbersRule,
|
|
2375
|
+
ipAddressesRule,
|
|
2376
|
+
hostnamesRule,
|
|
2377
|
+
usersRule,
|
|
2378
|
+
geoLocationRule,
|
|
2379
|
+
homePathsRule
|
|
2380
|
+
];
|
|
2381
|
+
var builtinRuleIds = builtinRules.map(
|
|
2382
|
+
(rule) => rule.id
|
|
2383
|
+
);
|
|
2384
|
+
var builtinsById = new Map(builtinRules.map((rule) => [rule.id, rule]));
|
|
2385
|
+
function getBuiltinRule(id) {
|
|
2386
|
+
return builtinsById.get(id);
|
|
2387
|
+
}
|
|
2388
|
+
|
|
2389
|
+
// src/core/allowlist.ts
|
|
2390
|
+
function patternSource(pattern) {
|
|
2391
|
+
return typeof pattern === "string" ? pattern : pattern.source;
|
|
2392
|
+
}
|
|
2393
|
+
function compileAnchored(patterns) {
|
|
2394
|
+
const sources = patterns.map(patternSource).filter((source) => source.length > 0);
|
|
2395
|
+
if (sources.length === 0) {
|
|
2396
|
+
return null;
|
|
2397
|
+
}
|
|
2398
|
+
const combined = sources.map((source) => `(?:${source})`).join("|");
|
|
2399
|
+
try {
|
|
2400
|
+
return new RegExp(`^(?:${combined})$`, "u");
|
|
2401
|
+
} catch (cause) {
|
|
2402
|
+
throw new InvalidOptionError(
|
|
2403
|
+
`neverRedact.patterns contains an invalid regular expression: ${String(cause)}`
|
|
2404
|
+
);
|
|
2405
|
+
}
|
|
2406
|
+
}
|
|
2407
|
+
function createAllowList(options) {
|
|
2408
|
+
const values = new Set((options?.values ?? []).filter((value) => value.length > 0));
|
|
2409
|
+
const pattern = compileAnchored(options?.patterns ?? []);
|
|
2410
|
+
const byRule = /* @__PURE__ */ new Map();
|
|
2411
|
+
for (const entry of options?.byRule ?? []) {
|
|
2412
|
+
const existing = byRule.get(entry.ruleId) ?? /* @__PURE__ */ new Set();
|
|
2413
|
+
for (const value of entry.values) {
|
|
2414
|
+
if (value.length > 0) {
|
|
2415
|
+
existing.add(value);
|
|
2416
|
+
}
|
|
2417
|
+
}
|
|
2418
|
+
byRule.set(entry.ruleId, existing);
|
|
2419
|
+
}
|
|
2420
|
+
return {
|
|
2421
|
+
empty: values.size === 0 && pattern === null && byRule.size === 0,
|
|
2422
|
+
values,
|
|
2423
|
+
pattern,
|
|
2424
|
+
byRule
|
|
2425
|
+
};
|
|
2426
|
+
}
|
|
2427
|
+
function isAllowed(allow, ruleId, value) {
|
|
2428
|
+
if (allow.empty) {
|
|
2429
|
+
return false;
|
|
2430
|
+
}
|
|
2431
|
+
if (allow.values.has(value)) {
|
|
2432
|
+
return true;
|
|
2433
|
+
}
|
|
2434
|
+
if (allow.byRule.get(ruleId)?.has(value) === true) {
|
|
2435
|
+
return true;
|
|
2436
|
+
}
|
|
2437
|
+
return allow.pattern !== null && allow.pattern.test(value);
|
|
2438
|
+
}
|
|
2439
|
+
|
|
2440
|
+
// src/core/pseudonymize.ts
|
|
2441
|
+
var TOKEN_HEX_LENGTH = 16;
|
|
2442
|
+
var TOKEN_BYTES = TOKEN_HEX_LENGTH / 2;
|
|
2443
|
+
var textEncoder = new TextEncoder();
|
|
2444
|
+
function createPseudonymizer(key, encoding) {
|
|
2445
|
+
const keyBytes = encoding === "utf8" ? textEncoder.encode(key) : hexToBytes(key);
|
|
2446
|
+
const sign = createHmacSha256(keyBytes);
|
|
2447
|
+
const cache = /* @__PURE__ */ new Map();
|
|
2448
|
+
return (ruleId, value) => {
|
|
2449
|
+
const cacheKey = `${ruleId}\0${value}`;
|
|
2450
|
+
const cached = cache.get(cacheKey);
|
|
2451
|
+
if (cached !== void 0) {
|
|
2452
|
+
return cached;
|
|
2453
|
+
}
|
|
2454
|
+
const digest = sign(textEncoder.encode(cacheKey));
|
|
2455
|
+
const token = bytesToHex(digest.subarray(0, TOKEN_BYTES));
|
|
2456
|
+
cache.set(cacheKey, token);
|
|
2457
|
+
return token;
|
|
2458
|
+
};
|
|
2459
|
+
}
|
|
2460
|
+
function replacementPrefix(rule) {
|
|
2461
|
+
if (rule.mode === "mask") {
|
|
2462
|
+
return MASK_TOKEN_PREFIX;
|
|
2463
|
+
}
|
|
2464
|
+
return rule.token ?? rule.id.toUpperCase();
|
|
2465
|
+
}
|
|
2466
|
+
|
|
2467
|
+
// src/core/compile.ts
|
|
2468
|
+
function compileRules(rules, aggressive) {
|
|
2469
|
+
const parts = rules.map((rule) => ({
|
|
2470
|
+
id: rule.id,
|
|
2471
|
+
patterns: aggressive ? [...rule.patterns, ...rule.aggressivePatterns ?? []] : rule.patterns
|
|
2472
|
+
})).filter(({ patterns }) => patterns.length > 0).map(({ id, patterns }) => `(?<${id}>${patterns.join("|")})`);
|
|
2473
|
+
if (parts.length === 0) {
|
|
2474
|
+
return null;
|
|
2475
|
+
}
|
|
2476
|
+
try {
|
|
2477
|
+
return new RegExp(parts.join("|"), "gu");
|
|
2478
|
+
} catch (cause) {
|
|
2479
|
+
throw new InvalidRuleError(`Failed to compile the combined rule pattern: ${String(cause)}`);
|
|
2480
|
+
}
|
|
2481
|
+
}
|
|
2482
|
+
function createRuleContext(input) {
|
|
2483
|
+
const { rules } = input;
|
|
2484
|
+
return {
|
|
2485
|
+
compiled: compileRules(rules, input.aggressive),
|
|
2486
|
+
rules,
|
|
2487
|
+
ruleIds: rules.map((rule) => rule.id),
|
|
2488
|
+
rulesById: new Map(rules.map((rule) => [rule.id, rule])),
|
|
2489
|
+
prefixById: new Map(rules.map((rule) => [rule.id, replacementPrefix(rule)])),
|
|
2490
|
+
pseudonymize: input.pseudonymize,
|
|
2491
|
+
allow: input.allow,
|
|
2492
|
+
contextChars: input.contextChars,
|
|
2493
|
+
json: input.json
|
|
2494
|
+
};
|
|
2495
|
+
}
|
|
2496
|
+
function buildReplacement(ctx, ruleId, value) {
|
|
2497
|
+
return `<${ctx.prefixById.get(ruleId) ?? ruleId.toUpperCase()}:${ctx.pseudonymize(ruleId, value)}>`;
|
|
2498
|
+
}
|
|
2499
|
+
|
|
2500
|
+
// src/core/lineSplitter.ts
|
|
2501
|
+
function extractLines(buffer) {
|
|
2502
|
+
const lines = [];
|
|
2503
|
+
let start = 0;
|
|
2504
|
+
for (let i = 0; i < buffer.length; i += 1) {
|
|
2505
|
+
if (buffer[i] === "\n") {
|
|
2506
|
+
lines.push(buffer.slice(start, i + 1));
|
|
2507
|
+
start = i + 1;
|
|
2508
|
+
}
|
|
2509
|
+
}
|
|
2510
|
+
return { lines, rest: buffer.slice(start) };
|
|
2511
|
+
}
|
|
2512
|
+
function createLineSplitter(options = {}) {
|
|
2513
|
+
const maxLineChars = options.maxLineChars ?? DEFAULT_MAX_LINE_CHARS;
|
|
2514
|
+
const overlapChars = options.overlapChars ?? DEFAULT_LINE_OVERLAP_CHARS;
|
|
2515
|
+
if (maxLineChars < 1) {
|
|
2516
|
+
throw new InvalidOptionError("lines.maxLineChars must be at least 1.");
|
|
2517
|
+
}
|
|
2518
|
+
if (overlapChars < 0 || overlapChars >= maxLineChars) {
|
|
2519
|
+
throw new InvalidOptionError(
|
|
2520
|
+
`lines.overlapChars must be between 0 and lines.maxLineChars - 1 (${maxLineChars - 1}), got ${overlapChars}.`
|
|
2521
|
+
);
|
|
2522
|
+
}
|
|
2523
|
+
let carry = "";
|
|
2524
|
+
function push(chunk) {
|
|
2525
|
+
carry += chunk;
|
|
2526
|
+
const { lines, rest } = extractLines(carry);
|
|
2527
|
+
carry = rest;
|
|
2528
|
+
while (carry.length > maxLineChars) {
|
|
2529
|
+
const cut = carry.length - overlapChars;
|
|
2530
|
+
lines.push(carry.slice(0, cut));
|
|
2531
|
+
carry = carry.slice(cut);
|
|
2532
|
+
}
|
|
2533
|
+
return lines;
|
|
2534
|
+
}
|
|
2535
|
+
function flush() {
|
|
2536
|
+
if (carry.length === 0) {
|
|
2537
|
+
return [];
|
|
2538
|
+
}
|
|
2539
|
+
const rest = carry;
|
|
2540
|
+
carry = "";
|
|
2541
|
+
return [rest];
|
|
2542
|
+
}
|
|
2543
|
+
return { push, flush };
|
|
2544
|
+
}
|
|
2545
|
+
|
|
2546
|
+
// src/core/redactLine.ts
|
|
2547
|
+
function sliceContext(text, index, length, chars) {
|
|
2548
|
+
return {
|
|
2549
|
+
contextBefore: text.slice(Math.max(0, index - chars), index),
|
|
2550
|
+
contextAfter: text.slice(index + length, index + length + chars)
|
|
2551
|
+
};
|
|
2552
|
+
}
|
|
2553
|
+
function matchedRuleId(ruleIds, groups) {
|
|
2554
|
+
for (const id of ruleIds) {
|
|
2555
|
+
if (groups[id] !== void 0) {
|
|
2556
|
+
return id;
|
|
2557
|
+
}
|
|
2558
|
+
}
|
|
2559
|
+
return void 0;
|
|
2560
|
+
}
|
|
2561
|
+
function redactLine(line, ctx, options = {}) {
|
|
2562
|
+
const { compiled, ruleIds, rulesById, allow, contextChars } = ctx;
|
|
2563
|
+
const withSegments = options.withSegments ?? false;
|
|
2564
|
+
const withMatches = options.withMatches ?? false;
|
|
2565
|
+
if (!compiled) {
|
|
2566
|
+
return {
|
|
2567
|
+
output: line,
|
|
2568
|
+
counts: {},
|
|
2569
|
+
matches: [],
|
|
2570
|
+
segments: withSegments ? { before: [{ text: line, changed: false }], after: [{ text: line, changed: false }] } : void 0
|
|
2571
|
+
};
|
|
2572
|
+
}
|
|
2573
|
+
compiled.lastIndex = 0;
|
|
2574
|
+
const counts = {};
|
|
2575
|
+
const matches = [];
|
|
2576
|
+
const before = [];
|
|
2577
|
+
const after = [];
|
|
2578
|
+
let output = "";
|
|
2579
|
+
let cursor = 0;
|
|
2580
|
+
let match = compiled.exec(line);
|
|
2581
|
+
while (match !== null) {
|
|
2582
|
+
const ruleId = match.groups ? matchedRuleId(ruleIds, match.groups) : void 0;
|
|
2583
|
+
const rule = ruleId === void 0 ? void 0 : rulesById.get(ruleId);
|
|
2584
|
+
const original = match[0];
|
|
2585
|
+
if (rule === void 0 || ruleId === void 0) {
|
|
2586
|
+
compiled.lastIndex = match.index + Math.max(original.length, 1);
|
|
2587
|
+
match = compiled.exec(line);
|
|
2588
|
+
continue;
|
|
2589
|
+
}
|
|
2590
|
+
const rejected = rule.validate !== void 0 && !rule.validate(original) || isAllowed(allow, ruleId, original);
|
|
2591
|
+
if (rejected) {
|
|
2592
|
+
compiled.lastIndex = match.index + Math.max(original.length, 1);
|
|
2593
|
+
match = compiled.exec(line);
|
|
2594
|
+
continue;
|
|
2595
|
+
}
|
|
2596
|
+
const replacement = buildReplacement(ctx, ruleId, original);
|
|
2597
|
+
if (withSegments) {
|
|
2598
|
+
if (match.index > cursor) {
|
|
2599
|
+
const gap = line.slice(cursor, match.index);
|
|
2600
|
+
before.push({ text: gap, changed: false });
|
|
2601
|
+
after.push({ text: gap, changed: false });
|
|
2602
|
+
}
|
|
2603
|
+
before.push({ text: original, changed: true });
|
|
2604
|
+
after.push({ text: replacement, changed: true });
|
|
2605
|
+
}
|
|
2606
|
+
output += line.slice(cursor, match.index) + replacement;
|
|
2607
|
+
cursor = match.index + original.length;
|
|
2608
|
+
counts[ruleId] = (counts[ruleId] ?? 0) + 1;
|
|
2609
|
+
if (withMatches) {
|
|
2610
|
+
matches.push({
|
|
2611
|
+
ruleId,
|
|
2612
|
+
original,
|
|
2613
|
+
replacement,
|
|
2614
|
+
...contextChars > 0 ? sliceContext(line, match.index, original.length, contextChars) : {}
|
|
2615
|
+
});
|
|
2616
|
+
}
|
|
2617
|
+
if (original.length === 0) {
|
|
2618
|
+
compiled.lastIndex += 1;
|
|
2619
|
+
}
|
|
2620
|
+
match = compiled.exec(line);
|
|
2621
|
+
}
|
|
2622
|
+
output += line.slice(cursor);
|
|
2623
|
+
if (withSegments && cursor < line.length) {
|
|
2624
|
+
const tail = line.slice(cursor);
|
|
2625
|
+
before.push({ text: tail, changed: false });
|
|
2626
|
+
after.push({ text: tail, changed: false });
|
|
2627
|
+
}
|
|
2628
|
+
return {
|
|
2629
|
+
output,
|
|
2630
|
+
counts,
|
|
2631
|
+
matches,
|
|
2632
|
+
segments: withSegments ? { before, after } : void 0
|
|
2633
|
+
};
|
|
2634
|
+
}
|
|
2635
|
+
|
|
2636
|
+
// src/core/redactJsonLine.ts
|
|
2637
|
+
var REPLACEMENT_TOKEN_RE = /<[A-Z][A-Z0-9]*:[0-9a-f]+>/g;
|
|
2638
|
+
function looksLikeJson(line) {
|
|
2639
|
+
const trimmed = line.trim();
|
|
2640
|
+
return trimmed.length > 0 && (trimmed.startsWith("{") || trimmed.startsWith("["));
|
|
2641
|
+
}
|
|
2642
|
+
function normalizeKey2(key) {
|
|
2643
|
+
return key.toLowerCase().replace(/[-_]/g, "");
|
|
2644
|
+
}
|
|
2645
|
+
function buildJsonKeyIndex(rules) {
|
|
2646
|
+
const index = /* @__PURE__ */ new Map();
|
|
2647
|
+
for (const rule of rules) {
|
|
2648
|
+
for (const key of rule.jsonKeys ?? []) {
|
|
2649
|
+
const normalized = normalizeKey2(key);
|
|
2650
|
+
if (!index.has(normalized)) {
|
|
2651
|
+
index.set(normalized, rule);
|
|
2652
|
+
}
|
|
2653
|
+
}
|
|
2654
|
+
}
|
|
2655
|
+
return index;
|
|
2656
|
+
}
|
|
2657
|
+
var jsonKeyIndexCache = /* @__PURE__ */ new WeakMap();
|
|
2658
|
+
function jsonKeyIndex(rules) {
|
|
2659
|
+
const cached = jsonKeyIndexCache.get(rules);
|
|
2660
|
+
if (cached) {
|
|
2661
|
+
return cached;
|
|
2662
|
+
}
|
|
2663
|
+
const index = buildJsonKeyIndex(rules);
|
|
2664
|
+
jsonKeyIndexCache.set(rules, index);
|
|
2665
|
+
return index;
|
|
2666
|
+
}
|
|
2667
|
+
function mergeCounts(target, source) {
|
|
2668
|
+
for (const [id, count] of Object.entries(source)) {
|
|
2669
|
+
target[id] = (target[id] ?? 0) + count;
|
|
2670
|
+
}
|
|
2671
|
+
}
|
|
2672
|
+
function highlightReplacements(text) {
|
|
2673
|
+
const segments = [];
|
|
2674
|
+
let cursor = 0;
|
|
2675
|
+
REPLACEMENT_TOKEN_RE.lastIndex = 0;
|
|
2676
|
+
let match = REPLACEMENT_TOKEN_RE.exec(text);
|
|
2677
|
+
while (match !== null) {
|
|
2678
|
+
if (match.index > cursor) {
|
|
2679
|
+
segments.push({ text: text.slice(cursor, match.index), changed: false });
|
|
2680
|
+
}
|
|
2681
|
+
segments.push({ text: match[0], changed: true });
|
|
2682
|
+
cursor = match.index + match[0].length;
|
|
2683
|
+
match = REPLACEMENT_TOKEN_RE.exec(text);
|
|
2684
|
+
}
|
|
2685
|
+
if (cursor < text.length) {
|
|
2686
|
+
segments.push({ text: text.slice(cursor), changed: false });
|
|
2687
|
+
}
|
|
2688
|
+
return segments.length > 0 ? segments : [{ text, changed: false }];
|
|
2689
|
+
}
|
|
2690
|
+
function redactJsonLine(line, ctx, options = {}) {
|
|
2691
|
+
const terminatorMatch = /(\r\n|\r|\n)$/.exec(line);
|
|
2692
|
+
const terminator = terminatorMatch ? terminatorMatch[0] : "";
|
|
2693
|
+
const content = terminator ? line.slice(0, -terminator.length) : line;
|
|
2694
|
+
let parsed;
|
|
2695
|
+
try {
|
|
2696
|
+
parsed = JSON.parse(content);
|
|
2697
|
+
} catch {
|
|
2698
|
+
return null;
|
|
2699
|
+
}
|
|
2700
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
2701
|
+
return null;
|
|
2702
|
+
}
|
|
2703
|
+
const withSegments = options.withSegments ?? false;
|
|
2704
|
+
const withMatches = options.withMatches ?? false;
|
|
2705
|
+
const keyIndex = jsonKeyIndex(ctx.rules);
|
|
2706
|
+
const counts = {};
|
|
2707
|
+
const matches = [];
|
|
2708
|
+
const contextFor = (value) => {
|
|
2709
|
+
if (ctx.contextChars <= 0) {
|
|
2710
|
+
return {};
|
|
2711
|
+
}
|
|
2712
|
+
const index = content.indexOf(value);
|
|
2713
|
+
if (index < 0) {
|
|
2714
|
+
return { contextBefore: "", contextAfter: "" };
|
|
2715
|
+
}
|
|
2716
|
+
return sliceContext(content, index, value.length, ctx.contextChars);
|
|
2717
|
+
};
|
|
2718
|
+
const redactValue = (value, key) => {
|
|
2719
|
+
if (typeof value === "string") {
|
|
2720
|
+
const fieldRule = key === void 0 ? void 0 : keyIndex.get(normalizeKey2(key));
|
|
2721
|
+
if (fieldRule) {
|
|
2722
|
+
if (isAllowed(ctx.allow, fieldRule.id, value)) {
|
|
2723
|
+
return value;
|
|
2724
|
+
}
|
|
2725
|
+
const replacement = buildReplacement(ctx, fieldRule.id, value);
|
|
2726
|
+
counts[fieldRule.id] = (counts[fieldRule.id] ?? 0) + 1;
|
|
2727
|
+
if (withMatches) {
|
|
2728
|
+
matches.push({
|
|
2729
|
+
ruleId: fieldRule.id,
|
|
2730
|
+
original: value,
|
|
2731
|
+
replacement,
|
|
2732
|
+
...contextFor(value)
|
|
2733
|
+
});
|
|
2734
|
+
}
|
|
2735
|
+
return replacement;
|
|
2736
|
+
}
|
|
2737
|
+
const result = redactLine(value, ctx, { withMatches });
|
|
2738
|
+
mergeCounts(counts, result.counts);
|
|
2739
|
+
if (withMatches) {
|
|
2740
|
+
for (const match of result.matches) {
|
|
2741
|
+
matches.push({ ...match, ...contextFor(match.original) });
|
|
2742
|
+
}
|
|
2743
|
+
}
|
|
2744
|
+
return result.output;
|
|
2745
|
+
}
|
|
2746
|
+
if (Array.isArray(value)) {
|
|
2747
|
+
return value.map((item) => redactValue(item));
|
|
2748
|
+
}
|
|
2749
|
+
if (value !== null && typeof value === "object") {
|
|
2750
|
+
const out = {};
|
|
2751
|
+
for (const [entryKey, entryValue] of Object.entries(value)) {
|
|
2752
|
+
out[entryKey] = redactValue(entryValue, entryKey);
|
|
2753
|
+
}
|
|
2754
|
+
return out;
|
|
2755
|
+
}
|
|
2756
|
+
return value;
|
|
2757
|
+
};
|
|
2758
|
+
const output = JSON.stringify(redactValue(parsed)) + terminator;
|
|
2759
|
+
if (!withSegments) {
|
|
2760
|
+
return { output, counts, matches };
|
|
2761
|
+
}
|
|
2762
|
+
const preview = redactLine(line, ctx, { withSegments: true });
|
|
2763
|
+
return {
|
|
2764
|
+
output,
|
|
2765
|
+
counts,
|
|
2766
|
+
matches,
|
|
2767
|
+
segments: {
|
|
2768
|
+
before: preview.segments?.before ?? [{ text: line, changed: false }],
|
|
2769
|
+
after: highlightReplacements(output)
|
|
2770
|
+
}
|
|
2771
|
+
};
|
|
2772
|
+
}
|
|
2773
|
+
|
|
2774
|
+
// src/core/report.ts
|
|
2775
|
+
function createReportCollector(options = {}) {
|
|
2776
|
+
const previewBytes = options.previewBytes ?? DEFAULT_PREVIEW_BYTES;
|
|
2777
|
+
const collectReplacements = options.replacements ?? true;
|
|
2778
|
+
if (previewBytes < 0) {
|
|
2779
|
+
throw new InvalidOptionError("report.previewBytes must not be negative.");
|
|
2780
|
+
}
|
|
2781
|
+
const counts = {};
|
|
2782
|
+
const replacements = /* @__PURE__ */ new Map();
|
|
2783
|
+
const before = [];
|
|
2784
|
+
const after = [];
|
|
2785
|
+
let lineCount = 0;
|
|
2786
|
+
let previewBytesUsed = 0;
|
|
2787
|
+
let previewClosed = previewBytes === 0;
|
|
2788
|
+
function needsSegments() {
|
|
2789
|
+
return !previewClosed;
|
|
2790
|
+
}
|
|
2791
|
+
function needsMatches() {
|
|
2792
|
+
return collectReplacements;
|
|
2793
|
+
}
|
|
2794
|
+
function record(result) {
|
|
2795
|
+
lineCount += 1;
|
|
2796
|
+
for (const [id, count] of Object.entries(result.counts)) {
|
|
2797
|
+
counts[id] = (counts[id] ?? 0) + count;
|
|
2798
|
+
}
|
|
2799
|
+
if (collectReplacements) {
|
|
2800
|
+
for (const match of result.matches) {
|
|
2801
|
+
const key = `${match.ruleId}\0${match.original}`;
|
|
2802
|
+
const existing = replacements.get(key);
|
|
2803
|
+
if (existing) {
|
|
2804
|
+
existing.count += 1;
|
|
2805
|
+
} else {
|
|
2806
|
+
replacements.set(key, {
|
|
2807
|
+
ruleId: match.ruleId,
|
|
2808
|
+
original: match.original,
|
|
2809
|
+
replacement: match.replacement,
|
|
2810
|
+
count: 1,
|
|
2811
|
+
...match.contextBefore !== void 0 ? { contextBefore: match.contextBefore, contextAfter: match.contextAfter } : {}
|
|
2812
|
+
});
|
|
2813
|
+
}
|
|
2814
|
+
}
|
|
2815
|
+
}
|
|
2816
|
+
if (previewClosed) {
|
|
2817
|
+
return;
|
|
2818
|
+
}
|
|
2819
|
+
if (result.segments) {
|
|
2820
|
+
before.push(...result.segments.before);
|
|
2821
|
+
after.push(...result.segments.after);
|
|
2822
|
+
}
|
|
2823
|
+
previewBytesUsed += result.output.length;
|
|
2824
|
+
if (previewBytesUsed >= previewBytes) {
|
|
2825
|
+
previewClosed = true;
|
|
2826
|
+
}
|
|
2827
|
+
}
|
|
2828
|
+
function build(snapshot) {
|
|
2829
|
+
const includeReplacements = snapshot?.includeReplacements ?? true;
|
|
2830
|
+
let totalMatches = 0;
|
|
2831
|
+
for (const count of Object.values(counts)) {
|
|
2832
|
+
totalMatches += count;
|
|
2833
|
+
}
|
|
2834
|
+
return {
|
|
2835
|
+
counts: { ...counts },
|
|
2836
|
+
totalMatches,
|
|
2837
|
+
lineCount,
|
|
2838
|
+
replacements: includeReplacements ? [...replacements.values()] : [],
|
|
2839
|
+
preview: { before: [...before], after: [...after] }
|
|
2840
|
+
};
|
|
2841
|
+
}
|
|
2842
|
+
return { needsSegments, needsMatches, record, build };
|
|
2843
|
+
}
|
|
2844
|
+
|
|
2845
|
+
// src/rules/validateRule.ts
|
|
2846
|
+
var RULE_ID_PATTERN = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
2847
|
+
var TOKEN_PATTERN = /^[A-Z][A-Z0-9]*$/;
|
|
2848
|
+
var NAMED_GROUP_PATTERN = /\(\?<[^=!]/;
|
|
2849
|
+
function requireText(value, rule, field) {
|
|
2850
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
2851
|
+
throw new InvalidRuleError(`Rule "${rule}": \`${field}\` must be a non-empty string.`);
|
|
2852
|
+
}
|
|
2853
|
+
return value;
|
|
2854
|
+
}
|
|
2855
|
+
function assertPatterns(patterns, ruleId, field) {
|
|
2856
|
+
if (patterns === void 0) {
|
|
2857
|
+
return [];
|
|
2858
|
+
}
|
|
2859
|
+
if (!Array.isArray(patterns)) {
|
|
2860
|
+
throw new InvalidRuleError(`Rule "${ruleId}": \`${field}\` must be an array of strings.`);
|
|
2861
|
+
}
|
|
2862
|
+
return patterns.map((pattern, index) => {
|
|
2863
|
+
if (typeof pattern !== "string" || pattern.length === 0) {
|
|
2864
|
+
throw new InvalidRuleError(
|
|
2865
|
+
`Rule "${ruleId}": \`${field}[${index}]\` must be a non-empty regular expression source.`
|
|
2866
|
+
);
|
|
2867
|
+
}
|
|
2868
|
+
if (NAMED_GROUP_PATTERN.test(pattern)) {
|
|
2869
|
+
throw new InvalidRuleError(
|
|
2870
|
+
`Rule "${ruleId}": \`${field}[${index}]\` must not declare a named capture group \u2014 the rule identifier is used as the group name.`
|
|
2871
|
+
);
|
|
2872
|
+
}
|
|
2873
|
+
try {
|
|
2874
|
+
new RegExp(pattern, "u");
|
|
2875
|
+
} catch (cause) {
|
|
2876
|
+
throw new InvalidRuleError(
|
|
2877
|
+
`Rule "${ruleId}": \`${field}[${index}]\` is not a valid Unicode regular expression: ${String(cause)}`
|
|
2878
|
+
);
|
|
2879
|
+
}
|
|
2880
|
+
return pattern;
|
|
2881
|
+
});
|
|
2882
|
+
}
|
|
2883
|
+
function validateRule(rule) {
|
|
2884
|
+
if (typeof rule !== "object" || rule === null) {
|
|
2885
|
+
throw new InvalidRuleError("A rule must be an object.");
|
|
2886
|
+
}
|
|
2887
|
+
const id = requireText(rule.id, String(rule.id), "id");
|
|
2888
|
+
if (!RULE_ID_PATTERN.test(id)) {
|
|
2889
|
+
throw new InvalidRuleError(
|
|
2890
|
+
`Rule "${id}": \`id\` must be a valid ASCII identifier matching ${RULE_ID_PATTERN.source}, because it is used as a named capture group.`
|
|
2891
|
+
);
|
|
2892
|
+
}
|
|
2893
|
+
requireText(rule.label, id, "label");
|
|
2894
|
+
requireText(rule.description, id, "description");
|
|
2895
|
+
if (rule.mode !== "pseudo" && rule.mode !== "mask") {
|
|
2896
|
+
throw new InvalidRuleError(`Rule "${id}": \`mode\` must be either "pseudo" or "mask".`);
|
|
2897
|
+
}
|
|
2898
|
+
if (rule.token !== void 0 && !TOKEN_PATTERN.test(rule.token)) {
|
|
2899
|
+
throw new InvalidRuleError(
|
|
2900
|
+
`Rule "${id}": \`token\` must match ${TOKEN_PATTERN.source}, so replacement tokens stay recognizable.`
|
|
2901
|
+
);
|
|
2902
|
+
}
|
|
2903
|
+
const patterns = assertPatterns(rule.patterns, id, "patterns");
|
|
2904
|
+
const aggressivePatterns = assertPatterns(rule.aggressivePatterns, id, "aggressivePatterns");
|
|
2905
|
+
if (patterns.length === 0 && aggressivePatterns.length === 0) {
|
|
2906
|
+
throw new InvalidRuleError(`Rule "${id}": at least one pattern is required.`);
|
|
2907
|
+
}
|
|
2908
|
+
if (rule.jsonKeys !== void 0) {
|
|
2909
|
+
if (!Array.isArray(rule.jsonKeys)) {
|
|
2910
|
+
throw new InvalidRuleError(`Rule "${id}": \`jsonKeys\` must be an array of strings.`);
|
|
2911
|
+
}
|
|
2912
|
+
for (const [index, key] of rule.jsonKeys.entries()) {
|
|
2913
|
+
requireText(key, id, `jsonKeys[${index}]`);
|
|
2914
|
+
}
|
|
2915
|
+
}
|
|
2916
|
+
if (rule.validate !== void 0 && typeof rule.validate !== "function") {
|
|
2917
|
+
throw new InvalidRuleError(`Rule "${id}": \`validate\` must be a function.`);
|
|
2918
|
+
}
|
|
2919
|
+
return rule;
|
|
2920
|
+
}
|
|
2921
|
+
|
|
2922
|
+
// src/core/resolveRules.ts
|
|
2923
|
+
function toRule(selector) {
|
|
2924
|
+
if (typeof selector === "string") {
|
|
2925
|
+
const builtin = getBuiltinRule(selector);
|
|
2926
|
+
if (!builtin) {
|
|
2927
|
+
throw new UnknownRuleError(
|
|
2928
|
+
`Unknown rule "${selector}". Pass a rule object for custom rules, or one of the built-in identifiers.`
|
|
2929
|
+
);
|
|
2930
|
+
}
|
|
2931
|
+
return builtin;
|
|
2932
|
+
}
|
|
2933
|
+
return validateRule(selector);
|
|
2934
|
+
}
|
|
2935
|
+
function resolveRules(selectors, extraRules = []) {
|
|
2936
|
+
const resolved = [];
|
|
2937
|
+
const positions = /* @__PURE__ */ new Map();
|
|
2938
|
+
for (const selector of [...selectors, ...extraRules]) {
|
|
2939
|
+
const rule = toRule(selector);
|
|
2940
|
+
const existing = positions.get(rule.id);
|
|
2941
|
+
if (existing === void 0) {
|
|
2942
|
+
positions.set(rule.id, resolved.length);
|
|
2943
|
+
resolved.push(rule);
|
|
2944
|
+
} else {
|
|
2945
|
+
resolved[existing] = rule;
|
|
2946
|
+
}
|
|
2947
|
+
}
|
|
2948
|
+
return resolved;
|
|
2949
|
+
}
|
|
2950
|
+
|
|
2951
|
+
// src/core/sanitizer.ts
|
|
2952
|
+
async function* toAsyncIterable(source) {
|
|
2953
|
+
yield* source;
|
|
2954
|
+
}
|
|
2955
|
+
function processLine(line, ctx, collector) {
|
|
2956
|
+
const options = {
|
|
2957
|
+
withSegments: collector.needsSegments(),
|
|
2958
|
+
withMatches: collector.needsMatches()
|
|
2959
|
+
};
|
|
2960
|
+
const parsed = ctx.json && looksLikeJson(line) ? redactJsonLine(line, ctx, options) : null;
|
|
2961
|
+
const result = parsed ?? redactLine(line, ctx, options);
|
|
2962
|
+
collector.record(result);
|
|
2963
|
+
return result.output;
|
|
2964
|
+
}
|
|
2965
|
+
function createSanitizer(options = {}) {
|
|
2966
|
+
const keyEncoding = options.keyEncoding ?? "hex";
|
|
2967
|
+
const key = options.key ?? generateKey();
|
|
2968
|
+
assertKey(key, keyEncoding);
|
|
2969
|
+
const contextChars = options.report?.contextChars ?? 0;
|
|
2970
|
+
if (contextChars < 0) {
|
|
2971
|
+
throw new InvalidOptionError("report.contextChars must not be negative.");
|
|
2972
|
+
}
|
|
2973
|
+
const selected = resolveWithAlwaysRedact(options);
|
|
2974
|
+
const ctx = createRuleContext({
|
|
2975
|
+
rules: selected,
|
|
2976
|
+
aggressive: options.aggressive ?? false,
|
|
2977
|
+
pseudonymize: createPseudonymizer(key, keyEncoding),
|
|
2978
|
+
allow: createAllowList(options.neverRedact),
|
|
2979
|
+
contextChars,
|
|
2980
|
+
json: options.json !== false
|
|
2981
|
+
});
|
|
2982
|
+
const collectorOptions = {
|
|
2983
|
+
previewBytes: options.report?.previewBytes,
|
|
2984
|
+
replacements: options.report?.replacements
|
|
2985
|
+
};
|
|
2986
|
+
const rules = selected.map((rule) => ({
|
|
2987
|
+
id: rule.id,
|
|
2988
|
+
label: rule.label,
|
|
2989
|
+
description: rule.description,
|
|
2990
|
+
mode: rule.mode
|
|
2991
|
+
}));
|
|
2992
|
+
function sanitizeText(text) {
|
|
2993
|
+
const collector = createReportCollector(collectorOptions);
|
|
2994
|
+
const splitter = createLineSplitter(options.lines);
|
|
2995
|
+
let output = "";
|
|
2996
|
+
for (const line of splitter.push(text)) {
|
|
2997
|
+
output += processLine(line, ctx, collector);
|
|
2998
|
+
}
|
|
2999
|
+
for (const line of splitter.flush()) {
|
|
3000
|
+
output += processLine(line, ctx, collector);
|
|
3001
|
+
}
|
|
3002
|
+
return { output, report: collector.build() };
|
|
3003
|
+
}
|
|
3004
|
+
async function sanitizeStream(source, sink, streamOptions = {}) {
|
|
3005
|
+
const collector = createReportCollector(collectorOptions);
|
|
3006
|
+
const splitter = createLineSplitter(options.lines);
|
|
3007
|
+
const { signal, onProgress } = streamOptions;
|
|
3008
|
+
let charsRead = 0;
|
|
3009
|
+
const writeLine = async (line) => {
|
|
3010
|
+
if (signal?.aborted === true) {
|
|
3011
|
+
throw new SanitizationAbortedError();
|
|
3012
|
+
}
|
|
3013
|
+
await sink.write(processLine(line, ctx, collector));
|
|
3014
|
+
};
|
|
3015
|
+
for await (const chunk of toAsyncIterable(source)) {
|
|
3016
|
+
charsRead += chunk.length;
|
|
3017
|
+
for (const line of splitter.push(chunk)) {
|
|
3018
|
+
await writeLine(line);
|
|
3019
|
+
}
|
|
3020
|
+
onProgress?.({ charsRead, report: collector.build({ includeReplacements: false }) });
|
|
3021
|
+
}
|
|
3022
|
+
for (const line of splitter.flush()) {
|
|
3023
|
+
await writeLine(line);
|
|
3024
|
+
}
|
|
3025
|
+
await sink.close?.();
|
|
3026
|
+
return collector.build();
|
|
3027
|
+
}
|
|
3028
|
+
return { key, rules, sanitizeText, sanitizeStream };
|
|
3029
|
+
}
|
|
3030
|
+
function resolveWithAlwaysRedact(options) {
|
|
3031
|
+
const selected = resolveRules(options.rules ?? builtinRuleIds, options.extraRules);
|
|
3032
|
+
const alwaysRule = createAlwaysRedactRule(options.alwaysRedact);
|
|
3033
|
+
if (!alwaysRule) {
|
|
3034
|
+
return selected;
|
|
3035
|
+
}
|
|
3036
|
+
if (selected.some((rule) => rule.id === alwaysRule.id)) {
|
|
3037
|
+
throw new InvalidOptionError(
|
|
3038
|
+
`alwaysRedact.ruleId "${alwaysRule.id}" collides with an active rule. Choose an identifier that is not in use.`
|
|
3039
|
+
);
|
|
3040
|
+
}
|
|
3041
|
+
return [alwaysRule, ...selected];
|
|
3042
|
+
}
|
|
3043
|
+
|
|
3044
|
+
// src/io/sinks.ts
|
|
3045
|
+
function toNullSink() {
|
|
3046
|
+
return { write: () => void 0 };
|
|
3047
|
+
}
|
|
3048
|
+
var DEFAULT_CHUNK_BYTES = 4 * 1024 * 1024;
|
|
3049
|
+
function fromNodeStream(stream) {
|
|
3050
|
+
return {
|
|
3051
|
+
async *[Symbol.asyncIterator]() {
|
|
3052
|
+
const decoder = new TextDecoder("utf-8");
|
|
3053
|
+
for await (const chunk of stream) {
|
|
3054
|
+
const text = typeof chunk === "string" ? chunk : decoder.decode(chunk, { stream: true });
|
|
3055
|
+
if (text.length > 0) {
|
|
3056
|
+
yield text;
|
|
3057
|
+
}
|
|
3058
|
+
}
|
|
3059
|
+
const tail = decoder.decode();
|
|
3060
|
+
if (tail.length > 0) {
|
|
3061
|
+
yield tail;
|
|
3062
|
+
}
|
|
3063
|
+
}
|
|
3064
|
+
};
|
|
3065
|
+
}
|
|
3066
|
+
function fromFile(path, chunkBytes = DEFAULT_CHUNK_BYTES) {
|
|
3067
|
+
return fromNodeStream(createReadStream(path, { highWaterMark: chunkBytes }));
|
|
3068
|
+
}
|
|
3069
|
+
function toNodeStream(stream) {
|
|
3070
|
+
return {
|
|
3071
|
+
async write(chunk) {
|
|
3072
|
+
if (!stream.write(chunk)) {
|
|
3073
|
+
await once(stream, "drain");
|
|
3074
|
+
}
|
|
3075
|
+
}
|
|
3076
|
+
};
|
|
3077
|
+
}
|
|
3078
|
+
function toFile(path) {
|
|
3079
|
+
const stream = createWriteStream(path, { encoding: "utf-8" });
|
|
3080
|
+
const target = toNodeStream(stream);
|
|
3081
|
+
return {
|
|
3082
|
+
write: target.write.bind(target),
|
|
3083
|
+
async close() {
|
|
3084
|
+
stream.end();
|
|
3085
|
+
await finished(stream);
|
|
3086
|
+
}
|
|
3087
|
+
};
|
|
3088
|
+
}
|
|
3089
|
+
|
|
3090
|
+
// src/cli.ts
|
|
3091
|
+
var EXIT_OK = 0;
|
|
3092
|
+
var EXIT_ERROR = 1;
|
|
3093
|
+
var EXIT_USAGE = 2;
|
|
3094
|
+
var HELP = `Usage: logtotal-sanitize [options] <input>
|
|
3095
|
+
|
|
3096
|
+
Redact secrets, identifiers and PII from a log file. Input "-" reads stdin.
|
|
3097
|
+
|
|
3098
|
+
Options:
|
|
3099
|
+
-o, --out <path> Output file (default: <input>.sanitized)
|
|
3100
|
+
--stdout Write sanitized text to stdout
|
|
3101
|
+
--report <path> Write a JSON report to <path>
|
|
3102
|
+
--report-format <fmt> json | text (default: json)
|
|
3103
|
+
--rules <ids> Comma-separated built-in rule ids (default: all)
|
|
3104
|
+
--exclude-rules <ids> Comma-separated built-in rule ids to skip
|
|
3105
|
+
--rules-file <path> Load extra rules from a JS/JSON module
|
|
3106
|
+
--exclude <value> Never redact this exact value (repeatable)
|
|
3107
|
+
--exclude-file <path> Newline-separated never-redact values
|
|
3108
|
+
--redact <value> Always redact this exact value (repeatable)
|
|
3109
|
+
--redact-file <path> Newline-separated always-redact values
|
|
3110
|
+
--aggressive Enable broader, noisier patterns
|
|
3111
|
+
--key <value> HMAC key (reuse to correlate tokens across files)
|
|
3112
|
+
--key-file <path> Read HMAC key from a file
|
|
3113
|
+
--key-encoding <enc> hex | utf8
|
|
3114
|
+
--print-key Print the key to stderr
|
|
3115
|
+
--dry-run Report only; do not write sanitized output
|
|
3116
|
+
--fail-on-match Exit 1 if anything was redacted
|
|
3117
|
+
--progress Show a live progress bar on stderr
|
|
3118
|
+
--no-progress Do not show a progress bar
|
|
3119
|
+
-q, --quiet Suppress the text summary
|
|
3120
|
+
-h, --help Show this help
|
|
3121
|
+
-v, --version Show version
|
|
3122
|
+
`;
|
|
3123
|
+
function parseCli(argv) {
|
|
3124
|
+
const { values, positionals } = parseArgs({
|
|
3125
|
+
args: argv,
|
|
3126
|
+
allowPositionals: true,
|
|
3127
|
+
options: {
|
|
3128
|
+
out: { type: "string", short: "o" },
|
|
3129
|
+
stdout: { type: "boolean", default: false },
|
|
3130
|
+
report: { type: "string" },
|
|
3131
|
+
"report-format": { type: "string", default: "json" },
|
|
3132
|
+
rules: { type: "string" },
|
|
3133
|
+
"exclude-rules": { type: "string" },
|
|
3134
|
+
"rules-file": { type: "string" },
|
|
3135
|
+
exclude: { type: "string", multiple: true },
|
|
3136
|
+
"exclude-file": { type: "string" },
|
|
3137
|
+
redact: { type: "string", multiple: true },
|
|
3138
|
+
"redact-file": { type: "string" },
|
|
3139
|
+
aggressive: { type: "boolean", default: false },
|
|
3140
|
+
key: { type: "string" },
|
|
3141
|
+
"key-file": { type: "string" },
|
|
3142
|
+
"key-encoding": { type: "string" },
|
|
3143
|
+
"print-key": { type: "boolean", default: false },
|
|
3144
|
+
"dry-run": { type: "boolean", default: false },
|
|
3145
|
+
"fail-on-match": { type: "boolean", default: false },
|
|
3146
|
+
progress: { type: "boolean", default: false },
|
|
3147
|
+
"no-progress": { type: "boolean", default: false },
|
|
3148
|
+
quiet: { type: "boolean", short: "q", default: false },
|
|
3149
|
+
help: { type: "boolean", short: "h", default: false },
|
|
3150
|
+
version: { type: "boolean", short: "v", default: false }
|
|
3151
|
+
}
|
|
3152
|
+
});
|
|
3153
|
+
const keyEncoding = values["key-encoding"] === "hex" || values["key-encoding"] === "utf8" ? values["key-encoding"] : void 0;
|
|
3154
|
+
return {
|
|
3155
|
+
out: values.out,
|
|
3156
|
+
stdout: Boolean(values.stdout),
|
|
3157
|
+
report: values.report,
|
|
3158
|
+
reportFormat: values["report-format"] === "text" ? "text" : "json",
|
|
3159
|
+
rules: values.rules,
|
|
3160
|
+
excludeRules: values["exclude-rules"],
|
|
3161
|
+
rulesFile: values["rules-file"],
|
|
3162
|
+
exclude: values.exclude ?? [],
|
|
3163
|
+
excludeFile: values["exclude-file"],
|
|
3164
|
+
redact: values.redact ?? [],
|
|
3165
|
+
redactFile: values["redact-file"],
|
|
3166
|
+
aggressive: Boolean(values.aggressive),
|
|
3167
|
+
key: values.key,
|
|
3168
|
+
keyFile: values["key-file"],
|
|
3169
|
+
keyEncoding,
|
|
3170
|
+
printKey: Boolean(values["print-key"]),
|
|
3171
|
+
dryRun: Boolean(values["dry-run"]),
|
|
3172
|
+
failOnMatch: Boolean(values["fail-on-match"]),
|
|
3173
|
+
progress: Boolean(values.progress),
|
|
3174
|
+
noProgress: Boolean(values["no-progress"]),
|
|
3175
|
+
quiet: Boolean(values.quiet),
|
|
3176
|
+
help: Boolean(values.help),
|
|
3177
|
+
version: Boolean(values.version),
|
|
3178
|
+
input: positionals[0]
|
|
3179
|
+
};
|
|
3180
|
+
}
|
|
3181
|
+
async function readLines(path) {
|
|
3182
|
+
const text = await readFile(path, "utf8");
|
|
3183
|
+
return text.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
3184
|
+
}
|
|
3185
|
+
async function loadRulesFile(path) {
|
|
3186
|
+
if (path.endsWith(".json")) {
|
|
3187
|
+
const parsed = JSON.parse(await readFile(path, "utf8"));
|
|
3188
|
+
return Array.isArray(parsed) ? parsed : [parsed];
|
|
3189
|
+
}
|
|
3190
|
+
const mod = await import(pathToFileURL(path).href);
|
|
3191
|
+
const value = mod.default;
|
|
3192
|
+
if (Array.isArray(value)) {
|
|
3193
|
+
return value;
|
|
3194
|
+
}
|
|
3195
|
+
return [value];
|
|
3196
|
+
}
|
|
3197
|
+
function formatReportText(report) {
|
|
3198
|
+
const lines = [`lines: ${report.lineCount}`, `matches: ${report.totalMatches}`];
|
|
3199
|
+
for (const [id, count] of Object.entries(report.counts)) {
|
|
3200
|
+
lines.push(` ${id}: ${count}`);
|
|
3201
|
+
}
|
|
3202
|
+
return `${lines.join("\n")}
|
|
3203
|
+
`;
|
|
3204
|
+
}
|
|
3205
|
+
var PROGRESS_BAR_WIDTH = 20;
|
|
3206
|
+
function streamIsTty(stream) {
|
|
3207
|
+
return Boolean(stream.isTTY);
|
|
3208
|
+
}
|
|
3209
|
+
function progressLabel(input, useStdin) {
|
|
3210
|
+
if (useStdin) {
|
|
3211
|
+
return "stdin";
|
|
3212
|
+
}
|
|
3213
|
+
const name = basename(input);
|
|
3214
|
+
return name.length > 32 ? `${name.slice(0, 29)}...` : name;
|
|
3215
|
+
}
|
|
3216
|
+
function formatBytes(bytes) {
|
|
3217
|
+
if (bytes < 1024) {
|
|
3218
|
+
return `${Math.round(bytes)} B`;
|
|
3219
|
+
}
|
|
3220
|
+
const units = ["KB", "MB", "GB", "TB"];
|
|
3221
|
+
let value = bytes / 1024;
|
|
3222
|
+
let unit = 0;
|
|
3223
|
+
while (value >= 1024 && unit < units.length - 1) {
|
|
3224
|
+
value /= 1024;
|
|
3225
|
+
unit += 1;
|
|
3226
|
+
}
|
|
3227
|
+
const digits = value >= 10 ? 1 : 2;
|
|
3228
|
+
return `${value.toFixed(digits)} ${units[unit]}`;
|
|
3229
|
+
}
|
|
3230
|
+
function formatBar(ratio) {
|
|
3231
|
+
const clamped = Math.min(1, Math.max(0, ratio));
|
|
3232
|
+
const filled = Math.round(clamped * PROGRESS_BAR_WIDTH);
|
|
3233
|
+
const head = filled > 0 && filled < PROGRESS_BAR_WIDTH ? ">" : filled === PROGRESS_BAR_WIDTH ? "=" : "";
|
|
3234
|
+
const body = "=".repeat(Math.max(0, filled - head.length));
|
|
3235
|
+
const empty = " ".repeat(PROGRESS_BAR_WIDTH - body.length - head.length);
|
|
3236
|
+
return `[${body}${head}${empty}]`;
|
|
3237
|
+
}
|
|
3238
|
+
function formatProgressLine(state) {
|
|
3239
|
+
const stats = `${formatBytes(state.bytesRead)}${state.totalBytes !== void 0 ? ` / ${formatBytes(state.totalBytes)}` : ""} ${state.lineCount} lines ${state.totalMatches} matches`;
|
|
3240
|
+
if (state.totalBytes === void 0) {
|
|
3241
|
+
return `${state.label} ${stats}`;
|
|
3242
|
+
}
|
|
3243
|
+
const ratio = state.totalBytes === 0 ? 1 : Math.min(1, state.bytesRead / state.totalBytes);
|
|
3244
|
+
const pct = String(Math.min(100, Math.floor(ratio * 100))).padStart(3, " ");
|
|
3245
|
+
return `${state.label} ${formatBar(ratio)} ${pct}% ${stats}`;
|
|
3246
|
+
}
|
|
3247
|
+
function withByteTracking(source, tracked) {
|
|
3248
|
+
return {
|
|
3249
|
+
async *[Symbol.asyncIterator]() {
|
|
3250
|
+
for await (const chunk of source) {
|
|
3251
|
+
tracked.bytes += Buffer.byteLength(chunk, "utf8");
|
|
3252
|
+
yield chunk;
|
|
3253
|
+
}
|
|
3254
|
+
}
|
|
3255
|
+
};
|
|
3256
|
+
}
|
|
3257
|
+
function createProgressPrinter(stderr, options) {
|
|
3258
|
+
const minIntervalMs = options.tty ? 80 : 500;
|
|
3259
|
+
let lastAt = 0;
|
|
3260
|
+
let lastLen = 0;
|
|
3261
|
+
let lastText = "";
|
|
3262
|
+
const lineFor = (bytesRead, report) => formatProgressLine({
|
|
3263
|
+
label: options.label,
|
|
3264
|
+
bytesRead,
|
|
3265
|
+
totalBytes: options.totalBytes,
|
|
3266
|
+
lineCount: report.lineCount,
|
|
3267
|
+
totalMatches: report.totalMatches
|
|
3268
|
+
});
|
|
3269
|
+
const paint = (text, newline) => {
|
|
3270
|
+
lastText = text;
|
|
3271
|
+
if (options.tty) {
|
|
3272
|
+
const padded = text.length < lastLen ? `${text}${" ".repeat(lastLen - text.length)}` : text;
|
|
3273
|
+
stderr.write(`\r${padded}`);
|
|
3274
|
+
lastLen = text.length;
|
|
3275
|
+
if (newline) {
|
|
3276
|
+
stderr.write("\n");
|
|
3277
|
+
}
|
|
3278
|
+
} else {
|
|
3279
|
+
stderr.write(`${text}
|
|
3280
|
+
`);
|
|
3281
|
+
}
|
|
3282
|
+
};
|
|
3283
|
+
return {
|
|
3284
|
+
update(bytesRead, report) {
|
|
3285
|
+
const now = Date.now();
|
|
3286
|
+
if (now - lastAt < minIntervalMs) {
|
|
3287
|
+
return;
|
|
3288
|
+
}
|
|
3289
|
+
lastAt = now;
|
|
3290
|
+
paint(lineFor(bytesRead, report), false);
|
|
3291
|
+
},
|
|
3292
|
+
finish(bytesRead, report) {
|
|
3293
|
+
const text = lineFor(bytesRead, report);
|
|
3294
|
+
if (!options.tty && text === lastText) {
|
|
3295
|
+
return;
|
|
3296
|
+
}
|
|
3297
|
+
paint(text, true);
|
|
3298
|
+
}
|
|
3299
|
+
};
|
|
3300
|
+
}
|
|
3301
|
+
async function runCli(argv, io = {
|
|
3302
|
+
stdin: process.stdin,
|
|
3303
|
+
stdout: process.stdout,
|
|
3304
|
+
stderr: process.stderr
|
|
3305
|
+
}) {
|
|
3306
|
+
let flags;
|
|
3307
|
+
try {
|
|
3308
|
+
flags = parseCli(argv);
|
|
3309
|
+
} catch (error) {
|
|
3310
|
+
io.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
3311
|
+
`);
|
|
3312
|
+
return EXIT_USAGE;
|
|
3313
|
+
}
|
|
3314
|
+
if (flags.help) {
|
|
3315
|
+
io.stdout.write(HELP);
|
|
3316
|
+
return EXIT_OK;
|
|
3317
|
+
}
|
|
3318
|
+
if (flags.version) {
|
|
3319
|
+
const pkgUrl = new URL("../package.json", import.meta.url);
|
|
3320
|
+
const pkg = JSON.parse(await readFile(pkgUrl, "utf8"));
|
|
3321
|
+
io.stdout.write(`${pkg.version}
|
|
3322
|
+
`);
|
|
3323
|
+
return EXIT_OK;
|
|
3324
|
+
}
|
|
3325
|
+
if (!flags.input) {
|
|
3326
|
+
io.stderr.write('Missing input path. Use "-" for stdin.\n');
|
|
3327
|
+
io.stderr.write(HELP);
|
|
3328
|
+
return EXIT_USAGE;
|
|
3329
|
+
}
|
|
3330
|
+
const extraRules = flags.rulesFile ? await loadRulesFile(flags.rulesFile) : [];
|
|
3331
|
+
let ruleIds = [...builtinRuleIds];
|
|
3332
|
+
if (flags.rules) {
|
|
3333
|
+
ruleIds = flags.rules.split(",").map((id) => id.trim());
|
|
3334
|
+
}
|
|
3335
|
+
if (flags.excludeRules) {
|
|
3336
|
+
const skip = new Set(flags.excludeRules.split(",").map((id) => id.trim()));
|
|
3337
|
+
ruleIds = ruleIds.filter((id) => !skip.has(id));
|
|
3338
|
+
}
|
|
3339
|
+
const neverValues = [...flags.exclude];
|
|
3340
|
+
if (flags.excludeFile) {
|
|
3341
|
+
neverValues.push(...await readLines(flags.excludeFile));
|
|
3342
|
+
}
|
|
3343
|
+
const alwaysValues = [...flags.redact];
|
|
3344
|
+
if (flags.redactFile) {
|
|
3345
|
+
alwaysValues.push(...await readLines(flags.redactFile));
|
|
3346
|
+
}
|
|
3347
|
+
const suppliedKey = Boolean(flags.key || flags.keyFile);
|
|
3348
|
+
let key = flags.key;
|
|
3349
|
+
if (flags.keyFile) {
|
|
3350
|
+
key = (await readFile(flags.keyFile, "utf8")).trim();
|
|
3351
|
+
}
|
|
3352
|
+
if (!key) {
|
|
3353
|
+
key = generateKey();
|
|
3354
|
+
}
|
|
3355
|
+
if (flags.printKey) {
|
|
3356
|
+
io.stderr.write(`${key}
|
|
3357
|
+
`);
|
|
3358
|
+
}
|
|
3359
|
+
const options = {
|
|
3360
|
+
rules: ruleIds,
|
|
3361
|
+
extraRules: extraRules.length > 0 ? extraRules : void 0,
|
|
3362
|
+
aggressive: flags.aggressive,
|
|
3363
|
+
key,
|
|
3364
|
+
keyEncoding: flags.keyEncoding ?? (suppliedKey ? "utf8" : "hex"),
|
|
3365
|
+
alwaysRedact: alwaysValues.length > 0 ? { values: alwaysValues } : void 0,
|
|
3366
|
+
neverRedact: neverValues.length > 0 ? { values: neverValues } : void 0
|
|
3367
|
+
};
|
|
3368
|
+
const sanitizer = createSanitizer(options);
|
|
3369
|
+
const useStdin = flags.input === "-";
|
|
3370
|
+
const source = useStdin ? fromNodeStream(io.stdin) : fromFile(flags.input);
|
|
3371
|
+
const tty = streamIsTty(io.stderr);
|
|
3372
|
+
const showProgress = flags.noProgress ? false : flags.progress || tty && !flags.quiet;
|
|
3373
|
+
const totalBytes = showProgress && !useStdin ? (await stat(flags.input)).size : void 0;
|
|
3374
|
+
const tracked = { bytes: 0 };
|
|
3375
|
+
const trackedSource = showProgress ? withByteTracking(source, tracked) : source;
|
|
3376
|
+
const progress = showProgress ? createProgressPrinter(io.stderr, {
|
|
3377
|
+
tty,
|
|
3378
|
+
label: progressLabel(flags.input, useStdin),
|
|
3379
|
+
totalBytes
|
|
3380
|
+
}) : void 0;
|
|
3381
|
+
const writeStdout = flags.stdout || useStdin && !flags.out;
|
|
3382
|
+
let outputPath = flags.out;
|
|
3383
|
+
if (!writeStdout && !flags.dryRun && !outputPath && !useStdin) {
|
|
3384
|
+
outputPath = join(dirname(flags.input), `${basename(flags.input)}.sanitized`);
|
|
3385
|
+
}
|
|
3386
|
+
const sink = flags.dryRun ? toNullSink() : writeStdout ? toNodeStream(io.stdout) : toFile(outputPath);
|
|
3387
|
+
const report = await sanitizer.sanitizeStream(trackedSource, sink, {
|
|
3388
|
+
onProgress: progress ? (snapshot) => {
|
|
3389
|
+
progress.update(tracked.bytes, snapshot.report);
|
|
3390
|
+
} : void 0
|
|
3391
|
+
});
|
|
3392
|
+
progress?.finish(tracked.bytes, report);
|
|
3393
|
+
if (flags.report) {
|
|
3394
|
+
const body = flags.reportFormat === "text" ? formatReportText(report) : `${JSON.stringify(report, null, 2)}
|
|
3395
|
+
`;
|
|
3396
|
+
await writeFile(flags.report, body, "utf8");
|
|
3397
|
+
}
|
|
3398
|
+
if (!flags.quiet && !writeStdout) {
|
|
3399
|
+
io.stderr.write(formatReportText(report));
|
|
3400
|
+
}
|
|
3401
|
+
if (flags.failOnMatch && report.totalMatches > 0) {
|
|
3402
|
+
return EXIT_ERROR;
|
|
3403
|
+
}
|
|
3404
|
+
return EXIT_OK;
|
|
3405
|
+
}
|
|
3406
|
+
async function main() {
|
|
3407
|
+
try {
|
|
3408
|
+
process.exitCode = await runCli(process.argv.slice(2));
|
|
3409
|
+
} catch (error) {
|
|
3410
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
3411
|
+
`);
|
|
3412
|
+
process.exitCode = EXIT_ERROR;
|
|
3413
|
+
}
|
|
3414
|
+
}
|
|
3415
|
+
function isCliEntryPoint(entryPath = process.argv[1]) {
|
|
3416
|
+
if (!entryPath) {
|
|
3417
|
+
return false;
|
|
3418
|
+
}
|
|
3419
|
+
try {
|
|
3420
|
+
return realpathSync(fileURLToPath(import.meta.url)) === realpathSync(entryPath);
|
|
3421
|
+
} catch {
|
|
3422
|
+
return false;
|
|
3423
|
+
}
|
|
3424
|
+
}
|
|
3425
|
+
if (isCliEntryPoint()) {
|
|
3426
|
+
void main();
|
|
3427
|
+
}
|
|
3428
|
+
|
|
3429
|
+
export { EXIT_ERROR, EXIT_OK, EXIT_USAGE, isCliEntryPoint, runCli };
|
|
3430
|
+
//# sourceMappingURL=cli.js.map
|
|
3431
|
+
//# sourceMappingURL=cli.js.map
|