@uniglot/wont-let-you-see 0.1.1 → 0.3.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/.github/workflows/ci.yml +14 -0
- package/.github/workflows/publish.yml +20 -0
- package/.github/workflows/release.yml +20 -0
- package/.github/workflows/test.yml +13 -0
- package/README.md +67 -6
- package/dist/index.js +125 -90
- package/package.json +1 -1
- package/patterns/aws.json +27 -0
- package/patterns/common.json +8 -0
- package/patterns/kubernetes.json +9 -0
- package/src/__tests__/config.test.ts +24 -20
- package/src/__tests__/masker.test.ts +64 -0
- package/src/__tests__/patterns.test.ts +212 -63
- package/src/config.ts +27 -8
- package/src/masker.ts +62 -52
- package/src/patterns.ts +108 -45
- package/dist/patterns.js +0 -5233
|
@@ -2,6 +2,7 @@ import { describe, it, expect, beforeEach, vi, afterEach } from "vitest";
|
|
|
2
2
|
import { mask, unmask } from "../masker";
|
|
3
3
|
import { createMapping } from "../mapping";
|
|
4
4
|
import { resetConfig } from "../config";
|
|
5
|
+
import { resetPatternCache } from "../patterns";
|
|
5
6
|
|
|
6
7
|
describe("masker", () => {
|
|
7
8
|
const sessionId = "test-session-masker";
|
|
@@ -10,12 +11,14 @@ describe("masker", () => {
|
|
|
10
11
|
beforeEach(() => {
|
|
11
12
|
process.env.WONT_LET_YOU_SEE_REVEALED_PATTERNS = "";
|
|
12
13
|
resetConfig();
|
|
14
|
+
resetPatternCache();
|
|
13
15
|
createMapping(sessionId);
|
|
14
16
|
});
|
|
15
17
|
|
|
16
18
|
afterEach(() => {
|
|
17
19
|
process.env = { ...originalEnv };
|
|
18
20
|
resetConfig();
|
|
21
|
+
resetPatternCache();
|
|
19
22
|
});
|
|
20
23
|
|
|
21
24
|
describe("mask()", () => {
|
|
@@ -182,6 +185,67 @@ describe("masker", () => {
|
|
|
182
185
|
});
|
|
183
186
|
});
|
|
184
187
|
|
|
188
|
+
describe("customPatterns with regex support", () => {
|
|
189
|
+
it("should mask exact string patterns", () => {
|
|
190
|
+
process.env.WONT_LET_YOU_SEE_CUSTOM_PATTERNS = "my-secret-value";
|
|
191
|
+
resetConfig();
|
|
192
|
+
|
|
193
|
+
const input = "Secret: my-secret-value";
|
|
194
|
+
const result = mask(sessionId, input);
|
|
195
|
+
|
|
196
|
+
expect(result).toMatch(/Secret: #\(custom-\d+\)/);
|
|
197
|
+
expect(result).not.toContain("my-secret-value");
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it("should mask regex patterns with regex: prefix", () => {
|
|
201
|
+
process.env.WONT_LET_YOU_SEE_CUSTOM_PATTERNS =
|
|
202
|
+
"regex:secret-[a-z]{3}-\\d{4}";
|
|
203
|
+
resetConfig();
|
|
204
|
+
|
|
205
|
+
const input = "Keys: secret-abc-1234, secret-xyz-5678";
|
|
206
|
+
const result = mask(sessionId, input);
|
|
207
|
+
|
|
208
|
+
expect(result).toMatch(/#\(custom-\d+\)/);
|
|
209
|
+
expect(result).not.toContain("secret-abc-1234");
|
|
210
|
+
expect(result).not.toContain("secret-xyz-5678");
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it("should treat patterns without regex: prefix as literal strings", () => {
|
|
214
|
+
process.env.WONT_LET_YOU_SEE_CUSTOM_PATTERNS = "literal-value";
|
|
215
|
+
resetConfig();
|
|
216
|
+
|
|
217
|
+
const input = "Value: literal-value, Other: literal-values";
|
|
218
|
+
const result = mask(sessionId, input);
|
|
219
|
+
|
|
220
|
+
expect(result).toMatch(/Value: #\(custom-\d+\)/);
|
|
221
|
+
expect(result).toContain("literal-values");
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("should round-trip custom regex patterns", () => {
|
|
225
|
+
process.env.WONT_LET_YOU_SEE_CUSTOM_PATTERNS = "regex:token-[A-Z]{8}";
|
|
226
|
+
resetConfig();
|
|
227
|
+
|
|
228
|
+
const original = "Auth: token-ABCDEFGH";
|
|
229
|
+
const masked = mask(sessionId, original);
|
|
230
|
+
const unmasked = unmask(sessionId, masked);
|
|
231
|
+
|
|
232
|
+
expect(unmasked).toBe(original);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it("should support multiple custom patterns including regex", () => {
|
|
236
|
+
process.env.WONT_LET_YOU_SEE_CUSTOM_PATTERNS =
|
|
237
|
+
"exact-secret,regex:pattern-\\d+";
|
|
238
|
+
resetConfig();
|
|
239
|
+
|
|
240
|
+
const input = "Secrets: exact-secret, pattern-123, pattern-456";
|
|
241
|
+
const result = mask(sessionId, input);
|
|
242
|
+
|
|
243
|
+
expect(result).not.toContain("exact-secret");
|
|
244
|
+
expect(result).not.toContain("pattern-123");
|
|
245
|
+
expect(result).not.toContain("pattern-456");
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
|
|
185
249
|
describe("round-trip integrity", () => {
|
|
186
250
|
it("should maintain round-trip integrity for VPC", () => {
|
|
187
251
|
const original = "vpc-1234567890abcdef0";
|
|
@@ -1,26 +1,31 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
+
});
|
|
14
|
+
|
|
10
15
|
describe("ARN", () => {
|
|
11
16
|
it("should match aws partition", () => {
|
|
12
17
|
expect(
|
|
13
|
-
|
|
18
|
+
getPattern("arn").test("arn:aws:iam::123456789012:user/admin"),
|
|
14
19
|
).toBe(true);
|
|
15
20
|
});
|
|
16
21
|
|
|
17
22
|
it("should match aws-cn partition", () => {
|
|
18
|
-
expect(
|
|
23
|
+
expect(getPattern("arn").test("arn:aws-cn:s3:::my-bucket")).toBe(true);
|
|
19
24
|
});
|
|
20
25
|
|
|
21
26
|
it("should match aws-us-gov partition", () => {
|
|
22
27
|
expect(
|
|
23
|
-
|
|
28
|
+
getPattern("arn").test(
|
|
24
29
|
"arn:aws-us-gov:ec2:us-gov-west-1:123456789012:instance/i-123",
|
|
25
30
|
),
|
|
26
31
|
).toBe(true);
|
|
@@ -29,94 +34,194 @@ describe("AWS_PATTERNS", () => {
|
|
|
29
34
|
|
|
30
35
|
describe("VPC resources", () => {
|
|
31
36
|
it("should match vpc", () => {
|
|
32
|
-
expect(
|
|
37
|
+
expect(getPattern("vpc").test("vpc-0123456789abcdef0")).toBe(true);
|
|
33
38
|
});
|
|
34
39
|
|
|
35
40
|
it("should match subnet", () => {
|
|
36
|
-
expect(
|
|
41
|
+
expect(getPattern("subnet").test("subnet-0123456789abcdef0")).toBe(true);
|
|
37
42
|
});
|
|
38
43
|
|
|
39
44
|
it("should match security group", () => {
|
|
40
|
-
expect(
|
|
45
|
+
expect(getPattern("security-group").test("sg-0123456789abcdef0")).toBe(
|
|
41
46
|
true,
|
|
42
47
|
);
|
|
43
48
|
});
|
|
44
49
|
|
|
45
50
|
it("should match nat gateway", () => {
|
|
46
|
-
expect(
|
|
51
|
+
expect(getPattern("nat-gateway").test("nat-0123456789abcdef0")).toBe(
|
|
52
|
+
true,
|
|
53
|
+
);
|
|
47
54
|
});
|
|
48
55
|
|
|
49
56
|
it("should match network acl", () => {
|
|
50
|
-
expect(
|
|
57
|
+
expect(getPattern("network-acl").test("acl-0123456789abcdef0")).toBe(
|
|
58
|
+
true,
|
|
59
|
+
);
|
|
51
60
|
});
|
|
52
61
|
|
|
53
62
|
it("should match eni", () => {
|
|
54
|
-
expect(
|
|
63
|
+
expect(getPattern("eni").test("eni-0123456789abcdef0")).toBe(true);
|
|
55
64
|
});
|
|
56
65
|
});
|
|
57
66
|
|
|
58
67
|
describe("EC2 resources", () => {
|
|
59
68
|
it("should match ebs volume", () => {
|
|
60
|
-
expect(
|
|
69
|
+
expect(getPattern("ebs").test("vol-0123456789abcdef0")).toBe(true);
|
|
61
70
|
});
|
|
62
71
|
|
|
63
72
|
it("should match snapshot", () => {
|
|
64
|
-
expect(
|
|
73
|
+
expect(getPattern("snapshot").test("snap-0123456789abcdef0")).toBe(true);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe("VPC networking resources", () => {
|
|
78
|
+
it("should match vpc endpoint", () => {
|
|
79
|
+
expect(getPattern("vpc-endpoint").test("vpce-0123456789abcdef0")).toBe(
|
|
80
|
+
true,
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("should match transit gateway", () => {
|
|
85
|
+
expect(getPattern("transit-gateway").test("tgw-0123456789abcdef0")).toBe(
|
|
86
|
+
true,
|
|
87
|
+
);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("should match customer gateway", () => {
|
|
91
|
+
expect(getPattern("customer-gateway").test("cgw-0123456789abcdef0")).toBe(
|
|
92
|
+
true,
|
|
93
|
+
);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("should match vpn gateway", () => {
|
|
97
|
+
expect(getPattern("vpn-gateway").test("vgw-0123456789abcdef0")).toBe(
|
|
98
|
+
true,
|
|
99
|
+
);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("should match vpn connection", () => {
|
|
103
|
+
expect(getPattern("vpn-connection").test("vpn-0123456789abcdef0")).toBe(
|
|
104
|
+
true,
|
|
105
|
+
);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("should NOT match invalid vpc endpoint", () => {
|
|
109
|
+
expect(getPattern("vpc-endpoint").test("vpce-invalid")).toBe(false);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe("ECR resources", () => {
|
|
114
|
+
it("should match ECR repo URI", () => {
|
|
115
|
+
expect(
|
|
116
|
+
getPattern("ecr-repo").test(
|
|
117
|
+
"123456789012.dkr.ecr.us-west-2.amazonaws.com/my-repo",
|
|
118
|
+
),
|
|
119
|
+
).toBe(true);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("should match ECR repo URI with nested path", () => {
|
|
123
|
+
expect(
|
|
124
|
+
getPattern("ecr-repo").test(
|
|
125
|
+
"123456789012.dkr.ecr.eu-central-1.amazonaws.com/org/app/service",
|
|
126
|
+
),
|
|
127
|
+
).toBe(true);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("should match ECR repo URI with dots and underscores", () => {
|
|
131
|
+
expect(
|
|
132
|
+
getPattern("ecr-repo").test(
|
|
133
|
+
"123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/my_repo.name",
|
|
134
|
+
),
|
|
135
|
+
).toBe(true);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("should NOT match invalid ECR URI (wrong account ID length)", () => {
|
|
139
|
+
expect(
|
|
140
|
+
getPattern("ecr-repo").test(
|
|
141
|
+
"12345.dkr.ecr.us-west-2.amazonaws.com/my-repo",
|
|
142
|
+
),
|
|
143
|
+
).toBe(false);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("should NOT match non-ECR docker registry", () => {
|
|
147
|
+
expect(getPattern("ecr-repo").test("docker.io/library/nginx")).toBe(
|
|
148
|
+
false,
|
|
149
|
+
);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("should match ECR repo URI with masked account ID", () => {
|
|
153
|
+
expect(
|
|
154
|
+
getPattern("ecr-repo").test(
|
|
155
|
+
"#(custom-1).dkr.ecr.us-west-2.amazonaws.com/my-repo",
|
|
156
|
+
),
|
|
157
|
+
).toBe(true);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("should match ECR repo URI with masked account ID (higher number)", () => {
|
|
161
|
+
expect(
|
|
162
|
+
getPattern("ecr-repo").test(
|
|
163
|
+
"#(custom-123).dkr.ecr.us-east-1.amazonaws.com/app/service",
|
|
164
|
+
),
|
|
165
|
+
).toBe(true);
|
|
65
166
|
});
|
|
66
167
|
});
|
|
67
168
|
|
|
68
169
|
describe("Account ID (contextual)", () => {
|
|
69
170
|
it("should match OwnerId field", () => {
|
|
70
|
-
expect(
|
|
171
|
+
expect(getPattern("account-id").test('"OwnerId": "123456789012"')).toBe(
|
|
71
172
|
true,
|
|
72
173
|
);
|
|
73
174
|
});
|
|
74
175
|
|
|
75
176
|
it("should match account_id field (terraform style)", () => {
|
|
76
|
-
expect(
|
|
77
|
-
|
|
78
|
-
);
|
|
177
|
+
expect(
|
|
178
|
+
getPattern("account-id").test('"account_id": "123456789012"'),
|
|
179
|
+
).toBe(true);
|
|
79
180
|
});
|
|
80
181
|
|
|
81
182
|
it("should NOT match bare number", () => {
|
|
82
|
-
expect(
|
|
183
|
+
expect(getPattern("account-id").test("123456789012")).toBe(false);
|
|
83
184
|
});
|
|
84
185
|
});
|
|
85
186
|
|
|
86
187
|
describe("Access Key ID", () => {
|
|
87
188
|
it("should match AKIA prefix", () => {
|
|
88
|
-
expect(
|
|
189
|
+
expect(getPattern("access-key-id").test(" AKIAIOSFODNN7EXAMPLE ")).toBe(
|
|
89
190
|
true,
|
|
90
191
|
);
|
|
91
192
|
});
|
|
92
193
|
|
|
93
194
|
it("should match ASIA prefix (temporary)", () => {
|
|
94
|
-
expect(
|
|
195
|
+
expect(getPattern("access-key-id").test(" ASIAISAMPLEKEYID1234 ")).toBe(
|
|
95
196
|
true,
|
|
96
197
|
);
|
|
97
198
|
});
|
|
98
199
|
|
|
99
200
|
it("should NOT match random string", () => {
|
|
100
|
-
expect(
|
|
201
|
+
expect(getPattern("access-key-id").test("RANDOMSTRING12345678")).toBe(
|
|
202
|
+
false,
|
|
203
|
+
);
|
|
101
204
|
});
|
|
102
205
|
});
|
|
103
206
|
});
|
|
104
207
|
|
|
105
|
-
describe("
|
|
208
|
+
describe("Kubernetes Patterns", () => {
|
|
209
|
+
beforeEach(() => {
|
|
210
|
+
resetPatternCache();
|
|
211
|
+
});
|
|
212
|
+
|
|
106
213
|
describe("Service Account Token", () => {
|
|
107
214
|
it("should match JWT format", () => {
|
|
108
215
|
const jwt =
|
|
109
216
|
"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImRlZmF1bHQtdG9rZW4tYWJjZGUiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiZGVmYXVsdCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjEyMzQ1Njc4LTEyMzQtMTIzNC0xMjM0LTEyMzQ1Njc4OTAxMiIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZWZhdWx0OmRlZmF1bHQifQ.signature";
|
|
110
|
-
expect(
|
|
217
|
+
expect(getPattern("k8s-token").test(jwt)).toBe(true);
|
|
111
218
|
});
|
|
112
219
|
});
|
|
113
220
|
|
|
114
221
|
describe("Node Names", () => {
|
|
115
222
|
it("should match AWS node name", () => {
|
|
116
223
|
expect(
|
|
117
|
-
|
|
118
|
-
"ip-10-0-1-123.us-west-2.compute.internal",
|
|
119
|
-
),
|
|
224
|
+
getPattern("k8s-node").test("ip-10-0-1-123.us-west-2.compute.internal"),
|
|
120
225
|
).toBe(true);
|
|
121
226
|
});
|
|
122
227
|
});
|
|
@@ -124,7 +229,7 @@ describe("K8S_PATTERNS", () => {
|
|
|
124
229
|
describe("Cluster Endpoints", () => {
|
|
125
230
|
it("should match EKS endpoint", () => {
|
|
126
231
|
expect(
|
|
127
|
-
|
|
232
|
+
getPattern("k8s-endpoint").test(
|
|
128
233
|
"https://ABCDEF1234567890ABCD.us-west-2.eks.amazonaws.com",
|
|
129
234
|
),
|
|
130
235
|
).toBe(true);
|
|
@@ -132,14 +237,18 @@ describe("K8S_PATTERNS", () => {
|
|
|
132
237
|
});
|
|
133
238
|
});
|
|
134
239
|
|
|
135
|
-
describe("
|
|
240
|
+
describe("Common Patterns", () => {
|
|
241
|
+
beforeEach(() => {
|
|
242
|
+
resetPatternCache();
|
|
243
|
+
});
|
|
244
|
+
|
|
136
245
|
describe("IPv4", () => {
|
|
137
246
|
it("should match IPv4", () => {
|
|
138
|
-
expect(
|
|
247
|
+
expect(getPattern("ipv4").test("192.168.1.1")).toBe(true);
|
|
139
248
|
});
|
|
140
249
|
|
|
141
250
|
it("should not match CIDR notation", () => {
|
|
142
|
-
expect(
|
|
251
|
+
expect(getPattern("ipv4").test("10.0.0.0/8")).toBe(false);
|
|
143
252
|
});
|
|
144
253
|
});
|
|
145
254
|
|
|
@@ -153,16 +262,16 @@ MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC
|
|
|
153
262
|
-----END PRIVATE KEY-----`;
|
|
154
263
|
|
|
155
264
|
it("should match entire RSA private key block", () => {
|
|
156
|
-
expect(
|
|
265
|
+
expect(getPattern("private-key").test(rsaKey)).toBe(true);
|
|
157
266
|
});
|
|
158
267
|
|
|
159
268
|
it("should match entire generic private key block", () => {
|
|
160
|
-
expect(
|
|
269
|
+
expect(getPattern("private-key").test(genericKey)).toBe(true);
|
|
161
270
|
});
|
|
162
271
|
|
|
163
272
|
it("should not match header only", () => {
|
|
164
273
|
expect(
|
|
165
|
-
|
|
274
|
+
getPattern("private-key").test("-----BEGIN RSA PRIVATE KEY-----"),
|
|
166
275
|
).toBe(false);
|
|
167
276
|
});
|
|
168
277
|
});
|
|
@@ -170,49 +279,89 @@ MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC
|
|
|
170
279
|
describe("API Key Field (contextual)", () => {
|
|
171
280
|
it("should match api_key field", () => {
|
|
172
281
|
expect(
|
|
173
|
-
|
|
282
|
+
getPattern("api-key").test('"api_key": "sk-1234567890abcdef"'),
|
|
174
283
|
).toBe(true);
|
|
175
284
|
});
|
|
176
285
|
|
|
177
286
|
it("should match password field", () => {
|
|
178
|
-
expect(
|
|
179
|
-
|
|
180
|
-
)
|
|
287
|
+
expect(getPattern("api-key").test('"password": "supersecret123"')).toBe(
|
|
288
|
+
true,
|
|
289
|
+
);
|
|
181
290
|
});
|
|
182
291
|
|
|
183
292
|
it("should match token field", () => {
|
|
184
|
-
expect(
|
|
185
|
-
|
|
186
|
-
)
|
|
293
|
+
expect(getPattern("api-key").test('"token": "ghp_xxxxxxxxxxxx"')).toBe(
|
|
294
|
+
true,
|
|
295
|
+
);
|
|
187
296
|
});
|
|
188
297
|
});
|
|
189
298
|
});
|
|
190
299
|
|
|
191
|
-
describe("
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
expect(PATTERNS.arn).toBe(AWS_PATTERNS.arn);
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
it("should include all K8S patterns", () => {
|
|
198
|
-
expect(PATTERNS.serviceAccountToken).toBe(K8S_PATTERNS.serviceAccountToken);
|
|
300
|
+
describe("ReDoS safety", () => {
|
|
301
|
+
beforeEach(() => {
|
|
302
|
+
resetPatternCache();
|
|
199
303
|
});
|
|
200
304
|
|
|
201
|
-
it("should include all common patterns", () => {
|
|
202
|
-
expect(PATTERNS.ipv4).toBe(COMMON_PATTERNS.ipv4);
|
|
203
|
-
expect(PATTERNS.privateKey).toBe(COMMON_PATTERNS.privateKey);
|
|
204
|
-
});
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
describe("ReDoS safety", () => {
|
|
208
305
|
it("should handle pathological input quickly", () => {
|
|
209
306
|
const pathological = "a".repeat(1000) + "b";
|
|
210
307
|
const start = performance.now();
|
|
211
308
|
|
|
212
|
-
|
|
309
|
+
const allPatterns = loadPatterns();
|
|
310
|
+
for (const { pattern } of allPatterns) {
|
|
213
311
|
pattern.test(pathological);
|
|
214
312
|
}
|
|
215
313
|
|
|
216
314
|
expect(performance.now() - start).toBeLessThan(100);
|
|
217
315
|
});
|
|
218
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/config.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
existsSync as nodeExistsSync,
|
|
3
|
+
readFileSync as nodeReadFileSync,
|
|
4
|
+
} from "fs";
|
|
5
|
+
import { join, dirname } from "path";
|
|
3
6
|
import { homedir } from "os";
|
|
4
7
|
|
|
5
8
|
export interface Config {
|
|
@@ -8,6 +11,24 @@ export interface Config {
|
|
|
8
11
|
customPatterns: string[];
|
|
9
12
|
}
|
|
10
13
|
|
|
14
|
+
interface FsAdapter {
|
|
15
|
+
existsSync: (path: string) => boolean;
|
|
16
|
+
readFileSync: (path: string, encoding: "utf-8") => string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let fsAdapter: FsAdapter = {
|
|
20
|
+
existsSync: nodeExistsSync,
|
|
21
|
+
readFileSync: nodeReadFileSync,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export function setFsAdapter(adapter: FsAdapter): void {
|
|
25
|
+
fsAdapter = adapter;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function resetFsAdapter(): void {
|
|
29
|
+
fsAdapter = { existsSync: nodeExistsSync, readFileSync: nodeReadFileSync };
|
|
30
|
+
}
|
|
31
|
+
|
|
11
32
|
const DEFAULT_CONFIG: Config = {
|
|
12
33
|
enabled: true,
|
|
13
34
|
revealedPatterns: [],
|
|
@@ -22,7 +43,7 @@ function findConfigInAncestors(startDir: string): string | null {
|
|
|
22
43
|
|
|
23
44
|
while (true) {
|
|
24
45
|
const configPath = join(currentDir, CONFIG_FILENAME);
|
|
25
|
-
if (existsSync(configPath)) {
|
|
46
|
+
if (fsAdapter.existsSync(configPath)) {
|
|
26
47
|
return configPath;
|
|
27
48
|
}
|
|
28
49
|
|
|
@@ -41,20 +62,18 @@ function findConfigInAncestors(startDir: string): string | null {
|
|
|
41
62
|
}
|
|
42
63
|
|
|
43
64
|
function loadJsonConfig(): Partial<Config> {
|
|
44
|
-
// First, search up from cwd to find nearest config
|
|
45
65
|
const ancestorConfig = findConfigInAncestors(process.cwd());
|
|
46
66
|
if (ancestorConfig) {
|
|
47
67
|
try {
|
|
48
|
-
const content = readFileSync(ancestorConfig, "utf-8");
|
|
68
|
+
const content = fsAdapter.readFileSync(ancestorConfig, "utf-8");
|
|
49
69
|
return JSON.parse(content);
|
|
50
70
|
} catch {}
|
|
51
71
|
}
|
|
52
72
|
|
|
53
|
-
// Fall back to home directory
|
|
54
73
|
const homeConfig = join(homedir(), CONFIG_FILENAME);
|
|
55
|
-
if (existsSync(homeConfig)) {
|
|
74
|
+
if (fsAdapter.existsSync(homeConfig)) {
|
|
56
75
|
try {
|
|
57
|
-
const content = readFileSync(homeConfig, "utf-8");
|
|
76
|
+
const content = fsAdapter.readFileSync(homeConfig, "utf-8");
|
|
58
77
|
return JSON.parse(content);
|
|
59
78
|
} catch {}
|
|
60
79
|
}
|