@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.
package/dist/index.js ADDED
@@ -0,0 +1,308 @@
1
+ // src/patterns.ts
2
+ var AWS_PATTERNS = {
3
+ arn: /^arn:(?:aws|aws-cn|aws-us-gov):[a-zA-Z0-9-]+:[a-z0-9-]*:(?:[0-9]{12})?:.+$/,
4
+ eksCluster: /^arn:(?:aws|aws-cn|aws-us-gov):eks:[a-z0-9-]+:[0-9]{12}:cluster\/.+$/,
5
+ vpc: /^vpc-[0-9a-f]{8,17}$/,
6
+ subnet: /^subnet-[0-9a-f]{8,17}$/,
7
+ securityGroup: /^sg-[0-9a-f]{8,17}$/,
8
+ internetGateway: /^igw-[0-9a-f]{8,17}$/,
9
+ routeTable: /^rtb-[0-9a-f]{8,17}$/,
10
+ natGateway: /^nat-[0-9a-f]{8,17}$/,
11
+ networkAcl: /^acl-[0-9a-f]{8,17}$/,
12
+ ec2Instance: /^i-[0-9a-f]{8,17}$/,
13
+ ami: /^ami-[0-9a-f]{8,17}$/,
14
+ ebs: /^vol-[0-9a-f]{8,17}$/,
15
+ snapshot: /^snap-[0-9a-f]{8,17}$/,
16
+ eni: /^eni-[0-9a-f]{8,17}$/,
17
+ accountId: /"(?:OwnerId|AccountId|Owner|account_id)":\s*"(\d{12})"/,
18
+ accessKeyId: /(?:^|[^A-Z0-9])(?:AKIA|ABIA|ACCA|ASIA)[A-Z0-9]{16}(?:[^A-Z0-9]|$)/
19
+ };
20
+ var K8S_PATTERNS = {
21
+ serviceAccountToken: /^eyJ[A-Za-z0-9_-]*\.eyJ[A-Za-z0-9_-]*\.[A-Za-z0-9_-]*$/,
22
+ nodeNameAws: /^ip-(?:\d{1,3}-){3}\d{1,3}\.[a-z0-9-]+\.compute\.internal$/,
23
+ clusterEndpoint: /^https:\/\/[A-Z0-9]+\.[a-z0-9-]+\.eks\.amazonaws\.com$/,
24
+ kubeconfigServer: /^https:\/\/[0-9A-F]{32}\.[a-z]{2}-[a-z]+-\d\.eks\.amazonaws\.com$/i
25
+ };
26
+ var COMMON_PATTERNS = {
27
+ ipv4: /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/,
28
+ privateKey: /-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/,
29
+ apiKeyField: /"(?:api_key|apiKey|secret_key|secretKey|access_token|auth_token|password|token)":\s*"([^"]+)"/
30
+ };
31
+ var PATTERNS = {
32
+ ...AWS_PATTERNS,
33
+ ...K8S_PATTERNS,
34
+ ...COMMON_PATTERNS
35
+ };
36
+
37
+ // src/mapping.ts
38
+ import {
39
+ existsSync,
40
+ mkdirSync,
41
+ readFileSync,
42
+ writeFileSync,
43
+ renameSync
44
+ } from "fs";
45
+ import { join } from "path";
46
+ import { homedir } from "os";
47
+ var sessionStates = new Map;
48
+ function getSessionPath(sessionId) {
49
+ const projectRoot = process.cwd();
50
+ const defaultPath = join(projectRoot, ".opencode", "sessions", sessionId, "wont-let-you-see-mapping.json");
51
+ if (existsSync(join(projectRoot, ".opencode")) || !existsSync(homedir())) {
52
+ return defaultPath;
53
+ }
54
+ return join(homedir(), ".opencode", "sessions", sessionId, "wont-let-you-see-mapping.json");
55
+ }
56
+ function ensureSessionDir(sessionPath) {
57
+ const dir = sessionPath.substring(0, sessionPath.lastIndexOf("/"));
58
+ if (!existsSync(dir)) {
59
+ mkdirSync(dir, { recursive: true });
60
+ }
61
+ }
62
+ function getSessionState(sessionId) {
63
+ let state = sessionStates.get(sessionId);
64
+ if (!state) {
65
+ const sessionPath = getSessionPath(sessionId);
66
+ if (existsSync(sessionPath)) {
67
+ const mapping = JSON.parse(readFileSync(sessionPath, "utf-8"));
68
+ const counters = {};
69
+ for (const token of Object.keys(mapping.entries)) {
70
+ const match = token.match(/^#\(([^-]+)-(\d+)\)$/);
71
+ if (match && match[1] && match[2]) {
72
+ const type = match[1];
73
+ const num = match[2];
74
+ counters[type] = Math.max(counters[type] || 0, parseInt(num, 10));
75
+ }
76
+ }
77
+ state = { mapping, counters };
78
+ } else {
79
+ state = {
80
+ mapping: { version: 1, entries: {} },
81
+ counters: {}
82
+ };
83
+ }
84
+ sessionStates.set(sessionId, state);
85
+ }
86
+ return state;
87
+ }
88
+ function saveMapping(sessionId) {
89
+ const state = getSessionState(sessionId);
90
+ const sessionPath = getSessionPath(sessionId);
91
+ ensureSessionDir(sessionPath);
92
+ const tempPath = `${sessionPath}.tmp`;
93
+ writeFileSync(tempPath, JSON.stringify(state.mapping, null, 2), "utf-8");
94
+ renameSync(tempPath, sessionPath);
95
+ }
96
+ function addEntry(sessionId, type, originalValue) {
97
+ const state = getSessionState(sessionId);
98
+ for (const [token2, value] of Object.entries(state.mapping.entries)) {
99
+ if (value === originalValue) {
100
+ return token2;
101
+ }
102
+ }
103
+ const counter = (state.counters[type] || 0) + 1;
104
+ state.counters[type] = counter;
105
+ const token = `#(${type}-${counter})`;
106
+ state.mapping.entries[token] = originalValue;
107
+ saveMapping(sessionId);
108
+ return token;
109
+ }
110
+ function getOriginal(sessionId, token) {
111
+ const state = getSessionState(sessionId);
112
+ return state.mapping.entries[token];
113
+ }
114
+
115
+ // src/config.ts
116
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
117
+ import { join as join2 } from "path";
118
+ import { homedir as homedir2 } from "os";
119
+ var DEFAULT_CONFIG = {
120
+ enabled: true,
121
+ revealedPatterns: [],
122
+ customPatterns: []
123
+ };
124
+ var CONFIG_FILENAME = ".wont-let-you-see.json";
125
+ function loadJsonConfig() {
126
+ const paths = [
127
+ join2(process.cwd(), CONFIG_FILENAME),
128
+ join2(homedir2(), CONFIG_FILENAME)
129
+ ];
130
+ for (const configPath of paths) {
131
+ if (existsSync2(configPath)) {
132
+ try {
133
+ const content = readFileSync2(configPath, "utf-8");
134
+ return JSON.parse(content);
135
+ } catch {}
136
+ }
137
+ }
138
+ return {};
139
+ }
140
+ function loadEnvConfig() {
141
+ const config = {};
142
+ const enabled = process.env.WONT_LET_YOU_SEE_ENABLED;
143
+ if (enabled !== undefined) {
144
+ config.enabled = enabled.toLowerCase() !== "false" && enabled !== "0";
145
+ }
146
+ const revealedPatterns = process.env.WONT_LET_YOU_SEE_REVEALED_PATTERNS;
147
+ if (revealedPatterns !== undefined) {
148
+ config.revealedPatterns = revealedPatterns.split(",").map((p) => p.trim()).filter(Boolean);
149
+ }
150
+ const customPatterns = process.env.WONT_LET_YOU_SEE_CUSTOM_PATTERNS;
151
+ if (customPatterns !== undefined) {
152
+ config.customPatterns = customPatterns.split(",").map((p) => p.trim()).filter(Boolean);
153
+ }
154
+ return config;
155
+ }
156
+ var cachedConfig = null;
157
+ function getConfig() {
158
+ if (cachedConfig) {
159
+ return cachedConfig;
160
+ }
161
+ const jsonConfig = loadJsonConfig();
162
+ const envConfig = loadEnvConfig();
163
+ cachedConfig = {
164
+ ...DEFAULT_CONFIG,
165
+ ...jsonConfig,
166
+ ...envConfig
167
+ };
168
+ return cachedConfig;
169
+ }
170
+ function isPatternEnabled(patternType) {
171
+ const config = getConfig();
172
+ if (!config.enabled) {
173
+ return false;
174
+ }
175
+ return !config.revealedPatterns.includes(patternType);
176
+ }
177
+
178
+ // src/masker.ts
179
+ var PATTERN_ORDER = [
180
+ { pattern: AWS_PATTERNS.eksCluster, type: "eks-cluster" },
181
+ { pattern: AWS_PATTERNS.arn, type: "arn" },
182
+ { pattern: AWS_PATTERNS.accountId, type: "account-id", isContextual: true },
183
+ { pattern: AWS_PATTERNS.accessKeyId, type: "access-key-id" },
184
+ { pattern: AWS_PATTERNS.vpc, type: "vpc" },
185
+ { pattern: AWS_PATTERNS.subnet, type: "subnet" },
186
+ { pattern: AWS_PATTERNS.securityGroup, type: "security-group" },
187
+ { pattern: AWS_PATTERNS.internetGateway, type: "internet-gateway" },
188
+ { pattern: AWS_PATTERNS.routeTable, type: "route-table" },
189
+ { pattern: AWS_PATTERNS.natGateway, type: "nat-gateway" },
190
+ { pattern: AWS_PATTERNS.networkAcl, type: "network-acl" },
191
+ { pattern: AWS_PATTERNS.eni, type: "eni" },
192
+ { pattern: AWS_PATTERNS.ami, type: "ami" },
193
+ { pattern: AWS_PATTERNS.ec2Instance, type: "ec2-instance" },
194
+ { pattern: AWS_PATTERNS.ebs, type: "ebs" },
195
+ { pattern: AWS_PATTERNS.snapshot, type: "snapshot" },
196
+ { pattern: K8S_PATTERNS.serviceAccountToken, type: "k8s-token" },
197
+ { pattern: K8S_PATTERNS.clusterEndpoint, type: "k8s-endpoint" },
198
+ { pattern: K8S_PATTERNS.kubeconfigServer, type: "k8s-endpoint" },
199
+ { pattern: K8S_PATTERNS.nodeNameAws, type: "k8s-node" },
200
+ { pattern: COMMON_PATTERNS.privateKey, type: "private-key" },
201
+ { pattern: COMMON_PATTERNS.apiKeyField, type: "api-key", isContextual: true },
202
+ { pattern: COMMON_PATTERNS.ipv4, type: "ipv4" }
203
+ ];
204
+ function removeAnchors(source) {
205
+ let result = source.replace(/^\^/, "").replace(/\$$/, "");
206
+ if (!result.endsWith("\\b")) {
207
+ result += "\\b";
208
+ }
209
+ return result;
210
+ }
211
+ function mask(sessionId, text) {
212
+ if (!text || typeof text !== "string") {
213
+ return text;
214
+ }
215
+ const config = getConfig();
216
+ if (!config.enabled) {
217
+ return text;
218
+ }
219
+ let result = text;
220
+ for (const customPattern of config.customPatterns) {
221
+ if (result.includes(customPattern)) {
222
+ const token = addEntry(sessionId, "custom", customPattern);
223
+ result = result.split(customPattern).join(token);
224
+ }
225
+ }
226
+ for (const { pattern, type, isContextual } of PATTERN_ORDER) {
227
+ if (!isPatternEnabled(type)) {
228
+ continue;
229
+ }
230
+ if (isContextual) {
231
+ result = result.replace(new RegExp(pattern.source, "g"), (match, capturedValue) => {
232
+ const token = addEntry(sessionId, type, capturedValue);
233
+ return match.replace(capturedValue, token);
234
+ });
235
+ } else {
236
+ const matches = new Set;
237
+ const globalPattern = new RegExp(removeAnchors(pattern.source), "g");
238
+ let match;
239
+ while ((match = globalPattern.exec(result)) !== null) {
240
+ matches.add(match[0]);
241
+ }
242
+ for (const value of matches) {
243
+ const token = addEntry(sessionId, type, value);
244
+ result = result.split(value).join(token);
245
+ }
246
+ }
247
+ }
248
+ return result;
249
+ }
250
+ function unmask(sessionId, text) {
251
+ if (!text || typeof text !== "string") {
252
+ return text;
253
+ }
254
+ let result = text;
255
+ const tokenPattern = /#\([^)]+\)/g;
256
+ const tokens = new Set;
257
+ let match;
258
+ while ((match = tokenPattern.exec(text)) !== null) {
259
+ tokens.add(match[0]);
260
+ }
261
+ for (const token of tokens) {
262
+ const original = getOriginal(sessionId, token);
263
+ if (original === undefined) {
264
+ console.warn(`Unknown token: ${token}`);
265
+ } else {
266
+ result = result.split(token).join(original);
267
+ }
268
+ }
269
+ return result;
270
+ }
271
+
272
+ // src/index.ts
273
+ var INFRA_COMMAND_PATTERN = /\b(aws|terraform|kubectl|helm)\s/;
274
+ var plugin = async (input) => {
275
+ const infraCommands = new Map;
276
+ return {
277
+ "tool.execute.before": async (hookInput, output) => {
278
+ if (hookInput.tool !== "bash") {
279
+ return;
280
+ }
281
+ const command = output.args.command;
282
+ if (!INFRA_COMMAND_PATTERN.test(command)) {
283
+ return;
284
+ }
285
+ infraCommands.set(hookInput.callID, true);
286
+ output.args.command = unmask(hookInput.sessionID, command);
287
+ },
288
+ "tool.execute.after": async (hookInput, output) => {
289
+ if (hookInput.tool !== "bash") {
290
+ return;
291
+ }
292
+ if (!infraCommands.get(hookInput.callID)) {
293
+ return;
294
+ }
295
+ output.output = mask(hookInput.sessionID, output.output);
296
+ },
297
+ "chat.message": async (hookInput, output) => {
298
+ for (const part of output.parts) {
299
+ if (part.type === "text" && part.text) {
300
+ part.text = mask(hookInput.sessionID, part.text);
301
+ }
302
+ }
303
+ }
304
+ };
305
+ };
306
+ export {
307
+ plugin
308
+ };