@socprime/logtotal-sanitizer 0.0.1-beta.3 → 0.2.0-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 CHANGED
@@ -7,6 +7,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.0-beta.0] - 2026-09-07
11
+
12
+ ### Added
13
+
14
+ - `mergeReports` combines reports from disjoint, ordered ranges of the same input: counts and
15
+ line totals are summed, distinct replacements are merged by `(ruleId, original)`, and preview
16
+ segments are concatenated until `previewBytes`.
17
+ - `planLineAlignedRanges` splits a `Blob` / `File` into newline-aligned byte ranges so callers can
18
+ sanitize parts concurrently and concatenate the outputs.
19
+ - `report.maxReplacementsPerRule` caps distinct original values stored per rule. Counts stay
20
+ complete; `replacementsTruncated` is set when the cap drops at least one value. Unset by
21
+ default, so CLI `--report json` still dumps the full list.
22
+ - `sanitizeStream` accepts a per-run `previewBytes` override so one sanitizer can collect a
23
+ preview from only the first range.
24
+
25
+ ### Changed
26
+
27
+ - Most positive lookbehind fragments now consume their prefix and capture only the value to redact.
28
+ JavaScriptCore (Safari) evaluates lookbehind-led alternatives about 15× slower than V8; this
29
+ rewrite is equivalent on value spans and is faster in both engines. Two fragments keep their
30
+ lookbehind because consumption would not be equivalent there.
31
+ - Patterns are compiled one regex per fragment, behind a cheap per-rule prefilter, instead of a
32
+ single combined pattern. Match selection is unchanged: the leftmost value wins, and ties go to
33
+ the earlier rule and then the earlier fragment.
34
+ - Sanitizing 1 MB of Windows PowerShell logs is about 4.9× faster under JavaScriptCore and about
35
+ 1.6× faster under V8. Output is byte-identical: verified against the previous implementation on
36
+ 318k real log lines and 1.8M generated adversarial lines across every rule, both modes, JSON
37
+ mode, allowlists and segment reporting.
38
+ - The CLI skips collecting the replacement list and preview unless `--report` is JSON, so a
39
+ normal run no longer pays for detail it never prints.
40
+
10
41
  ## [0.0.1-beta.3] - 2026-08-26
11
42
 
12
43
  ### Fixed
package/README.md CHANGED
@@ -50,6 +50,7 @@ Reuse `key` across files when the same original value must map to the same token
50
50
  | `alwaysRedact` | — | Extra literals and regexes, highest match priority. |
51
51
  | `neverRedact` | — | Allowlist. Highest overall priority: matching values are left unchanged. |
52
52
  | `report.contextChars` | `0` | Characters of surrounding text on each unique replacement. |
53
+ | `report.maxReplacementsPerRule` | — | Cap on distinct originals stored per rule. Counts stay complete. |
53
54
 
54
55
  ### Priority
55
56
 
@@ -116,7 +117,7 @@ const ticket = defineRule({
116
117
  const sanitizer = createSanitizer({ rules: ['secrets', ticket] });
117
118
  ```
118
119
 
119
- `id` must be a JavaScript identifier (it becomes a named capture group). Use non-capturing groups in `patterns`. Put expensive checks in `validate`, not in the regex.
120
+ `id` must be a JavaScript identifier. At most one capturing group per fragment; it marks the value to replace (the rest of the match is kept). Put expensive checks in `validate`, not in the regex.
120
121
 
121
122
  ## Streaming and I/O
122
123
 
@@ -129,6 +130,8 @@ const output = sink.result();
129
130
  ```
130
131
 
131
132
  Browser helpers: `fromBlob`, `fromWebStream`, `toWebStream`.
133
+ `planLineAlignedRanges(blob, partCount)` returns newline-aligned byte ranges for concurrent
134
+ sanitization; `mergeReports(reports)` folds the resulting reports back together.
132
135
 
133
136
  Node helpers (subpath `@socprime/logtotal-sanitizer/node`):
134
137
 
@@ -150,7 +153,8 @@ The root entry does not import `node:*`.
150
153
  totalMatches: 3,
151
154
  lineCount: 40,
152
155
  replacements: [{ ruleId, original, replacement, count, contextBefore?, contextAfter? }],
153
- preview: { before: [{ text, changed }], after: [{ text, changed }] }
156
+ preview: { before: [{ text, changed }], after: [{ text, changed }] },
157
+ replacementsTruncated?: true
154
158
  }
155
159
  ```
156
160
 
@@ -86,13 +86,13 @@ var geoLocationRule = {
86
86
  patterns: [
87
87
  "(?:\\(\\s*-?\\d{1,3}\\.\\d{3,}\\s*[,;]\\s*-?\\d{1,3}\\.\\d{3,}\\s*\\))",
88
88
  "(?:(?<![\\d.])-?\\d{1,3}\\.\\d{3,}\\s*[,;]\\s*-?\\d{1,3}\\.\\d{3,}(?!\\d))",
89
- `(?:(?<=\\b${LAT_KEY}\\s*[=:]\\s*)-?\\d{1,3}\\.\\d+)`,
90
- `(?:(?<=\\b${LON_KEY}\\s*[=:]\\s*)-?\\d{1,3}\\.\\d+)`,
91
- `(?:(?<=\\bgeohash\\s*[=:]\\s*)${GEOHASH}{5,12}\\b)`,
89
+ `(?:\\b${LAT_KEY}\\s*[=:]\\s*)(-?\\d{1,3}\\.\\d+)`,
90
+ `(?:\\b${LON_KEY}\\s*[=:]\\s*)(-?\\d{1,3}\\.\\d+)`,
91
+ `(?:\\bgeohash\\s*[=:]\\s*)(${GEOHASH}{5,12}\\b)`,
92
92
  `(?:(?<![\\w+])${OLC}{4,8}\\+${OLC}{2,3}(?![\\w+]))`,
93
93
  `(?:\\b\\d{1,3}\xB0\\d{1,2}'\\d{1,2}(?:\\.\\d+)?"[NSEW]\\b)`,
94
94
  "(?:\\b[A-Za-z]{1,2}\\d{1,2}[A-Za-z]?\\s\\d[A-Za-z]{2}\\b)",
95
- `(?:(?<=\\b${ZIP_KEY}\\s*[=:]\\s*)\\d{5}(?:-\\d{4})?\\b)`
95
+ `(?:\\b${ZIP_KEY}\\s*[=:]\\s*)(\\d{5}(?:-\\d{4})?\\b)`
96
96
  ],
97
97
  aggressivePatterns: [
98
98
  "(?:\\(\\s*-?\\d{1,3}\\.\\d{2,}\\s*[,;]\\s*-?\\d{1,3}\\.\\d{2,}\\s*\\))",
@@ -206,14 +206,11 @@ var govIdentifiersRule = {
206
206
  "(?:\\b(?:NHS|nhs)(?:[_-]?number)?\\s*[=:]\\s*\\d{10}\\b)",
207
207
  "(?:\\b(?!BG|GB|NK|KN|TN|NT|ZZ)[A-CEGHJ-PR-TW-Z][A-CEGHJ-NPR-TW-Z]\\d{6}\\s?[A-D]\\b)",
208
208
  "(?:(?<![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)",
209
- `(?:(?<=\\b${SSN_CTX}\\s*[=:]?\\s*)\\d{9}\\b)`,
210
- "(?:(?<=(?:\u0406\u041F\u041D|\u0420\u041D\u041E\u041A\u041F\u041F|tax[_-]?id)\\s*[=:]?\\s*)\\d{10}\\b)",
211
- `(?:(?<=\\b${ID_CTX}\\s*[=:]\\s*)[A-Za-z0-9<]{5,32})`
212
- ],
213
- aggressivePatterns: [
214
- "(?:\\b[A-Z]{1,2}\\d{6,9}\\b)",
215
- "(?:\\b\\d{9,11}\\b)"
209
+ `(?:\\b${SSN_CTX}\\s*[=:]?\\s*)(\\d{9})\\b`,
210
+ "(?:(?:\u0406\u041F\u041D|\u0420\u041D\u041E\u041A\u041F\u041F|tax[_-]?id)\\s*[=:]?\\s*)(\\d{10})\\b",
211
+ `(?:\\b${ID_CTX}\\s*[=:]\\s*)([A-Za-z0-9<]{5,32})`
216
212
  ],
213
+ aggressivePatterns: ["(?:\\b[A-Z]{1,2}\\d{6,9}\\b)", "(?:\\b\\d{9,11}\\b)"],
217
214
  validate: validateGovId,
218
215
  jsonKeys: [
219
216
  "ssn",
@@ -262,11 +259,9 @@ var healthInfoRule = {
262
259
  "(?:\\b(?:NDC|[Nn]dc)[:\\s]+\\d{4,5}-\\d{3,4}-\\d{1,2}\\b)",
263
260
  "(?:\\b[ABFGMPRX][A-Z9]\\d{7}\\b)",
264
261
  "(?:\\b(?:[Nn][Pp][Ii])\\s*[=:]\\s*\\d{10}\\b)",
265
- `(?:(?<=\\b${MRN_KEY}\\s*[=:]\\s*)[A-Za-z0-9-]{4,}\\b)`
266
- ],
267
- aggressivePatterns: [
268
- "(?:(?<![A-Za-z0-9])[A-TV-Z]\\d{2}(?:\\.\\w{1,4})?(?![A-Za-z0-9:]))"
262
+ `(?:\\b${MRN_KEY}\\s*[=:]\\s*)([A-Za-z0-9-]{4,})\\b`
269
263
  ],
264
+ aggressivePatterns: ["(?:(?<![A-Za-z0-9])[A-TV-Z]\\d{2}(?:\\.\\w{1,4})?(?![A-Za-z0-9:]))"],
270
265
  validate: validateHealth,
271
266
  jsonKeys: [
272
267
  "diagnosis",
@@ -301,18 +296,18 @@ var homePathsRule = {
301
296
  mode: "mask",
302
297
  token: "PATH",
303
298
  patterns: [
304
- `(?:(?<=/export/home/)${USER_SEG})`,
305
- `(?:(?<=/usr/home/)${USER_SEG})`,
306
- `(?:(?<=/var/home/)${USER_SEG})`,
307
- `(?:(?<=/home/)${USER_SEG})`,
308
- `(?:(?<=/Users/)${USER_SEG})`,
309
- `(?:(?<=[A-Za-z]:\\\\Users\\\\)${USER_SEG})`,
310
- `(?:(?<=[A-Za-z]:/Users/)${USER_SEG})`,
311
- `(?:(?<=\\\\\\\\\\?\\\\[A-Za-z]:\\\\Users\\\\)${USER_SEG})`,
312
- `(?:(?<=[A-Za-z]:\\\\Documents and Settings\\\\)${USER_SEG})`,
313
- `(?:(?<=[A-Za-z]:\\\\\\\\Users\\\\\\\\)${USER_SEG})`,
314
- `(?:(?<=/mnt/[A-Za-z]/Users/)${USER_SEG})`,
315
- `(?:(?<=//wsl\\$/(?:[A-Za-z0-9_-]+)/home/)${USER_SEG})`
299
+ `(?:/export/home/(${USER_SEG}))`,
300
+ `(?:/usr/home/(${USER_SEG}))`,
301
+ `(?:/var/home/(${USER_SEG}))`,
302
+ `(?:/home/(${USER_SEG}))`,
303
+ `(?:/Users/(${USER_SEG}))`,
304
+ `(?:[A-Za-z]:\\\\Users\\\\(${USER_SEG}))`,
305
+ `(?:[A-Za-z]:/Users/(${USER_SEG}))`,
306
+ `(?:\\\\\\\\\\?\\\\[A-Za-z]:\\\\Users\\\\(${USER_SEG}))`,
307
+ `(?:[A-Za-z]:\\\\Documents and Settings\\\\(${USER_SEG}))`,
308
+ `(?:[A-Za-z]:\\\\\\\\Users\\\\\\\\(${USER_SEG}))`,
309
+ `(?:/mnt/[A-Za-z]/Users/(${USER_SEG}))`,
310
+ `(?://wsl\\$/(?:[A-Za-z0-9_-]+)/home/(${USER_SEG}))`
316
311
  ],
317
312
  validate: validateHomeUser
318
313
  };
@@ -1281,18 +1276,18 @@ var TLD_EXACT = [...INTERNAL_COMPOUND_SUFFIXES, ...GTLD_UNRESTRICTED, ...INTERNA
1281
1276
  var TLD_COLLISION = FILE_EXT_COLLISION_TLDS.join("|");
1282
1277
  var LABEL = "[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?";
1283
1278
  var HOST_EXACT = `(?:(?<![\\w.$-])(?<![^:/\\\\][/\\\\])(?:${LABEL}\\.)+(?:${TLD_EXACT})(?!\\.?\\w))`;
1284
- var HOST_COLLISION = `(?:(?<=(?:https?|sftp|ftps?|wss?)://(?:[^/@\\s]+@)?)(?:${LABEL}\\.)+(?:${TLD_COLLISION})(?!\\.?\\w))`;
1285
- var UNC_COMPUTER = "(?:(?<=\\\\\\\\)[A-Za-z][A-Za-z0-9-]{0,14}(?=\\\\[A-Za-z0-9$]))";
1286
- var SPN_HOST = "(?:(?<=\\b[A-Za-z][A-Za-z0-9]*/)[A-Za-z0-9][A-Za-z0-9.-]*(?=@[A-Z0-9.-]+\\b))";
1279
+ var HOST_COLLISION = `(?:(?:https?|sftp|ftps?|wss?)://(?:[^/@\\s]+@)?)((?:${LABEL}\\.)+(?:${TLD_COLLISION}))(?!\\.?\\w)`;
1280
+ var UNC_COMPUTER = "(?:\\\\\\\\)([A-Za-z][A-Za-z0-9-]{0,14})(?=\\\\[A-Za-z0-9$])";
1281
+ var SPN_HOST = "(?:\\b[A-Za-z][A-Za-z0-9]*/)([A-Za-z0-9][A-Za-z0-9.-]*)(?=@[A-Z0-9.-]+\\b)";
1287
1282
  var HOST_CTX_KEY = "(?:host|hostname|computer)";
1288
- var HOST_CTX = `(?:(?<=\\b${HOST_CTX_KEY}\\s*[=:]\\s*)[A-Za-z0-9](?:[A-Za-z0-9.-]*[A-Za-z0-9])?)`;
1283
+ var HOST_CTX = `(?:\\b${HOST_CTX_KEY}\\s*[=:]\\s*)([A-Za-z0-9](?:[A-Za-z0-9.-]*[A-Za-z0-9])?)`;
1289
1284
  var NETBIOS_AGGRESSIVE = "(?:\\b(?:WIN|DESKTOP)-[A-Z0-9]{7,10}\\b)";
1290
1285
  var SYSLOG_MONTH = "(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)";
1291
1286
  var SYSLOG_BSD_TS = `${SYSLOG_MONTH}\\s+\\d{1,2}\\s+\\d{2}:\\d{2}:\\d{2}`;
1292
1287
  var SYSLOG_ISO_TS = "\\d{4}-\\d{2}-\\d{2}[T ]\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?(?:Z|[+-]\\d{2}:?\\d{2})?";
1293
1288
  var SYSLOG_HOST_NAME = `${LABEL}(?:\\.${LABEL})*`;
1294
1289
  var SYSLOG_HOST_LOOKAHEAD = "(?=\\s+[A-Za-z][A-Za-z0-9_.@-]*(?:\\[\\d+\\])?:)";
1295
- var SYSLOG_HOST = `(?:(?<=(?:${SYSLOG_BSD_TS}|${SYSLOG_ISO_TS})\\s+)${SYSLOG_HOST_NAME}${SYSLOG_HOST_LOOKAHEAD})`;
1290
+ var SYSLOG_HOST = `(?:(?:${SYSLOG_BSD_TS}|${SYSLOG_ISO_TS})\\s+)(${SYSLOG_HOST_NAME})${SYSLOG_HOST_LOOKAHEAD}`;
1296
1291
  var INVENTORY_PREFIX = "(?:[Ss][Rr][Vv]|[Ww][Ee][Bb]|[Dd][Bb]|[Aa][Pp][Pp]|[Hh][Oo][Ss][Tt]|[Nn][Oo][Dd][Ee]|[Pp][Oo][Dd])";
1297
1292
  var INVENTORY_HOST = `(?:\\b${INVENTORY_PREFIX}-[A-Za-z0-9]+(?:-[A-Za-z0-9]+)*-\\d+\\b)`;
1298
1293
  var hostnamesRule = {
@@ -1795,16 +1790,16 @@ var paymentInfoRule = {
1795
1790
  "(?:(?<![.\\d])\\b\\d(?:[ -]?\\d){12,18}\\b)",
1796
1791
  "(?:\\b[A-Z]{2}\\d{2}[A-Z0-9]{10,30}\\b)",
1797
1792
  "(?:\\b[A-Z]{2}\\d{2}(?: [A-Z0-9]{4}){2,7}(?: [A-Z0-9]{1,4})?\\b)",
1798
- `(?:(?<=\\b${CVV_KEY}\\s*[=:]\\s*)\\d{3,4}\\b)`,
1799
- `(?:(?<=\\b${EXP_KEY}\\s*[=:]\\s*)(?:0[1-9]|1[0-2])[/-](?:\\d{2}|\\d{4})\\b)`,
1793
+ `(?:\\b${CVV_KEY}\\s*[=:]\\s*)(\\d{3,4}\\b)`,
1794
+ `(?:\\b${EXP_KEY}\\s*[=:]\\s*)((?:0[1-9]|1[0-2])[/-](?:\\d{2}|\\d{4})\\b)`,
1800
1795
  "(?:\\bbc1[a-z0-9]{25,39}\\b)",
1801
1796
  "(?:(?<![A-Za-z0-9])[13][A-HJ-NP-Za-km-z1-9]{25,34}(?![A-Za-z0-9]))",
1802
1797
  "(?:\\b0x[A-Fa-f0-9]{40}\\b)"
1803
1798
  ],
1804
1799
  aggressivePatterns: [
1805
1800
  "(?:(?<![\\\\/])\\b[A-Z]{6}[A-Z0-9]{2}(?:[A-Z0-9]{3})?\\b(?![\\\\/]))",
1806
- `(?:(?<=\\b${ROUTING_KEY}\\s*[=:]\\s*)\\d{9}\\b)`,
1807
- `(?:(?<=\\b${ACCOUNT_KEY}\\s*[=:]\\s*)\\d{8,12}\\b)`
1801
+ `(?:\\b${ROUTING_KEY}\\s*[=:]\\s*)(\\d{9}\\b)`,
1802
+ `(?:\\b${ACCOUNT_KEY}\\s*[=:]\\s*)(\\d{8,12}\\b)`
1808
1803
  ],
1809
1804
  validate: validatePayment,
1810
1805
  jsonKeys: [
@@ -1844,9 +1839,9 @@ var phoneNumbersRule = {
1844
1839
  "(?:\\b1-\\d{3}-\\d{3}-\\d{4}\\b)",
1845
1840
  "(?:\\b0\\d{2}-\\d{3}-\\d{2}-\\d{2}\\b)",
1846
1841
  "(?:\\b0\\d{2}\\s\\d{3}\\s\\d{2}\\s\\d{2}\\b)",
1847
- "(?:(?<![.\\d])\\b00[1-9](?:[\\s().-]*\\d){7,13}(?!\\d))",
1848
- "(?:(?<=\\b(?:tel|sms):)\\+?[1-9](?:[\\s().-]*\\d){6,14}(?!\\d))",
1849
- `(?:(?<=\\b${PHONE_KEY}\\s*[=:]\\s*)\\+?[0-9](?:[\\s().-]*\\d){6,14}(?!\\d))`
1842
+ "(?:(?<![.\\d-])\\b00[1-9](?:[\\s().-]*\\d){7,13}(?!\\w)(?!-[\\dA-Fa-f]))",
1843
+ "(?:\\b(?:tel|sms):)(\\+?[1-9](?:[\\s().-]*\\d){6,14})(?!\\d)",
1844
+ `(?:\\b${PHONE_KEY}\\s*[=:]\\s*)(\\+?[0-9](?:[\\s().-]*\\d){6,14})(?!\\d)`
1850
1845
  ],
1851
1846
  validate: validatePhone,
1852
1847
  jsonKeys: [
@@ -1906,12 +1901,12 @@ var secretsRule = {
1906
1901
  mode: "mask",
1907
1902
  token: "SECRET",
1908
1903
  patterns: [
1909
- "(?:(?<=(?:Bearer|bearer|BEARER)\\s+)[A-Za-z0-9._\\-+/=]{8,})",
1910
- "(?:(?<=(?:Basic|basic|BASIC)\\s+)[A-Za-z0-9+/]{8,}={0,2})",
1904
+ "(?:(?:Bearer|bearer|BEARER)\\s+)([A-Za-z0-9._\\-+/=]{8,})",
1905
+ "(?:(?:Basic|basic|BASIC)\\s+)([A-Za-z0-9+/]{8,}={0,2})",
1911
1906
  "(?:(?<![A-Za-z0-9_-])eyJ[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}(?:\\.[A-Za-z0-9_-]{8,})?)",
1912
1907
  "(?:-----BEGIN (?:(?:RSA |EC |DSA |OPENSSH |ENCRYPTED )?PRIVATE KEY|CERTIFICATE)-----[A-Za-z0-9+/=\\s\\\\]+?-----END (?:(?:RSA |EC |DSA |OPENSSH |ENCRYPTED )?PRIVATE KEY|CERTIFICATE)-----)",
1913
1908
  "(?:\\b(?:AKIA|ASIA|ABIA|ACCA)[A-Z0-9]{16}\\b)",
1914
- "(?:(?<=(?:aws_secret_access_key|AWS_SECRET_ACCESS_KEY)\\s*[=:]\\s*)[A-Za-z0-9/+=]{30,})",
1909
+ "(?:(?:aws_secret_access_key|AWS_SECRET_ACCESS_KEY)\\s*[=:]\\s*)([A-Za-z0-9/+=]{30,})",
1915
1910
  "(?:github_pat_[A-Za-z0-9_]{22,})",
1916
1911
  "(?:gh[pousr]_[A-Za-z0-9]{36,})",
1917
1912
  "(?:xox[abprs]-[A-Za-z0-9-]{10,})",
@@ -1920,13 +1915,15 @@ var secretsRule = {
1920
1915
  "(?:(?:sk_live_|sk_test_|rk_live_|rk_test_)[A-Za-z0-9]{16,})",
1921
1916
  "(?:sk-proj-[A-Za-z0-9_-]{16,})",
1922
1917
  "(?:sk-[A-Za-z0-9]{20,})",
1918
+ // Kept as a lookbehind: `.{0,60}` asserts a nearby keyword rather than an adjacent prefix, so
1919
+ // one `Twilio` has to stay reusable by every identifier that follows it.
1923
1920
  "(?:(?<=(?:[Tt]wilio.{0,60}|\\b(?:[Aa]ccount[Ss]id|[Aa]uth[Tt]oken|[Ss]id)\\s*[=:]\\s*))(?:AC|SK)[0-9A-Fa-f]{32})",
1924
1921
  "(?:SG\\.[A-Za-z0-9_-]{10,}\\.[A-Za-z0-9_-]{10,})",
1925
1922
  "(?:npm_[A-Za-z0-9]{36,})",
1926
- `(?:(?<=[?&][Ss]ig=)[^&\\s"']{16,})`,
1927
- "(?:(?<=[a-zA-Z][a-zA-Z0-9+.-]*://)[^/@\\s:]+:[^/@\\s]+@)",
1928
- `(?:(?<=--${CLI_FLAGS}(?:\\s+|=)"?)[^\\s"']{4,})`,
1929
- `(?:(?<=\\b${CONTEXT_KEYS}"?\\s*[=:]\\s*"?)${CONTEXT_VALUE})`
1923
+ `(?:[?&][Ss]ig=)([^&\\s"']{16,})`,
1924
+ "(?:[a-zA-Z][a-zA-Z0-9+.-]*://)([^/@\\s:]+:[^/@\\s]+@)",
1925
+ `(?:--${CLI_FLAGS}(?:\\s+|=)"?)([^\\s"']{4,})`,
1926
+ `(?:\\b${CONTEXT_KEYS}"?\\s*[=:]\\s*"?)(${CONTEXT_VALUE})`
1930
1927
  ],
1931
1928
  aggressivePatterns: [
1932
1929
  "(?:(?<![A-Za-z0-9+/])[A-Za-z0-9+/]{40,}={0,2}(?![A-Za-z0-9+/=]))",
@@ -1978,7 +1975,7 @@ var sessionCookiesRule = {
1978
1975
  `(?:(?:csrftoken|csrf_token|XSRF-TOKEN|xsrf-token)=${COOKIE_VALUE})`,
1979
1976
  `(?:remember_token=${COOKIE_VALUE})`,
1980
1977
  `(?:(?:SSESS|SESS)[A-Fa-f0-9]{32}=${COOKIE_VALUE})`,
1981
- `(?:(?<=[Ss]et-[Cc]ookie:\\s*)[^;=\\s]+=${COOKIE_VALUE})`
1978
+ `(?:[Ss]et-[Cc]ookie:\\s*)([^;=\\s]+=${COOKIE_VALUE})`
1982
1979
  ],
1983
1980
  aggressivePatterns: [
1984
1981
  `(?:(?<![\\w-])[\\w-]*session[\\w-]*=${COOKIE_VALUE})`,
@@ -2000,21 +1997,21 @@ var NOT_GUID = "(?![0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-)";
2000
1997
  var ACCOUNT_END = "(?![A-Za-z0-9_$\\\\/-])";
2001
1998
  var PATH_ROOT = "(?:\\b(?:HKLM|HKCU|HKU|HKCR|HKEY_[A-Z_]+)|\\b[A-Za-z]:)\\\\";
2002
1999
  var NOT_UNDER_PATH_ROOT = `(?<!${PATH_ROOT}(?:(?! [-/])[^<>'"\\n])*)`;
2003
- 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})`;
2000
+ var DOMAIN_USER = `(?:(?:(?:^|[^\\\\.-])\\b[A-Za-z0-9](?:[A-Za-z0-9.-]{0,13}[A-Za-z0-9])?\\\\)(?<!${WIN_DOMAIN_EXCLUDE})${NOT_FILENAME}${NOT_GUID}(${USER_SEG_SIMPLE})${ACCOUNT_END}${NOT_UNDER_PATH_ROOT})`;
2004
2001
  var SID = "(?:\\bS-1-(?:\\d+-){1,14}\\d+\\b)";
2005
2002
  var VALUE_WITH_LETTER = "[\\w.@+-]*[A-Za-z][\\w.@+-]*";
2006
- var LDAP_DN_SINGLE = `(?:(?<=\\b(?:uid|sAMAccountName)=)${VALUE_WITH_LETTER})`;
2003
+ var LDAP_DN_SINGLE = `(?:\\b(?:uid|sAMAccountName)=)(${VALUE_WITH_LETTER})`;
2007
2004
  var CN_VALUE = "(?:[A-Za-z0-9.'-]+(?: [A-Za-z0-9.'-]+){0,3})";
2008
- var LDAP_DN_CN = `(?:(?<=\\bcn=)${CN_VALUE})`;
2009
- var ARN_USER = "(?:(?<=:user/)[\\w.@+=,-]+)";
2010
- var ARN_ASSUMED_ROLE_SESSION = "(?:(?<=:assumed-role/[\\w+=,.@-]+/)[\\w.@+=,-]+)";
2011
- var K8S_SERVICE_ACCOUNT = "(?:(?<=\\bsystem:serviceaccount:[A-Za-z0-9-]+:)[A-Za-z0-9-]+)";
2012
- var SSHD_FOR_USER = "(?:(?<=(?:\\bfor (?:invalid )?user|\\b[Ii]nvalid user)\\s+)[A-Za-z0-9._-]+)";
2013
- var SSHD_RUSER = "(?:(?<=\\bruser=)[A-Za-z0-9._-]+)";
2014
- var SSHD_LOGNAME = "(?:(?<=\\blogname=)[A-Za-z0-9._-]+)";
2015
- var SUDO_USER = "(?:(?<=\\bsudo:\\s+)[A-Za-z0-9._-]+(?=\\s+:))";
2005
+ var LDAP_DN_CN = `(?:\\bcn=)(${CN_VALUE})`;
2006
+ var ARN_USER = "(?::user/)([\\w.@+=,-]+)";
2007
+ var ARN_ASSUMED_ROLE_SESSION = "(?::assumed-role/[\\w+=,.@-]+/)([\\w.@+=,-]+)";
2008
+ var K8S_SERVICE_ACCOUNT = "(?:\\bsystem:serviceaccount:[A-Za-z0-9-]+:)([A-Za-z0-9-]+)";
2009
+ var SSHD_FOR_USER = "(?:(?:\\bfor (?:invalid )?user|\\b[Ii]nvalid user)\\s+)([A-Za-z0-9._-]+)";
2010
+ var SSHD_RUSER = "(?:\\bruser=)([A-Za-z0-9._-]+)";
2011
+ var SSHD_LOGNAME = "(?:\\blogname=)([A-Za-z0-9._-]+)";
2012
+ var SUDO_USER = "(?:\\bsudo:\\s+)([A-Za-z0-9._-]+)(?=\\s+:)";
2016
2013
  var GENERIC_KEY = "(?:user|username|login|account|owner|actor|principal|requester|createdBy|modifiedBy|assignee|operator)";
2017
- var GENERIC_CTX = `(?:(?<=\\b${GENERIC_KEY}\\s*[=:]\\s*)${VALUE_WITH_LETTER})`;
2014
+ var GENERIC_CTX = `(?:\\b${GENERIC_KEY}\\s*[=:]\\s*)(${VALUE_WITH_LETTER})`;
2018
2015
  var SLACK_USER_ID_AGGRESSIVE = "(?:\\bU[A-Z0-9]{8,10}\\b)";
2019
2016
  var SYSTEM_ACCOUNTS = /* @__PURE__ */ new Set([
2020
2017
  "root",
@@ -2126,6 +2123,121 @@ function getBuiltinRule(id) {
2126
2123
  return builtinsById.get(id);
2127
2124
  }
2128
2125
 
2126
+ // src/core/regexSource.ts
2127
+ function matchParen(source, openIndex) {
2128
+ let depth = 0;
2129
+ let inClass = false;
2130
+ for (let index = openIndex; index < source.length; index += 1) {
2131
+ const char = source[index];
2132
+ if (char === "\\") {
2133
+ index += 1;
2134
+ continue;
2135
+ }
2136
+ if (inClass) {
2137
+ if (char === "]") {
2138
+ inClass = false;
2139
+ }
2140
+ continue;
2141
+ }
2142
+ if (char === "[") {
2143
+ inClass = true;
2144
+ continue;
2145
+ }
2146
+ if (char === "(") {
2147
+ depth += 1;
2148
+ } else if (char === ")") {
2149
+ depth -= 1;
2150
+ if (depth === 0) {
2151
+ return index;
2152
+ }
2153
+ }
2154
+ }
2155
+ return -1;
2156
+ }
2157
+ function countCapturingGroups(source) {
2158
+ let count = 0;
2159
+ let inClass = false;
2160
+ for (let index = 0; index < source.length; index += 1) {
2161
+ const char = source[index];
2162
+ if (char === "\\") {
2163
+ index += 1;
2164
+ continue;
2165
+ }
2166
+ if (inClass) {
2167
+ if (char === "]") {
2168
+ inClass = false;
2169
+ }
2170
+ continue;
2171
+ }
2172
+ if (char === "[") {
2173
+ inClass = true;
2174
+ continue;
2175
+ }
2176
+ if (char !== "(") {
2177
+ continue;
2178
+ }
2179
+ const next = source.slice(index, index + 4);
2180
+ if (next.startsWith("(?:") || next.startsWith("(?=") || next.startsWith("(?!") || next.startsWith("(?<=") || next.startsWith("(?<!")) {
2181
+ continue;
2182
+ }
2183
+ count += 1;
2184
+ }
2185
+ return count;
2186
+ }
2187
+ function deriveGateSource(source) {
2188
+ let output = "";
2189
+ let inClass = false;
2190
+ for (let index = 0; index < source.length; index += 1) {
2191
+ const char = source[index];
2192
+ if (char === "\\") {
2193
+ output += source.slice(index, index + 2);
2194
+ index += 1;
2195
+ continue;
2196
+ }
2197
+ if (inClass) {
2198
+ output += char;
2199
+ if (char === "]") {
2200
+ inClass = false;
2201
+ }
2202
+ continue;
2203
+ }
2204
+ if (char === "[") {
2205
+ inClass = true;
2206
+ output += char;
2207
+ continue;
2208
+ }
2209
+ if (char === "(" && source.startsWith("(?<=", index)) {
2210
+ const close = matchParen(source, index);
2211
+ if (close === -1) {
2212
+ output += char;
2213
+ continue;
2214
+ }
2215
+ output += `(?:${source.slice(index + 4, close)})`;
2216
+ index = close;
2217
+ continue;
2218
+ }
2219
+ if (char === "(" && (source.startsWith("(?<!", index) || source.startsWith("(?=", index) || source.startsWith("(?!", index))) {
2220
+ const close = matchParen(source, index);
2221
+ if (close === -1) {
2222
+ output += char;
2223
+ continue;
2224
+ }
2225
+ index = close;
2226
+ continue;
2227
+ }
2228
+ output += char;
2229
+ }
2230
+ return output;
2231
+ }
2232
+ function lineHasAnyChar(line, chars) {
2233
+ for (let index = 0; index < line.length; index += 1) {
2234
+ if (chars.includes(line[index])) {
2235
+ return true;
2236
+ }
2237
+ }
2238
+ return false;
2239
+ }
2240
+
2129
2241
  // src/rules/validateRule.ts
2130
2242
  var RULE_ID_PATTERN = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
2131
2243
  var TOKEN_PATTERN = /^[A-Z][A-Z0-9]*$/;
@@ -2151,7 +2263,7 @@ function assertPatterns(patterns, ruleId, field) {
2151
2263
  }
2152
2264
  if (NAMED_GROUP_PATTERN.test(pattern)) {
2153
2265
  throw new InvalidRuleError(
2154
- `Rule "${ruleId}": \`${field}[${index}]\` must not declare a named capture group \u2014 the rule identifier is used as the group name.`
2266
+ `Rule "${ruleId}": \`${field}[${index}]\` must not declare a named capture group.`
2155
2267
  );
2156
2268
  }
2157
2269
  try {
@@ -2161,6 +2273,11 @@ function assertPatterns(patterns, ruleId, field) {
2161
2273
  `Rule "${ruleId}": \`${field}[${index}]\` is not a valid Unicode regular expression: ${String(cause)}`
2162
2274
  );
2163
2275
  }
2276
+ if (countCapturingGroups(pattern) > 1) {
2277
+ throw new InvalidRuleError(
2278
+ `Rule "${ruleId}": \`${field}[${index}]\` must contain at most one capturing group, which marks the value to replace.`
2279
+ );
2280
+ }
2164
2281
  return pattern;
2165
2282
  });
2166
2283
  }
@@ -2171,7 +2288,7 @@ function validateRule(rule) {
2171
2288
  const id = requireText(rule.id, String(rule.id), "id");
2172
2289
  if (!RULE_ID_PATTERN.test(id)) {
2173
2290
  throw new InvalidRuleError(
2174
- `Rule "${id}": \`id\` must be a valid ASCII identifier matching ${RULE_ID_PATTERN.source}, because it is used as a named capture group.`
2291
+ `Rule "${id}": \`id\` must be a valid ASCII identifier matching ${RULE_ID_PATTERN.source}.`
2175
2292
  );
2176
2293
  }
2177
2294
  requireText(rule.label, id, "label");
@@ -2208,6 +2325,6 @@ function defineRule(rule) {
2208
2325
  return validateRule(rule);
2209
2326
  }
2210
2327
 
2211
- export { InvalidKeyError, InvalidOptionError, InvalidRuleError, SanitizationAbortedError, SanitizerError, UnknownRuleError, builtinRuleIds, builtinRules, defineRule, getBuiltinRule, isSanitizerError, validateRule };
2212
- //# sourceMappingURL=chunk-U4HRIFW3.js.map
2213
- //# sourceMappingURL=chunk-U4HRIFW3.js.map
2328
+ export { InvalidKeyError, InvalidOptionError, InvalidRuleError, SanitizationAbortedError, SanitizerError, UnknownRuleError, builtinRuleIds, builtinRules, countCapturingGroups, defineRule, deriveGateSource, getBuiltinRule, isSanitizerError, lineHasAnyChar, validateRule };
2329
+ //# sourceMappingURL=chunk-5R34WEOD.js.map
2330
+ //# sourceMappingURL=chunk-5R34WEOD.js.map