@socprime/logtotal-sanitizer 0.0.1-beta.3 → 0.1.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,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ### Changed
11
+
12
+ - Most positive lookbehind fragments now consume their prefix and capture only the value to redact.
13
+ JavaScriptCore (Safari) evaluates lookbehind-led alternatives about 15× slower than V8; this
14
+ rewrite is equivalent on value spans and is faster in both engines. Two fragments keep their
15
+ lookbehind because consumption would not be equivalent there.
16
+ - Patterns are compiled one regex per fragment, behind a cheap per-rule prefilter, instead of a
17
+ single combined pattern. Match selection is unchanged: the leftmost value wins, and ties go to
18
+ the earlier rule and then the earlier fragment.
19
+ - Sanitizing 1 MB of Windows PowerShell logs is about 4.9× faster under JavaScriptCore and about
20
+ 1.6× faster under V8. Output is byte-identical: verified against the previous implementation on
21
+ 318k real log lines and 1.8M generated adversarial lines across every rule, both modes, JSON
22
+ mode, allowlists and segment reporting.
23
+
10
24
  ## [0.0.1-beta.3] - 2026-08-26
11
25
 
12
26
  ### Fixed
package/README.md CHANGED
@@ -116,7 +116,7 @@ const ticket = defineRule({
116
116
  const sanitizer = createSanitizer({ rules: ['secrets', ticket] });
117
117
  ```
118
118
 
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.
119
+ `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
120
 
121
121
  ## Streaming and I/O
122
122
 
@@ -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