@uniglot/wont-let-you-see 0.1.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.
@@ -0,0 +1,279 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
+ import { existsSync, rmSync, mkdirSync, readFileSync } from "fs";
3
+ import { join } from "path";
4
+ import {
5
+ createMapping,
6
+ addEntry,
7
+ getOriginal,
8
+ getToken,
9
+ loadMapping,
10
+ saveMapping,
11
+ getSessionPath,
12
+ } from "../mapping";
13
+
14
+ describe("Session Mapping Table", () => {
15
+ const testSessionId = "test-session-mapping";
16
+ const testSessionId2 = "test-session-mapping-2";
17
+
18
+ beforeEach(() => {
19
+ // Clean up test session directories before each test
20
+ const sessionPath = getSessionPath(testSessionId);
21
+ const sessionPath2 = getSessionPath(testSessionId2);
22
+ const sessionDir = sessionPath.substring(0, sessionPath.lastIndexOf("/"));
23
+ const sessionDir2 = sessionPath2.substring(
24
+ 0,
25
+ sessionPath2.lastIndexOf("/"),
26
+ );
27
+
28
+ if (existsSync(sessionDir)) {
29
+ rmSync(sessionDir, { recursive: true, force: true });
30
+ }
31
+ if (existsSync(sessionDir2)) {
32
+ rmSync(sessionDir2, { recursive: true, force: true });
33
+ }
34
+ });
35
+
36
+ afterEach(() => {
37
+ // Clean up test session directories after each test
38
+ const sessionPath = getSessionPath(testSessionId);
39
+ const sessionPath2 = getSessionPath(testSessionId2);
40
+ const sessionDir = sessionPath.substring(0, sessionPath.lastIndexOf("/"));
41
+ const sessionDir2 = sessionPath2.substring(
42
+ 0,
43
+ sessionPath2.lastIndexOf("/"),
44
+ );
45
+
46
+ if (existsSync(sessionDir)) {
47
+ rmSync(sessionDir, { recursive: true, force: true });
48
+ }
49
+ if (existsSync(sessionDir2)) {
50
+ rmSync(sessionDir2, { recursive: true, force: true });
51
+ }
52
+ });
53
+
54
+ describe("Test 1: createMapping initializes empty mapping", () => {
55
+ it("should create an empty mapping for a new session", () => {
56
+ const mapping = createMapping(testSessionId);
57
+
58
+ expect(mapping).toBeDefined();
59
+ expect(mapping.version).toBe(1);
60
+ expect(mapping.entries).toEqual({});
61
+ });
62
+
63
+ it("should persist empty mapping to disk", () => {
64
+ createMapping(testSessionId);
65
+
66
+ const sessionPath = getSessionPath(testSessionId);
67
+ expect(existsSync(sessionPath)).toBe(true);
68
+
69
+ const fileContent = readFileSync(sessionPath, "utf-8");
70
+ const parsed = JSON.parse(fileContent);
71
+
72
+ expect(parsed.version).toBe(1);
73
+ expect(parsed.entries).toEqual({});
74
+ });
75
+ });
76
+
77
+ describe("Test 2: addEntry returns token #(type-N)", () => {
78
+ it("should return token in format #(type-N) for first entry", () => {
79
+ createMapping(testSessionId);
80
+ const token = addEntry(testSessionId, "file", "/path/to/secret.txt");
81
+
82
+ expect(token).toBe("#(file-1)");
83
+ });
84
+
85
+ it("should increment counter for same type", () => {
86
+ createMapping(testSessionId);
87
+ const token1 = addEntry(testSessionId, "file", "/path/to/secret1.txt");
88
+ const token2 = addEntry(testSessionId, "file", "/path/to/secret2.txt");
89
+
90
+ expect(token1).toBe("#(file-1)");
91
+ expect(token2).toBe("#(file-2)");
92
+ });
93
+
94
+ it("should maintain separate counters for different types", () => {
95
+ createMapping(testSessionId);
96
+ const fileToken = addEntry(testSessionId, "file", "/path/to/secret.txt");
97
+ const urlToken = addEntry(testSessionId, "url", "https://example.com");
98
+ const fileToken2 = addEntry(testSessionId, "file", "/another/file.txt");
99
+
100
+ expect(fileToken).toBe("#(file-1)");
101
+ expect(urlToken).toBe("#(url-1)");
102
+ expect(fileToken2).toBe("#(file-2)");
103
+ });
104
+ });
105
+
106
+ describe("Test 3: getOriginal returns original value or undefined", () => {
107
+ it("should return original value for valid token", () => {
108
+ createMapping(testSessionId);
109
+ const token = addEntry(testSessionId, "file", "/path/to/secret.txt");
110
+
111
+ const original = getOriginal(testSessionId, token);
112
+ expect(original).toBe("/path/to/secret.txt");
113
+ });
114
+
115
+ it("should return undefined for non-existent token", () => {
116
+ createMapping(testSessionId);
117
+
118
+ const original = getOriginal(testSessionId, "#(file-999)");
119
+ expect(original).toBeUndefined();
120
+ });
121
+
122
+ it("should return undefined for invalid token format", () => {
123
+ createMapping(testSessionId);
124
+
125
+ const original = getOriginal(testSessionId, "invalid-token");
126
+ expect(original).toBeUndefined();
127
+ });
128
+ });
129
+
130
+ describe("Test 4: getToken returns existing token or undefined", () => {
131
+ it("should return token for existing original value", () => {
132
+ createMapping(testSessionId);
133
+ const originalValue = "/path/to/secret.txt";
134
+ const addedToken = addEntry(testSessionId, "file", originalValue);
135
+
136
+ const foundToken = getToken(testSessionId, originalValue);
137
+ expect(foundToken).toBe(addedToken);
138
+ expect(foundToken).toBe("#(file-1)");
139
+ });
140
+
141
+ it("should return undefined for non-existent original value", () => {
142
+ createMapping(testSessionId);
143
+ addEntry(testSessionId, "file", "/path/to/secret.txt");
144
+
145
+ const token = getToken(testSessionId, "/non/existent/path.txt");
146
+ expect(token).toBeUndefined();
147
+ });
148
+ });
149
+
150
+ describe("Test 5: Persistence - mapping survives reload", () => {
151
+ it("should persist entries to disk and reload them", () => {
152
+ createMapping(testSessionId);
153
+ const token1 = addEntry(testSessionId, "file", "/path/to/secret.txt");
154
+ const token2 = addEntry(testSessionId, "url", "https://example.com");
155
+
156
+ // Load mapping from disk (simulating reload)
157
+ const reloadedMapping = loadMapping(testSessionId);
158
+
159
+ expect(reloadedMapping).toBeDefined();
160
+ expect(reloadedMapping.entries[token1]).toBe("/path/to/secret.txt");
161
+ expect(reloadedMapping.entries[token2]).toBe("https://example.com");
162
+ });
163
+
164
+ it("should maintain counter state after reload", () => {
165
+ createMapping(testSessionId);
166
+ addEntry(testSessionId, "file", "/path/to/secret1.txt");
167
+ addEntry(testSessionId, "file", "/path/to/secret2.txt");
168
+
169
+ // Add another entry after reload
170
+ const token3 = addEntry(testSessionId, "file", "/path/to/secret3.txt");
171
+
172
+ expect(token3).toBe("#(file-3)");
173
+ });
174
+
175
+ it("should verify JSON file structure on disk", () => {
176
+ createMapping(testSessionId);
177
+ addEntry(testSessionId, "file", "/path/to/secret.txt");
178
+
179
+ const sessionPath = getSessionPath(testSessionId);
180
+ const fileContent = readFileSync(sessionPath, "utf-8");
181
+ const parsed = JSON.parse(fileContent);
182
+
183
+ expect(parsed.version).toBe(1);
184
+ expect(parsed.entries).toBeDefined();
185
+ expect(typeof parsed.entries).toBe("object");
186
+ expect(parsed.entries["#(file-1)"]).toBe("/path/to/secret.txt");
187
+ });
188
+ });
189
+
190
+ describe("Test 6: Isolation - session A mappings don't affect session B", () => {
191
+ it("should maintain separate mappings for different sessions", () => {
192
+ createMapping(testSessionId);
193
+ createMapping(testSessionId2);
194
+
195
+ const tokenA = addEntry(testSessionId, "file", "/session-a/secret.txt");
196
+ const tokenB = addEntry(testSessionId2, "file", "/session-b/secret.txt");
197
+
198
+ expect(tokenA).toBe("#(file-1)");
199
+ expect(tokenB).toBe("#(file-1)");
200
+
201
+ const originalA = getOriginal(testSessionId, tokenA);
202
+ const originalB = getOriginal(testSessionId2, tokenB);
203
+
204
+ expect(originalA).toBe("/session-a/secret.txt");
205
+ expect(originalB).toBe("/session-b/secret.txt");
206
+ });
207
+
208
+ it("should not find tokens from other sessions", () => {
209
+ createMapping(testSessionId);
210
+ createMapping(testSessionId2);
211
+
212
+ const tokenA = addEntry(testSessionId, "file", "/session-a/secret.txt");
213
+
214
+ // Try to get token from session A using session B
215
+ const originalInB = getOriginal(testSessionId2, tokenA);
216
+ expect(originalInB).toBeUndefined();
217
+ });
218
+
219
+ it("should create separate files for different sessions", () => {
220
+ createMapping(testSessionId);
221
+ createMapping(testSessionId2);
222
+
223
+ addEntry(testSessionId, "file", "/session-a/secret.txt");
224
+ addEntry(testSessionId2, "file", "/session-b/secret.txt");
225
+
226
+ const pathA = getSessionPath(testSessionId);
227
+ const pathB = getSessionPath(testSessionId2);
228
+
229
+ expect(existsSync(pathA)).toBe(true);
230
+ expect(existsSync(pathB)).toBe(true);
231
+ expect(pathA).not.toBe(pathB);
232
+
233
+ const contentA = JSON.parse(readFileSync(pathA, "utf-8"));
234
+ const contentB = JSON.parse(readFileSync(pathB, "utf-8"));
235
+
236
+ expect(contentA.entries["#(file-1)"]).toBe("/session-a/secret.txt");
237
+ expect(contentB.entries["#(file-1)"]).toBe("/session-b/secret.txt");
238
+ });
239
+ });
240
+
241
+ describe("Test 7: Idempotency - adding same value twice returns same token", () => {
242
+ it("should return same token when adding identical value", () => {
243
+ createMapping(testSessionId);
244
+ const originalValue = "/path/to/secret.txt";
245
+
246
+ const token1 = addEntry(testSessionId, "file", originalValue);
247
+ const token2 = addEntry(testSessionId, "file", originalValue);
248
+
249
+ expect(token1).toBe(token2);
250
+ expect(token1).toBe("#(file-1)");
251
+ });
252
+
253
+ it("should not increment counter for duplicate values", () => {
254
+ createMapping(testSessionId);
255
+ const originalValue = "/path/to/secret.txt";
256
+
257
+ addEntry(testSessionId, "file", originalValue);
258
+ addEntry(testSessionId, "file", originalValue);
259
+ const token3 = addEntry(testSessionId, "file", "/different/path.txt");
260
+
261
+ expect(token3).toBe("#(file-2)");
262
+ });
263
+
264
+ it("should maintain single entry in mapping for duplicate values", () => {
265
+ createMapping(testSessionId);
266
+ const originalValue = "/path/to/secret.txt";
267
+
268
+ addEntry(testSessionId, "file", originalValue);
269
+ addEntry(testSessionId, "file", originalValue);
270
+
271
+ const mapping = loadMapping(testSessionId);
272
+ const entries = Object.entries(mapping.entries);
273
+
274
+ expect(entries.length).toBe(1);
275
+ expect(entries[0]?.[0]).toBe("#(file-1)");
276
+ expect(entries[0]?.[1]).toBe(originalValue);
277
+ });
278
+ });
279
+ });
@@ -0,0 +1,248 @@
1
+ import { describe, it, expect, beforeEach, vi, afterEach } from "vitest";
2
+ import { mask, unmask } from "../masker";
3
+ import { createMapping } from "../mapping";
4
+ import { resetConfig } from "../config";
5
+
6
+ describe("masker", () => {
7
+ const sessionId = "test-session-masker";
8
+ const originalEnv = { ...process.env };
9
+
10
+ beforeEach(() => {
11
+ process.env.WONT_LET_YOU_SEE_REVEALED_PATTERNS = "";
12
+ resetConfig();
13
+ createMapping(sessionId);
14
+ });
15
+
16
+ afterEach(() => {
17
+ process.env = { ...originalEnv };
18
+ resetConfig();
19
+ });
20
+
21
+ describe("mask()", () => {
22
+ it("should replace sensitive values with tokens", () => {
23
+ const input = "VPC: vpc-1234567890abcdef0";
24
+ const result = mask(sessionId, input);
25
+
26
+ expect(result).toMatch(/VPC: #\(vpc-\d+\)/);
27
+ expect(result).not.toContain("vpc-1234567890abcdef0");
28
+ });
29
+
30
+ it("should handle multiple different values with different tokens", () => {
31
+ const input =
32
+ "VPC1: vpc-1111111111111111, VPC2: vpc-2222222222222222, VPC3: vpc-3333333333333333, VPC4: vpc-4444444444444444, VPC5: vpc-5555555555555555";
33
+ const result = mask(sessionId, input);
34
+
35
+ // Extract all tokens
36
+ const tokens = result.match(/#\(vpc-\d+\)/g) || [];
37
+ expect(tokens).toHaveLength(5);
38
+
39
+ // Verify all tokens are unique
40
+ const uniqueTokens = new Set(tokens);
41
+ expect(uniqueTokens.size).toBe(5);
42
+ });
43
+
44
+ it("should be idempotent (masking twice returns same result)", () => {
45
+ const input = "VPC: vpc-1234567890abcdef0";
46
+ const masked1 = mask(sessionId, input);
47
+ const masked2 = mask(sessionId, masked1);
48
+
49
+ expect(masked1).toBe(masked2);
50
+ });
51
+
52
+ it("should apply patterns in priority order (ARN before Account ID)", () => {
53
+ const input =
54
+ '{"OwnerId": "123456789012", "ClusterArn": "arn:aws:eks:us-east-1:123456789012:cluster/my-cluster"}';
55
+ const result = mask(sessionId, input);
56
+
57
+ // ARN should be masked as eks-cluster (not as arn containing account-id)
58
+ expect(result).toMatch(/#\(eks-cluster-\d+\)/);
59
+ // Account ID in JSON field should be masked separately
60
+ expect(result).toMatch(/"OwnerId":\s*"#\(account-id-\d+\)"/);
61
+
62
+ // Verify no double-masking (no tokens inside tokens)
63
+ expect(result).not.toMatch(/#\([^)]*#\(/);
64
+ });
65
+
66
+ it("should preserve JSON validity after masking", () => {
67
+ const input =
68
+ '{"VpcId": "vpc-1234567890abcdef0", "SubnetId": "subnet-abcdef1234567890", "CidrBlock": "10.0.0.0/16"}';
69
+ const result = mask(sessionId, input);
70
+
71
+ // Should be valid JSON
72
+ expect(() => JSON.parse(result)).not.toThrow();
73
+
74
+ const parsed = JSON.parse(result);
75
+ expect(parsed.VpcId).toMatch(/#\(vpc-\d+\)/);
76
+ expect(parsed.SubnetId).toMatch(/#\(subnet-\d+\)/);
77
+ expect(parsed.CidrBlock).toMatch(/#\(ipv4-\d+\)\/16/);
78
+ });
79
+
80
+ it("should handle mixed content with multiple pattern types", () => {
81
+ const input =
82
+ "Instance i-1234567890abcdef0 in VPC vpc-abcdef1234567890 with IP 10.0.1.5 and CIDR 10.0.0.0/16";
83
+ const result = mask(sessionId, input);
84
+
85
+ expect(result).toMatch(/Instance #\(ec2-instance-\d+\)/);
86
+ expect(result).toMatch(/VPC #\(vpc-\d+\)/);
87
+ expect(result).toMatch(/IP #\(ipv4-\d+\)/);
88
+ expect(result).toMatch(/CIDR #\(ipv4-\d+\)\/16/);
89
+ });
90
+
91
+ it("should mask IP portion of CIDR while preserving subnet mask", () => {
92
+ const input = "CIDR: 10.0.0.0/16, IP: 10.0.1.5";
93
+ const result = mask(sessionId, input);
94
+
95
+ expect(result).toMatch(/CIDR: #\(ipv4-\d+\)\/16/);
96
+ expect(result).toMatch(/IP: #\(ipv4-\d+\)/);
97
+ });
98
+ });
99
+
100
+ describe("unmask()", () => {
101
+ it("should replace tokens with original values", () => {
102
+ const original = "VPC: vpc-1234567890abcdef0";
103
+ const masked = mask(sessionId, original);
104
+ const unmasked = unmask(sessionId, masked);
105
+
106
+ expect(unmasked).toBe(original);
107
+ });
108
+
109
+ it("should handle unknown tokens with console.warn", () => {
110
+ const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
111
+
112
+ const input = "Unknown token: #(vpc-999)";
113
+ const result = unmask(sessionId, input);
114
+
115
+ // Should return literal token when unknown
116
+ expect(result).toBe("Unknown token: #(vpc-999)");
117
+
118
+ // Should log warning
119
+ expect(warnSpy).toHaveBeenCalledWith("Unknown token: #(vpc-999)");
120
+
121
+ warnSpy.mockRestore();
122
+ });
123
+
124
+ it("should not warn for valid tokens", () => {
125
+ const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
126
+
127
+ const original = "VPC: vpc-1234567890abcdef0";
128
+ const masked = mask(sessionId, original);
129
+ unmask(sessionId, masked);
130
+
131
+ expect(warnSpy).not.toHaveBeenCalled();
132
+
133
+ warnSpy.mockRestore();
134
+ });
135
+ });
136
+
137
+ describe("config integration", () => {
138
+ it("should not mask when plugin is disabled", () => {
139
+ process.env.WONT_LET_YOU_SEE_ENABLED = "false";
140
+ resetConfig();
141
+
142
+ const input = "VPC: vpc-1234567890abcdef0";
143
+ const result = mask(sessionId, input);
144
+
145
+ expect(result).toBe(input);
146
+ });
147
+
148
+ it("should skip revealed patterns", () => {
149
+ process.env.WONT_LET_YOU_SEE_REVEALED_PATTERNS = "vpc,subnet";
150
+ resetConfig();
151
+
152
+ const input = "VPC: vpc-1234567890abcdef0, SG: sg-abcdef1234567890";
153
+ const result = mask(sessionId, input);
154
+
155
+ expect(result).toContain("vpc-1234567890abcdef0");
156
+ expect(result).toMatch(/SG: #\(security-group-\d+\)/);
157
+ });
158
+
159
+ it("should skip eks-cluster but fall back to generic ARN pattern", () => {
160
+ process.env.WONT_LET_YOU_SEE_REVEALED_PATTERNS = "eks-cluster";
161
+ resetConfig();
162
+
163
+ const input =
164
+ "EKS: arn:aws:eks:us-east-1:123456789012:cluster/my-cluster";
165
+ const result = mask(sessionId, input);
166
+
167
+ expect(result).toMatch(/EKS: #\(arn-\d+\)/);
168
+ expect(result).not.toMatch(/#\(eks-cluster-/);
169
+ });
170
+
171
+ it("should not mask ARN when both eks-cluster and arn are revealed", () => {
172
+ process.env.WONT_LET_YOU_SEE_REVEALED_PATTERNS = "eks-cluster,arn";
173
+ resetConfig();
174
+
175
+ const input =
176
+ "EKS: arn:aws:eks:us-east-1:123456789012:cluster/my-cluster";
177
+ const result = mask(sessionId, input);
178
+
179
+ expect(result).toContain(
180
+ "arn:aws:eks:us-east-1:123456789012:cluster/my-cluster",
181
+ );
182
+ });
183
+ });
184
+
185
+ describe("round-trip integrity", () => {
186
+ it("should maintain round-trip integrity for VPC", () => {
187
+ const original = "vpc-1234567890abcdef0";
188
+ const masked = mask(sessionId, original);
189
+ const unmasked = unmask(sessionId, masked);
190
+
191
+ expect(unmasked).toBe(original);
192
+ });
193
+
194
+ it("should maintain round-trip integrity for ARN", () => {
195
+ const original = "arn:aws:eks:us-east-1:123456789012:cluster/my-cluster";
196
+ const masked = mask(sessionId, original);
197
+ const unmasked = unmask(sessionId, masked);
198
+
199
+ expect(unmasked).toBe(original);
200
+ });
201
+
202
+ it("should maintain round-trip integrity for Account ID in JSON", () => {
203
+ const original = '{"OwnerId": "123456789012"}';
204
+ const masked = mask(sessionId, original);
205
+ const unmasked = unmask(sessionId, masked);
206
+
207
+ expect(unmasked).toBe(original);
208
+ });
209
+
210
+ it("should maintain round-trip integrity for CIDR (IP portion masked)", () => {
211
+ const original = "10.0.0.0/16";
212
+ const masked = mask(sessionId, original);
213
+ const unmasked = unmask(sessionId, masked);
214
+
215
+ expect(unmasked).toBe(original);
216
+ });
217
+
218
+ it("should maintain round-trip integrity for IPv4", () => {
219
+ const original = "10.0.1.5";
220
+ const masked = mask(sessionId, original);
221
+ const unmasked = unmask(sessionId, masked);
222
+
223
+ expect(unmasked).toBe(original);
224
+ });
225
+
226
+ it("should maintain round-trip integrity for complex JSON", () => {
227
+ const original =
228
+ '{"VpcId": "vpc-1234567890abcdef0", "SubnetId": "subnet-abcdef1234567890", "CidrBlock": "10.0.0.0/16", "OwnerId": "123456789012"}';
229
+ const masked = mask(sessionId, original);
230
+ const unmasked = unmask(sessionId, masked);
231
+
232
+ expect(unmasked).toBe(original);
233
+
234
+ // Verify JSON validity is preserved
235
+ expect(() => JSON.parse(unmasked)).not.toThrow();
236
+ expect(JSON.parse(unmasked)).toEqual(JSON.parse(original));
237
+ });
238
+
239
+ it("should maintain round-trip integrity for mixed content", () => {
240
+ const original =
241
+ "Instance i-1234567890abcdef0 in VPC vpc-abcdef1234567890 with IP 10.0.1.5 and CIDR 10.0.0.0/16";
242
+ const masked = mask(sessionId, original);
243
+ const unmasked = unmask(sessionId, masked);
244
+
245
+ expect(unmasked).toBe(original);
246
+ });
247
+ });
248
+ });