@uniglot/wont-let-you-see 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,21 +1,31 @@
1
- import { describe, it, expect } from "vitest";
2
- import { AWS_PATTERNS, K8S_PATTERNS, COMMON_PATTERNS } from "../patterns";
1
+ import { describe, it, expect, beforeEach } from "vitest";
2
+ import { loadPatterns, resetPatternCache, getPatternByName } from "../patterns";
3
+
4
+ function getPattern(name: string) {
5
+ const p = getPatternByName(name);
6
+ if (!p) throw new Error(`Pattern '${name}' not found`);
7
+ return p.pattern;
8
+ }
9
+
10
+ describe("AWS Patterns", () => {
11
+ beforeEach(() => {
12
+ resetPatternCache();
13
+ });
3
14
 
4
- describe("AWS_PATTERNS", () => {
5
15
  describe("ARN", () => {
6
16
  it("should match aws partition", () => {
7
17
  expect(
8
- AWS_PATTERNS.arn.test("arn:aws:iam::123456789012:user/admin"),
18
+ getPattern("arn").test("arn:aws:iam::123456789012:user/admin"),
9
19
  ).toBe(true);
10
20
  });
11
21
 
12
22
  it("should match aws-cn partition", () => {
13
- expect(AWS_PATTERNS.arn.test("arn:aws-cn:s3:::my-bucket")).toBe(true);
23
+ expect(getPattern("arn").test("arn:aws-cn:s3:::my-bucket")).toBe(true);
14
24
  });
15
25
 
16
26
  it("should match aws-us-gov partition", () => {
17
27
  expect(
18
- AWS_PATTERNS.arn.test(
28
+ getPattern("arn").test(
19
29
  "arn:aws-us-gov:ec2:us-gov-west-1:123456789012:instance/i-123",
20
30
  ),
21
31
  ).toBe(true);
@@ -24,80 +34,86 @@ describe("AWS_PATTERNS", () => {
24
34
 
25
35
  describe("VPC resources", () => {
26
36
  it("should match vpc", () => {
27
- expect(AWS_PATTERNS.vpc.test("vpc-0123456789abcdef0")).toBe(true);
37
+ expect(getPattern("vpc").test("vpc-0123456789abcdef0")).toBe(true);
28
38
  });
29
39
 
30
40
  it("should match subnet", () => {
31
- expect(AWS_PATTERNS.subnet.test("subnet-0123456789abcdef0")).toBe(true);
41
+ expect(getPattern("subnet").test("subnet-0123456789abcdef0")).toBe(true);
32
42
  });
33
43
 
34
44
  it("should match security group", () => {
35
- expect(AWS_PATTERNS.securityGroup.test("sg-0123456789abcdef0")).toBe(
45
+ expect(getPattern("security-group").test("sg-0123456789abcdef0")).toBe(
36
46
  true,
37
47
  );
38
48
  });
39
49
 
40
50
  it("should match nat gateway", () => {
41
- expect(AWS_PATTERNS.natGateway.test("nat-0123456789abcdef0")).toBe(true);
51
+ expect(getPattern("nat-gateway").test("nat-0123456789abcdef0")).toBe(
52
+ true,
53
+ );
42
54
  });
43
55
 
44
56
  it("should match network acl", () => {
45
- expect(AWS_PATTERNS.networkAcl.test("acl-0123456789abcdef0")).toBe(true);
57
+ expect(getPattern("network-acl").test("acl-0123456789abcdef0")).toBe(
58
+ true,
59
+ );
46
60
  });
47
61
 
48
62
  it("should match eni", () => {
49
- expect(AWS_PATTERNS.eni.test("eni-0123456789abcdef0")).toBe(true);
63
+ expect(getPattern("eni").test("eni-0123456789abcdef0")).toBe(true);
50
64
  });
51
65
  });
52
66
 
53
67
  describe("EC2 resources", () => {
54
68
  it("should match ebs volume", () => {
55
- expect(AWS_PATTERNS.ebs.test("vol-0123456789abcdef0")).toBe(true);
69
+ expect(getPattern("ebs").test("vol-0123456789abcdef0")).toBe(true);
56
70
  });
57
71
 
58
72
  it("should match snapshot", () => {
59
- expect(AWS_PATTERNS.snapshot.test("snap-0123456789abcdef0")).toBe(true);
73
+ expect(getPattern("snapshot").test("snap-0123456789abcdef0")).toBe(true);
60
74
  });
61
75
  });
62
76
 
63
77
  describe("VPC networking resources", () => {
64
78
  it("should match vpc endpoint", () => {
65
- expect(AWS_PATTERNS.vpcEndpoint.test("vpce-0123456789abcdef0")).toBe(
79
+ expect(getPattern("vpc-endpoint").test("vpce-0123456789abcdef0")).toBe(
66
80
  true,
67
81
  );
68
82
  });
69
83
 
70
84
  it("should match transit gateway", () => {
71
- expect(AWS_PATTERNS.transitGateway.test("tgw-0123456789abcdef0")).toBe(
85
+ expect(getPattern("transit-gateway").test("tgw-0123456789abcdef0")).toBe(
72
86
  true,
73
87
  );
74
88
  });
75
89
 
76
90
  it("should match customer gateway", () => {
77
- expect(AWS_PATTERNS.customerGateway.test("cgw-0123456789abcdef0")).toBe(
91
+ expect(getPattern("customer-gateway").test("cgw-0123456789abcdef0")).toBe(
78
92
  true,
79
93
  );
80
94
  });
81
95
 
82
96
  it("should match vpn gateway", () => {
83
- expect(AWS_PATTERNS.vpnGateway.test("vgw-0123456789abcdef0")).toBe(true);
97
+ expect(getPattern("vpn-gateway").test("vgw-0123456789abcdef0")).toBe(
98
+ true,
99
+ );
84
100
  });
85
101
 
86
102
  it("should match vpn connection", () => {
87
- expect(AWS_PATTERNS.vpnConnection.test("vpn-0123456789abcdef0")).toBe(
103
+ expect(getPattern("vpn-connection").test("vpn-0123456789abcdef0")).toBe(
88
104
  true,
89
105
  );
90
106
  });
91
107
 
92
108
  it("should NOT match invalid vpc endpoint", () => {
93
- expect(AWS_PATTERNS.vpcEndpoint.test("vpce-invalid")).toBe(false);
109
+ expect(getPattern("vpc-endpoint").test("vpce-invalid")).toBe(false);
94
110
  });
95
111
  });
96
112
 
97
113
  describe("ECR resources", () => {
98
114
  it("should match ECR repo URI", () => {
99
115
  expect(
100
- AWS_PATTERNS.ecrRepoUri.test(
116
+ getPattern("ecr-repo").test(
101
117
  "123456789012.dkr.ecr.us-west-2.amazonaws.com/my-repo",
102
118
  ),
103
119
  ).toBe(true);
@@ -105,7 +121,7 @@ describe("AWS_PATTERNS", () => {
105
121
 
106
122
  it("should match ECR repo URI with nested path", () => {
107
123
  expect(
108
- AWS_PATTERNS.ecrRepoUri.test(
124
+ getPattern("ecr-repo").test(
109
125
  "123456789012.dkr.ecr.eu-central-1.amazonaws.com/org/app/service",
110
126
  ),
111
127
  ).toBe(true);
@@ -113,7 +129,7 @@ describe("AWS_PATTERNS", () => {
113
129
 
114
130
  it("should match ECR repo URI with dots and underscores", () => {
115
131
  expect(
116
- AWS_PATTERNS.ecrRepoUri.test(
132
+ getPattern("ecr-repo").test(
117
133
  "123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/my_repo.name",
118
134
  ),
119
135
  ).toBe(true);
@@ -121,21 +137,21 @@ describe("AWS_PATTERNS", () => {
121
137
 
122
138
  it("should NOT match invalid ECR URI (wrong account ID length)", () => {
123
139
  expect(
124
- AWS_PATTERNS.ecrRepoUri.test(
140
+ getPattern("ecr-repo").test(
125
141
  "12345.dkr.ecr.us-west-2.amazonaws.com/my-repo",
126
142
  ),
127
143
  ).toBe(false);
128
144
  });
129
145
 
130
146
  it("should NOT match non-ECR docker registry", () => {
131
- expect(AWS_PATTERNS.ecrRepoUri.test("docker.io/library/nginx")).toBe(
147
+ expect(getPattern("ecr-repo").test("docker.io/library/nginx")).toBe(
132
148
  false,
133
149
  );
134
150
  });
135
151
 
136
152
  it("should match ECR repo URI with masked account ID", () => {
137
153
  expect(
138
- AWS_PATTERNS.ecrRepoUri.test(
154
+ getPattern("ecr-repo").test(
139
155
  "#(custom-1).dkr.ecr.us-west-2.amazonaws.com/my-repo",
140
156
  ),
141
157
  ).toBe(true);
@@ -143,7 +159,7 @@ describe("AWS_PATTERNS", () => {
143
159
 
144
160
  it("should match ECR repo URI with masked account ID (higher number)", () => {
145
161
  expect(
146
- AWS_PATTERNS.ecrRepoUri.test(
162
+ getPattern("ecr-repo").test(
147
163
  "#(custom-123).dkr.ecr.us-east-1.amazonaws.com/app/service",
148
164
  ),
149
165
  ).toBe(true);
@@ -152,56 +168,60 @@ describe("AWS_PATTERNS", () => {
152
168
 
153
169
  describe("Account ID (contextual)", () => {
154
170
  it("should match OwnerId field", () => {
155
- expect(AWS_PATTERNS.accountId.test('"OwnerId": "123456789012"')).toBe(
171
+ expect(getPattern("account-id").test('"OwnerId": "123456789012"')).toBe(
156
172
  true,
157
173
  );
158
174
  });
159
175
 
160
176
  it("should match account_id field (terraform style)", () => {
161
- expect(AWS_PATTERNS.accountId.test('"account_id": "123456789012"')).toBe(
162
- true,
163
- );
177
+ expect(
178
+ getPattern("account-id").test('"account_id": "123456789012"'),
179
+ ).toBe(true);
164
180
  });
165
181
 
166
182
  it("should NOT match bare number", () => {
167
- expect(AWS_PATTERNS.accountId.test("123456789012")).toBe(false);
183
+ expect(getPattern("account-id").test("123456789012")).toBe(false);
168
184
  });
169
185
  });
170
186
 
171
187
  describe("Access Key ID", () => {
172
188
  it("should match AKIA prefix", () => {
173
- expect(AWS_PATTERNS.accessKeyId.test(" AKIAIOSFODNN7EXAMPLE ")).toBe(
189
+ expect(getPattern("access-key-id").test(" AKIAIOSFODNN7EXAMPLE ")).toBe(
174
190
  true,
175
191
  );
176
192
  });
177
193
 
178
194
  it("should match ASIA prefix (temporary)", () => {
179
- expect(AWS_PATTERNS.accessKeyId.test(" ASIAISAMPLEKEYID1234 ")).toBe(
195
+ expect(getPattern("access-key-id").test(" ASIAISAMPLEKEYID1234 ")).toBe(
180
196
  true,
181
197
  );
182
198
  });
183
199
 
184
200
  it("should NOT match random string", () => {
185
- expect(AWS_PATTERNS.accessKeyId.test("RANDOMSTRING12345678")).toBe(false);
201
+ expect(getPattern("access-key-id").test("RANDOMSTRING12345678")).toBe(
202
+ false,
203
+ );
186
204
  });
187
205
  });
188
206
  });
189
207
 
190
- describe("K8S_PATTERNS", () => {
208
+ describe("Kubernetes Patterns", () => {
209
+ beforeEach(() => {
210
+ resetPatternCache();
211
+ });
212
+
191
213
  describe("Service Account Token", () => {
192
214
  it("should match JWT format", () => {
193
215
  const jwt =
194
216
  "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImRlZmF1bHQtdG9rZW4tYWJjZGUiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiZGVmYXVsdCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjEyMzQ1Njc4LTEyMzQtMTIzNC0xMjM0LTEyMzQ1Njc4OTAxMiIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZWZhdWx0OmRlZmF1bHQifQ.signature";
195
- expect(K8S_PATTERNS.serviceAccountToken.test(jwt)).toBe(true);
217
+ expect(getPattern("k8s-token").test(jwt)).toBe(true);
196
218
  });
197
219
  });
198
220
 
199
221
  describe("Node Names", () => {
200
222
  it("should match AWS node name", () => {
201
223
  expect(
202
- K8S_PATTERNS.nodeNameAws.test(
203
- "ip-10-0-1-123.us-west-2.compute.internal",
204
- ),
224
+ getPattern("k8s-node").test("ip-10-0-1-123.us-west-2.compute.internal"),
205
225
  ).toBe(true);
206
226
  });
207
227
  });
@@ -209,7 +229,7 @@ describe("K8S_PATTERNS", () => {
209
229
  describe("Cluster Endpoints", () => {
210
230
  it("should match EKS endpoint", () => {
211
231
  expect(
212
- K8S_PATTERNS.clusterEndpoint.test(
232
+ getPattern("k8s-endpoint").test(
213
233
  "https://ABCDEF1234567890ABCD.us-west-2.eks.amazonaws.com",
214
234
  ),
215
235
  ).toBe(true);
@@ -217,14 +237,18 @@ describe("K8S_PATTERNS", () => {
217
237
  });
218
238
  });
219
239
 
220
- describe("COMMON_PATTERNS", () => {
240
+ describe("Common Patterns", () => {
241
+ beforeEach(() => {
242
+ resetPatternCache();
243
+ });
244
+
221
245
  describe("IPv4", () => {
222
246
  it("should match IPv4", () => {
223
- expect(COMMON_PATTERNS.ipv4.test("192.168.1.1")).toBe(true);
247
+ expect(getPattern("ipv4").test("192.168.1.1")).toBe(true);
224
248
  });
225
249
 
226
250
  it("should not match CIDR notation", () => {
227
- expect(COMMON_PATTERNS.ipv4.test("10.0.0.0/8")).toBe(false);
251
+ expect(getPattern("ipv4").test("10.0.0.0/8")).toBe(false);
228
252
  });
229
253
  });
230
254
 
@@ -238,16 +262,16 @@ MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC
238
262
  -----END PRIVATE KEY-----`;
239
263
 
240
264
  it("should match entire RSA private key block", () => {
241
- expect(COMMON_PATTERNS.privateKey.test(rsaKey)).toBe(true);
265
+ expect(getPattern("private-key").test(rsaKey)).toBe(true);
242
266
  });
243
267
 
244
268
  it("should match entire generic private key block", () => {
245
- expect(COMMON_PATTERNS.privateKey.test(genericKey)).toBe(true);
269
+ expect(getPattern("private-key").test(genericKey)).toBe(true);
246
270
  });
247
271
 
248
272
  it("should not match header only", () => {
249
273
  expect(
250
- COMMON_PATTERNS.privateKey.test("-----BEGIN RSA PRIVATE KEY-----"),
274
+ getPattern("private-key").test("-----BEGIN RSA PRIVATE KEY-----"),
251
275
  ).toBe(false);
252
276
  });
253
277
  });
@@ -255,39 +279,89 @@ MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC
255
279
  describe("API Key Field (contextual)", () => {
256
280
  it("should match api_key field", () => {
257
281
  expect(
258
- COMMON_PATTERNS.apiKeyField.test('"api_key": "sk-1234567890abcdef"'),
282
+ getPattern("api-key").test('"api_key": "sk-1234567890abcdef"'),
259
283
  ).toBe(true);
260
284
  });
261
285
 
262
286
  it("should match password field", () => {
263
- expect(
264
- COMMON_PATTERNS.apiKeyField.test('"password": "supersecret123"'),
265
- ).toBe(true);
287
+ expect(getPattern("api-key").test('"password": "supersecret123"')).toBe(
288
+ true,
289
+ );
266
290
  });
267
291
 
268
292
  it("should match token field", () => {
269
- expect(
270
- COMMON_PATTERNS.apiKeyField.test('"token": "ghp_xxxxxxxxxxxx"'),
271
- ).toBe(true);
293
+ expect(getPattern("api-key").test('"token": "ghp_xxxxxxxxxxxx"')).toBe(
294
+ true,
295
+ );
272
296
  });
273
297
  });
274
298
  });
275
299
 
276
300
  describe("ReDoS safety", () => {
301
+ beforeEach(() => {
302
+ resetPatternCache();
303
+ });
304
+
277
305
  it("should handle pathological input quickly", () => {
278
306
  const pathological = "a".repeat(1000) + "b";
279
307
  const start = performance.now();
280
308
 
281
- const allPatterns = [
282
- ...Object.values(AWS_PATTERNS),
283
- ...Object.values(K8S_PATTERNS),
284
- ...Object.values(COMMON_PATTERNS),
285
- ];
286
-
287
- for (const pattern of allPatterns) {
309
+ const allPatterns = loadPatterns();
310
+ for (const { pattern } of allPatterns) {
288
311
  pattern.test(pathological);
289
312
  }
290
313
 
291
314
  expect(performance.now() - start).toBeLessThan(100);
292
315
  });
293
316
  });
317
+
318
+ describe("loadPatterns", () => {
319
+ beforeEach(() => {
320
+ resetPatternCache();
321
+ });
322
+
323
+ it("should load patterns from JSON files", () => {
324
+ const patterns = loadPatterns();
325
+ expect(patterns.length).toBeGreaterThan(0);
326
+ });
327
+
328
+ it("should load AWS patterns", () => {
329
+ const patterns = loadPatterns();
330
+ const vpcPattern = patterns.find((p) => p.name === "vpc");
331
+ expect(vpcPattern).toBeDefined();
332
+ expect(vpcPattern!.pattern.test("vpc-1234567890abcdef0")).toBe(true);
333
+ });
334
+
335
+ it("should load Kubernetes patterns", () => {
336
+ const patterns = loadPatterns();
337
+ const k8sTokenPattern = patterns.find((p) => p.name === "k8s-token");
338
+ expect(k8sTokenPattern).toBeDefined();
339
+ });
340
+
341
+ it("should load common patterns", () => {
342
+ const patterns = loadPatterns();
343
+ const ipv4Pattern = patterns.find((p) => p.name === "ipv4");
344
+ expect(ipv4Pattern).toBeDefined();
345
+ expect(ipv4Pattern!.pattern.test("192.168.1.1")).toBe(true);
346
+ });
347
+
348
+ it("should mark contextual patterns correctly", () => {
349
+ const patterns = loadPatterns();
350
+ const accountIdPattern = patterns.find((p) => p.name === "account-id");
351
+ expect(accountIdPattern).toBeDefined();
352
+ expect(accountIdPattern!.isContextual).toBe(true);
353
+ });
354
+
355
+ it("should cache patterns after first load", () => {
356
+ const patterns1 = loadPatterns();
357
+ const patterns2 = loadPatterns();
358
+ expect(patterns1).toBe(patterns2);
359
+ });
360
+
361
+ it("should reset cache on resetPatternCache()", () => {
362
+ const patterns1 = loadPatterns();
363
+ resetPatternCache();
364
+ const patterns2 = loadPatterns();
365
+ expect(patterns1).not.toBe(patterns2);
366
+ });
367
+ });
package/src/masker.ts CHANGED
@@ -1,47 +1,7 @@
1
- import { AWS_PATTERNS, K8S_PATTERNS, COMMON_PATTERNS } from "./patterns";
1
+ import { loadPatterns, type LoadedPattern } from "./patterns";
2
2
  import { addEntry, getOriginal } from "./mapping";
3
3
  import { getConfig, isPatternEnabled } from "./config";
4
4
 
5
- interface PatternConfig {
6
- pattern: RegExp;
7
- type: string;
8
- isContextual?: boolean;
9
- }
10
-
11
- const PATTERN_ORDER: PatternConfig[] = [
12
- { pattern: AWS_PATTERNS.eksCluster, type: "eks-cluster" },
13
- { pattern: AWS_PATTERNS.arn, type: "arn" },
14
- { pattern: AWS_PATTERNS.accountId, type: "account-id", isContextual: true },
15
- { pattern: AWS_PATTERNS.accessKeyId, type: "access-key-id" },
16
- { pattern: AWS_PATTERNS.vpc, type: "vpc" },
17
- { pattern: AWS_PATTERNS.subnet, type: "subnet" },
18
- { pattern: AWS_PATTERNS.securityGroup, type: "security-group" },
19
- { pattern: AWS_PATTERNS.internetGateway, type: "internet-gateway" },
20
- { pattern: AWS_PATTERNS.routeTable, type: "route-table" },
21
- { pattern: AWS_PATTERNS.natGateway, type: "nat-gateway" },
22
- { pattern: AWS_PATTERNS.networkAcl, type: "network-acl" },
23
- { pattern: AWS_PATTERNS.eni, type: "eni" },
24
- { pattern: AWS_PATTERNS.vpcEndpoint, type: "vpc-endpoint" },
25
- { pattern: AWS_PATTERNS.transitGateway, type: "transit-gateway" },
26
- { pattern: AWS_PATTERNS.customerGateway, type: "customer-gateway" },
27
- { pattern: AWS_PATTERNS.vpnGateway, type: "vpn-gateway" },
28
- { pattern: AWS_PATTERNS.vpnConnection, type: "vpn-connection" },
29
- { pattern: AWS_PATTERNS.ecrRepoUri, type: "ecr-repo" },
30
- { pattern: AWS_PATTERNS.ami, type: "ami" },
31
- { pattern: AWS_PATTERNS.ec2Instance, type: "ec2-instance" },
32
- { pattern: AWS_PATTERNS.ebs, type: "ebs" },
33
- { pattern: AWS_PATTERNS.snapshot, type: "snapshot" },
34
-
35
- { pattern: K8S_PATTERNS.serviceAccountToken, type: "k8s-token" },
36
- { pattern: K8S_PATTERNS.clusterEndpoint, type: "k8s-endpoint" },
37
- { pattern: K8S_PATTERNS.kubeconfigServer, type: "k8s-endpoint" },
38
- { pattern: K8S_PATTERNS.nodeNameAws, type: "k8s-node" },
39
-
40
- { pattern: COMMON_PATTERNS.privateKey, type: "private-key" },
41
- { pattern: COMMON_PATTERNS.apiKeyField, type: "api-key", isContextual: true },
42
- { pattern: COMMON_PATTERNS.ipv4, type: "ipv4" },
43
- ];
44
-
45
5
  function removeAnchors(source: string): string {
46
6
  let result = source.replace(/^\^/, "").replace(/\$$/, "");
47
7
  if (!result.endsWith("\\b")) {
@@ -50,34 +10,58 @@ function removeAnchors(source: string): string {
50
10
  return result;
51
11
  }
52
12
 
53
- export function mask(sessionId: string, text: string): string {
54
- if (!text || typeof text !== "string") {
55
- return text;
56
- }
57
-
58
- const config = getConfig();
59
- if (!config.enabled) {
60
- return text;
61
- }
13
+ const REGEX_PREFIX = "regex:";
62
14
 
15
+ function applyCustomPatterns(
16
+ sessionId: string,
17
+ text: string,
18
+ customPatterns: string[],
19
+ ): string {
63
20
  let result = text;
64
21
 
65
- for (const customPattern of config.customPatterns) {
66
- if (result.includes(customPattern)) {
67
- const token = addEntry(sessionId, "custom", customPattern);
68
- result = result.split(customPattern).join(token);
22
+ for (const customPattern of customPatterns) {
23
+ if (customPattern.startsWith(REGEX_PREFIX)) {
24
+ const regexStr = customPattern.slice(REGEX_PREFIX.length);
25
+ const regex = new RegExp(removeAnchors(regexStr), "g");
26
+ const matches = new Set<string>();
27
+ let match;
28
+ while ((match = regex.exec(result)) !== null) {
29
+ matches.add(match[0]);
30
+ }
31
+ for (const value of matches) {
32
+ const token = addEntry(sessionId, "custom", value);
33
+ result = result.split(value).join(token);
34
+ }
35
+ } else {
36
+ const escaped = customPattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
37
+ const literalRegex = new RegExp(escaped + "(?![\\w])", "g");
38
+ if (literalRegex.test(result)) {
39
+ const token = addEntry(sessionId, "custom", customPattern);
40
+ result = result.replace(literalRegex, token);
41
+ }
69
42
  }
70
43
  }
71
44
 
72
- for (const { pattern, type, isContextual } of PATTERN_ORDER) {
73
- if (!isPatternEnabled(type)) {
45
+ return result;
46
+ }
47
+
48
+ function applyLoadedPatterns(
49
+ sessionId: string,
50
+ text: string,
51
+ patterns: LoadedPattern[],
52
+ ): string {
53
+ let result = text;
54
+
55
+ for (const { name, pattern, isContextual } of patterns) {
56
+ if (!isPatternEnabled(name)) {
74
57
  continue;
75
58
  }
59
+
76
60
  if (isContextual) {
77
61
  result = result.replace(
78
62
  new RegExp(pattern.source, "g"),
79
63
  (match, capturedValue) => {
80
- const token = addEntry(sessionId, type, capturedValue);
64
+ const token = addEntry(sessionId, name, capturedValue);
81
65
  return match.replace(capturedValue, token);
82
66
  },
83
67
  );
@@ -91,7 +75,7 @@ export function mask(sessionId: string, text: string): string {
91
75
  }
92
76
 
93
77
  for (const value of matches) {
94
- const token = addEntry(sessionId, type, value);
78
+ const token = addEntry(sessionId, name, value);
95
79
  result = result.split(value).join(token);
96
80
  }
97
81
  }
@@ -100,6 +84,26 @@ export function mask(sessionId: string, text: string): string {
100
84
  return result;
101
85
  }
102
86
 
87
+ export function mask(sessionId: string, text: string): string {
88
+ if (!text || typeof text !== "string") {
89
+ return text;
90
+ }
91
+
92
+ const config = getConfig();
93
+ if (!config.enabled) {
94
+ return text;
95
+ }
96
+
97
+ let result = text;
98
+
99
+ result = applyCustomPatterns(sessionId, result, config.customPatterns);
100
+
101
+ const patterns = loadPatterns();
102
+ result = applyLoadedPatterns(sessionId, result, patterns);
103
+
104
+ return result;
105
+ }
106
+
103
107
  export function unmask(sessionId: string, text: string): string {
104
108
  if (!text || typeof text !== "string") {
105
109
  return text;