@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.
@@ -0,0 +1,14 @@
1
+ name: CI
2
+ on:
3
+ pull_request:
4
+ branches: [main]
5
+
6
+ jobs:
7
+ check:
8
+ runs-on: ubuntu-latest
9
+ steps:
10
+ - uses: actions/checkout@v4
11
+ - uses: oven-sh/setup-bun@v2
12
+ - run: bun install --frozen-lockfile
13
+ - run: bun run typecheck
14
+ - run: bunx prettier --check "src/**/*.ts"
@@ -0,0 +1,20 @@
1
+ name: Publish
2
+ on:
3
+ release:
4
+ types: [published]
5
+
6
+ jobs:
7
+ publish:
8
+ runs-on: ubuntu-latest
9
+ steps:
10
+ - uses: actions/checkout@v4
11
+ - uses: oven-sh/setup-bun@v2
12
+ - uses: actions/setup-node@v4
13
+ with:
14
+ registry-url: https://registry.npmjs.org
15
+ - run: bun install --frozen-lockfile
16
+ - run: bun run build
17
+ - run: bunx tsc --emitDeclarationOnly --declaration --outDir dist
18
+ - run: npm publish
19
+ env:
20
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
@@ -0,0 +1,20 @@
1
+ name: Draft Release
2
+ on:
3
+ push:
4
+ branches: [main]
5
+
6
+ jobs:
7
+ release:
8
+ runs-on: ubuntu-latest
9
+ permissions:
10
+ contents: write
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ - id: pkg
14
+ run: echo "version=$(jq -r .version package.json)" >> $GITHUB_OUTPUT
15
+ - uses: softprops/action-gh-release@v2
16
+ with:
17
+ tag_name: v${{ steps.pkg.outputs.version }}
18
+ name: v${{ steps.pkg.outputs.version }}
19
+ draft: true
20
+ generate_release_notes: true
@@ -0,0 +1,13 @@
1
+ name: Test
2
+ on:
3
+ pull_request:
4
+ branches: [main]
5
+
6
+ jobs:
7
+ test:
8
+ runs-on: ubuntu-latest
9
+ steps:
10
+ - uses: actions/checkout@v4
11
+ - uses: oven-sh/setup-bun@v2
12
+ - run: bun install --frozen-lockfile
13
+ - run: bun test
package/README.md CHANGED
@@ -28,11 +28,11 @@ Configure via environment variables or JSON config file. Environment variables t
28
28
 
29
29
  #### Environment Variables
30
30
 
31
- | Variable | Description | Default |
32
- | ------------------------------------ | ----------------------------------------------- | ------- |
33
- | `WONT_LET_YOU_SEE_ENABLED` | Set to `false` or `0` to disable masking | `true` |
34
- | `WONT_LET_YOU_SEE_REVEALED_PATTERNS` | Comma-separated list of pattern types to reveal | (none) |
35
- | `WONT_LET_YOU_SEE_CUSTOM_PATTERNS` | Comma-separated list of custom strings to mask | (none) |
31
+ | Variable | Description | Default |
32
+ | ------------------------------------ | -------------------------------------------------------------------------- | ------- |
33
+ | `WONT_LET_YOU_SEE_ENABLED` | Set to `false` or `0` to disable masking | `true` |
34
+ | `WONT_LET_YOU_SEE_REVEALED_PATTERNS` | Comma-separated list of pattern types to reveal | (none) |
35
+ | `WONT_LET_YOU_SEE_CUSTOM_PATTERNS` | Comma-separated list of custom patterns to mask (supports `regex:` prefix) | (none) |
36
36
 
37
37
  #### JSON Config File
38
38
 
@@ -48,6 +48,18 @@ Create `.wont-let-you-see.json` in your project root, `~/.config/opencode/`, or
48
48
 
49
49
  > **Tip**: Add your AWS account ID to `customPatterns`. The built-in `account-id` pattern only matches contextual fields like `"OwnerId": "123456789012"`, but may miss bare account IDs in terraform output or other contexts.
50
50
 
51
+ Custom patterns support both literal strings and regular expressions. Prefix with `regex:` to use regex:
52
+
53
+ ```json
54
+ {
55
+ "customPatterns": [
56
+ "123456789012",
57
+ "my-secret-value",
58
+ "regex:secret-[a-z]{3}-\\d{4}"
59
+ ]
60
+ }
61
+ ```
62
+
51
63
  #### Examples
52
64
 
53
65
  ```bash
@@ -59,6 +71,9 @@ WONT_LET_YOU_SEE_REVEALED_PATTERNS=eks-cluster,ipv4 opencode
59
71
 
60
72
  # Mask custom values (e.g., AWS account ID)
61
73
  WONT_LET_YOU_SEE_CUSTOM_PATTERNS=123456789012,my-secret opencode
74
+
75
+ # Mask with regex patterns
76
+ WONT_LET_YOU_SEE_CUSTOM_PATTERNS="regex:token-[A-Z]{8},my-literal-secret" opencode
62
77
  ```
63
78
 
64
79
  ## Supported Commands
@@ -92,6 +107,12 @@ The plugin automatically masks output from:
92
107
  | `ebs` | EBS Volume IDs | `vol-0123456789abcdef0` |
93
108
  | `snapshot` | EBS Snapshot IDs | `snap-0123456789abcdef0` |
94
109
  | `eni` | Network Interface IDs | `eni-0123456789abcdef0` |
110
+ | `vpc-endpoint` | VPC Endpoint IDs | `vpce-0123456789abcdef0` |
111
+ | `transit-gateway` | Transit Gateway IDs | `tgw-0123456789abcdef0` |
112
+ | `customer-gateway` | Customer Gateway IDs | `cgw-0123456789abcdef0` |
113
+ | `vpn-gateway` | VPN Gateway IDs | `vgw-0123456789abcdef0` |
114
+ | `vpn-connection` | VPN Connection IDs | `vpn-0123456789abcdef0` |
115
+ | `ecr-repo` | ECR Repository URIs | `123456789012.dkr.ecr.us-west-2.amazonaws.com/my-repo` |
95
116
 
96
117
  ### Kubernetes Resources (EKS)
97
118
 
@@ -141,9 +162,49 @@ What was the actual VPC ID from the last command?
141
162
 
142
163
  The LLM should only know the token (e.g., `#(vpc-1)`), not the real value.
143
164
 
165
+ ## Contributing Patterns
166
+
167
+ Patterns are defined in JSON files under the `patterns/` directory:
168
+
169
+ - `patterns/aws.json` - AWS resource patterns
170
+ - `patterns/kubernetes.json` - Kubernetes patterns
171
+ - `patterns/common.json` - Common patterns (IPs, keys, etc.)
172
+
173
+ You can request to add new files for resources of different categories.
174
+
175
+ ### Pattern Format
176
+
177
+ Each pattern can be defined as:
178
+
179
+ ```json
180
+ {
181
+ "pattern-name": "^regex-pattern$",
182
+
183
+ "contextual-pattern": {
184
+ "pattern": "\"field\":\\s*\"(captured-value)\"",
185
+ "contextual": true
186
+ },
187
+
188
+ "literal-match": {
189
+ "exact": "literal-string-to-match"
190
+ }
191
+ }
192
+ ```
193
+
194
+ - **Simple regex**: Just a string with the regex pattern
195
+ - **Contextual**: For patterns where only a captured group should be masked (e.g., JSON fields)
196
+ - **Exact match**: For literal strings that should be escaped
197
+
198
+ ### Adding New Patterns
199
+
200
+ 1. Fork the repository
201
+ 2. Add your pattern to the appropriate JSON file
202
+ 3. Add tests in `src/__tests__/patterns.test.ts`
203
+ 4. Submit a pull request
204
+
144
205
  ## Limitations
145
206
 
146
- - **AWS only**: Currently supports AWS. GCP and Azure are not yet supported.
207
+ - **AWS only**: Currently supports AWS. GCP and Azure are not yet supported. Do you need them? Feel free to contribute!
147
208
  - **S3 Buckets**: Bucket names are not masked (often public/intentional).
148
209
  - **Account IDs**: Only masked in contextual JSON fields. Add to `customPatterns` for full coverage.
149
210
  - **UI display**: The UI shows original values (OpenCode limitation).
package/dist/index.js CHANGED
@@ -1,57 +1,82 @@
1
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
- };
2
+ import { readdirSync, readFileSync } from "fs";
3
+ import { join, dirname } from "path";
4
+ import { fileURLToPath } from "url";
5
+ var cachedPatterns = null;
6
+ function getPackagePatternsDir() {
7
+ const currentFile = fileURLToPath(import.meta.url);
8
+ const srcDir = dirname(currentFile);
9
+ const packageRoot = dirname(srcDir);
10
+ return join(packageRoot, "patterns");
11
+ }
12
+ function parsePatternDefinition(name, definition) {
13
+ if (typeof definition === "string") {
14
+ return {
15
+ name,
16
+ pattern: new RegExp(definition),
17
+ isContextual: false,
18
+ isExact: false
19
+ };
20
+ }
21
+ if ("exact" in definition) {
22
+ const escaped = definition.exact.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
23
+ return {
24
+ name,
25
+ pattern: new RegExp(escaped),
26
+ isContextual: false,
27
+ isExact: true
28
+ };
29
+ }
30
+ return {
31
+ name,
32
+ pattern: new RegExp(definition.pattern),
33
+ isContextual: definition.contextual ?? false,
34
+ isExact: false
35
+ };
36
+ }
37
+ function loadPatternFile(filePath) {
38
+ const content = readFileSync(filePath, "utf-8");
39
+ const data = JSON.parse(content);
40
+ const patterns = [];
41
+ for (const [name, definition] of Object.entries(data)) {
42
+ patterns.push(parsePatternDefinition(name, definition));
43
+ }
44
+ return patterns;
45
+ }
46
+ function loadPatterns() {
47
+ if (cachedPatterns) {
48
+ return cachedPatterns;
49
+ }
50
+ const patternsDir = getPackagePatternsDir();
51
+ const patterns = [];
52
+ const files = readdirSync(patternsDir).filter((f) => f.endsWith(".json")).sort();
53
+ for (const file of files) {
54
+ const filePath = join(patternsDir, file);
55
+ const filePatterns = loadPatternFile(filePath);
56
+ patterns.push(...filePatterns);
57
+ }
58
+ cachedPatterns = patterns;
59
+ return patterns;
60
+ }
36
61
 
37
62
  // src/mapping.ts
38
63
  import {
39
64
  existsSync,
40
65
  mkdirSync,
41
- readFileSync,
66
+ readFileSync as readFileSync2,
42
67
  writeFileSync,
43
68
  renameSync
44
69
  } from "fs";
45
- import { join } from "path";
70
+ import { join as join2 } from "path";
46
71
  import { homedir } from "os";
47
72
  var sessionStates = new Map;
48
73
  function getSessionPath(sessionId) {
49
74
  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())) {
75
+ const defaultPath = join2(projectRoot, ".opencode", "sessions", sessionId, "wont-let-you-see-mapping.json");
76
+ if (existsSync(join2(projectRoot, ".opencode")) || !existsSync(homedir())) {
52
77
  return defaultPath;
53
78
  }
54
- return join(homedir(), ".opencode", "sessions", sessionId, "wont-let-you-see-mapping.json");
79
+ return join2(homedir(), ".opencode", "sessions", sessionId, "wont-let-you-see-mapping.json");
55
80
  }
56
81
  function ensureSessionDir(sessionPath) {
57
82
  const dir = sessionPath.substring(0, sessionPath.lastIndexOf("/"));
@@ -64,7 +89,7 @@ function getSessionState(sessionId) {
64
89
  if (!state) {
65
90
  const sessionPath = getSessionPath(sessionId);
66
91
  if (existsSync(sessionPath)) {
67
- const mapping = JSON.parse(readFileSync(sessionPath, "utf-8"));
92
+ const mapping = JSON.parse(readFileSync2(sessionPath, "utf-8"));
68
93
  const counters = {};
69
94
  for (const token of Object.keys(mapping.entries)) {
70
95
  const match = token.match(/^#\(([^-]+)-(\d+)\)$/);
@@ -113,9 +138,16 @@ function getOriginal(sessionId, token) {
113
138
  }
114
139
 
115
140
  // src/config.ts
116
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
117
- import { join as join2, dirname } from "path";
141
+ import {
142
+ existsSync as nodeExistsSync,
143
+ readFileSync as nodeReadFileSync
144
+ } from "fs";
145
+ import { join as join3, dirname as dirname2 } from "path";
118
146
  import { homedir as homedir2 } from "os";
147
+ var fsAdapter = {
148
+ existsSync: nodeExistsSync,
149
+ readFileSync: nodeReadFileSync
150
+ };
119
151
  var DEFAULT_CONFIG = {
120
152
  enabled: true,
121
153
  revealedPatterns: [],
@@ -126,14 +158,14 @@ function findConfigInAncestors(startDir) {
126
158
  const home = homedir2();
127
159
  let currentDir = startDir;
128
160
  while (true) {
129
- const configPath = join2(currentDir, CONFIG_FILENAME);
130
- if (existsSync2(configPath)) {
161
+ const configPath = join3(currentDir, CONFIG_FILENAME);
162
+ if (fsAdapter.existsSync(configPath)) {
131
163
  return configPath;
132
164
  }
133
165
  if (currentDir === home) {
134
166
  break;
135
167
  }
136
- const parentDir = dirname(currentDir);
168
+ const parentDir = dirname2(currentDir);
137
169
  if (parentDir === currentDir) {
138
170
  break;
139
171
  }
@@ -145,14 +177,14 @@ function loadJsonConfig() {
145
177
  const ancestorConfig = findConfigInAncestors(process.cwd());
146
178
  if (ancestorConfig) {
147
179
  try {
148
- const content = readFileSync2(ancestorConfig, "utf-8");
180
+ const content = fsAdapter.readFileSync(ancestorConfig, "utf-8");
149
181
  return JSON.parse(content);
150
182
  } catch {}
151
183
  }
152
- const homeConfig = join2(homedir2(), CONFIG_FILENAME);
153
- if (existsSync2(homeConfig)) {
184
+ const homeConfig = join3(homedir2(), CONFIG_FILENAME);
185
+ if (fsAdapter.existsSync(homeConfig)) {
154
186
  try {
155
- const content = readFileSync2(homeConfig, "utf-8");
187
+ const content = fsAdapter.readFileSync(homeConfig, "utf-8");
156
188
  return JSON.parse(content);
157
189
  } catch {}
158
190
  }
@@ -197,31 +229,6 @@ function isPatternEnabled(patternType) {
197
229
  }
198
230
 
199
231
  // src/masker.ts
200
- var PATTERN_ORDER = [
201
- { pattern: AWS_PATTERNS.eksCluster, type: "eks-cluster" },
202
- { pattern: AWS_PATTERNS.arn, type: "arn" },
203
- { pattern: AWS_PATTERNS.accountId, type: "account-id", isContextual: true },
204
- { pattern: AWS_PATTERNS.accessKeyId, type: "access-key-id" },
205
- { pattern: AWS_PATTERNS.vpc, type: "vpc" },
206
- { pattern: AWS_PATTERNS.subnet, type: "subnet" },
207
- { pattern: AWS_PATTERNS.securityGroup, type: "security-group" },
208
- { pattern: AWS_PATTERNS.internetGateway, type: "internet-gateway" },
209
- { pattern: AWS_PATTERNS.routeTable, type: "route-table" },
210
- { pattern: AWS_PATTERNS.natGateway, type: "nat-gateway" },
211
- { pattern: AWS_PATTERNS.networkAcl, type: "network-acl" },
212
- { pattern: AWS_PATTERNS.eni, type: "eni" },
213
- { pattern: AWS_PATTERNS.ami, type: "ami" },
214
- { pattern: AWS_PATTERNS.ec2Instance, type: "ec2-instance" },
215
- { pattern: AWS_PATTERNS.ebs, type: "ebs" },
216
- { pattern: AWS_PATTERNS.snapshot, type: "snapshot" },
217
- { pattern: K8S_PATTERNS.serviceAccountToken, type: "k8s-token" },
218
- { pattern: K8S_PATTERNS.clusterEndpoint, type: "k8s-endpoint" },
219
- { pattern: K8S_PATTERNS.kubeconfigServer, type: "k8s-endpoint" },
220
- { pattern: K8S_PATTERNS.nodeNameAws, type: "k8s-node" },
221
- { pattern: COMMON_PATTERNS.privateKey, type: "private-key" },
222
- { pattern: COMMON_PATTERNS.apiKeyField, type: "api-key", isContextual: true },
223
- { pattern: COMMON_PATTERNS.ipv4, type: "ipv4" }
224
- ];
225
232
  function removeAnchors(source) {
226
233
  let result = source.replace(/^\^/, "").replace(/\$$/, "");
227
234
  if (!result.endsWith("\\b")) {
@@ -229,28 +236,42 @@ function removeAnchors(source) {
229
236
  }
230
237
  return result;
231
238
  }
232
- function mask(sessionId, text) {
233
- if (!text || typeof text !== "string") {
234
- return text;
235
- }
236
- const config = getConfig();
237
- if (!config.enabled) {
238
- return text;
239
- }
239
+ var REGEX_PREFIX = "regex:";
240
+ function applyCustomPatterns(sessionId, text, customPatterns) {
240
241
  let result = text;
241
- for (const customPattern of config.customPatterns) {
242
- if (result.includes(customPattern)) {
243
- const token = addEntry(sessionId, "custom", customPattern);
244
- result = result.split(customPattern).join(token);
242
+ for (const customPattern of customPatterns) {
243
+ if (customPattern.startsWith(REGEX_PREFIX)) {
244
+ const regexStr = customPattern.slice(REGEX_PREFIX.length);
245
+ const regex = new RegExp(removeAnchors(regexStr), "g");
246
+ const matches = new Set;
247
+ let match;
248
+ while ((match = regex.exec(result)) !== null) {
249
+ matches.add(match[0]);
250
+ }
251
+ for (const value of matches) {
252
+ const token = addEntry(sessionId, "custom", value);
253
+ result = result.split(value).join(token);
254
+ }
255
+ } else {
256
+ const escaped = customPattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
257
+ const literalRegex = new RegExp(escaped + "(?![\\w])", "g");
258
+ if (literalRegex.test(result)) {
259
+ const token = addEntry(sessionId, "custom", customPattern);
260
+ result = result.replace(literalRegex, token);
261
+ }
245
262
  }
246
263
  }
247
- for (const { pattern, type, isContextual } of PATTERN_ORDER) {
248
- if (!isPatternEnabled(type)) {
264
+ return result;
265
+ }
266
+ function applyLoadedPatterns(sessionId, text, patterns) {
267
+ let result = text;
268
+ for (const { name, pattern, isContextual } of patterns) {
269
+ if (!isPatternEnabled(name)) {
249
270
  continue;
250
271
  }
251
272
  if (isContextual) {
252
273
  result = result.replace(new RegExp(pattern.source, "g"), (match, capturedValue) => {
253
- const token = addEntry(sessionId, type, capturedValue);
274
+ const token = addEntry(sessionId, name, capturedValue);
254
275
  return match.replace(capturedValue, token);
255
276
  });
256
277
  } else {
@@ -261,13 +282,27 @@ function mask(sessionId, text) {
261
282
  matches.add(match[0]);
262
283
  }
263
284
  for (const value of matches) {
264
- const token = addEntry(sessionId, type, value);
285
+ const token = addEntry(sessionId, name, value);
265
286
  result = result.split(value).join(token);
266
287
  }
267
288
  }
268
289
  }
269
290
  return result;
270
291
  }
292
+ function mask(sessionId, text) {
293
+ if (!text || typeof text !== "string") {
294
+ return text;
295
+ }
296
+ const config = getConfig();
297
+ if (!config.enabled) {
298
+ return text;
299
+ }
300
+ let result = text;
301
+ result = applyCustomPatterns(sessionId, result, config.customPatterns);
302
+ const patterns = loadPatterns();
303
+ result = applyLoadedPatterns(sessionId, result, patterns);
304
+ return result;
305
+ }
271
306
  function unmask(sessionId, text) {
272
307
  if (!text || typeof text !== "string") {
273
308
  return text;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniglot/wont-let-you-see",
3
- "version": "0.1.1",
3
+ "version": "0.3.0",
4
4
  "description": "OpenCode plugin that masks sensitive cloud infrastructure data (AWS, Kubernetes) from LLMs",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -0,0 +1,27 @@
1
+ {
2
+ "eks-cluster": "^arn:(?:aws|aws-cn|aws-us-gov):eks:[a-z0-9-]+:[0-9]{12}:cluster\\/.+$",
3
+ "arn": "^arn:(?:aws|aws-cn|aws-us-gov):[a-zA-Z0-9-]+:[a-z0-9-]*:(?:[0-9]{12})?:.+$",
4
+ "account-id": {
5
+ "pattern": "\"(?:OwnerId|AccountId|Owner|account_id)\":\\s*\"(\\d{12})\"",
6
+ "contextual": true
7
+ },
8
+ "access-key-id": "(?:^|[^A-Z0-9])(?:AKIA|ABIA|ACCA|ASIA)[A-Z0-9]{16}(?:[^A-Z0-9]|$)",
9
+ "vpc": "^vpc-[0-9a-f]{8,17}$",
10
+ "subnet": "^subnet-[0-9a-f]{8,17}$",
11
+ "security-group": "^sg-[0-9a-f]{8,17}$",
12
+ "internet-gateway": "^igw-[0-9a-f]{8,17}$",
13
+ "route-table": "^rtb-[0-9a-f]{8,17}$",
14
+ "nat-gateway": "^nat-[0-9a-f]{8,17}$",
15
+ "network-acl": "^acl-[0-9a-f]{8,17}$",
16
+ "eni": "^eni-[0-9a-f]{8,17}$",
17
+ "vpc-endpoint": "^vpce-[0-9a-f]{8,17}$",
18
+ "transit-gateway": "^tgw-[0-9a-f]{8,17}$",
19
+ "customer-gateway": "^cgw-[0-9a-f]{8,17}$",
20
+ "vpn-gateway": "^vgw-[0-9a-f]{8,17}$",
21
+ "vpn-connection": "^vpn-[0-9a-f]{8,17}$",
22
+ "ecr-repo": "^(?:\\d{12}|#\\(custom-\\d+\\))\\.dkr\\.ecr\\.[a-z0-9-]+\\.amazonaws\\.com\\/[a-z0-9._\\/-]+$",
23
+ "ami": "^ami-[0-9a-f]{8,17}$",
24
+ "ec2-instance": "^i-[0-9a-f]{8,17}$",
25
+ "ebs": "^vol-[0-9a-f]{8,17}$",
26
+ "snapshot": "^snap-[0-9a-f]{8,17}$"
27
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "private-key": "-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----[\\s\\S]*?-----END (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----",
3
+ "api-key": {
4
+ "pattern": "\"(?:api_key|apiKey|secret_key|secretKey|access_token|auth_token|password|token)\":\\s*\"([^\"]+)\"",
5
+ "contextual": true
6
+ },
7
+ "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]?)$"
8
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "k8s-token": "^eyJ[A-Za-z0-9_-]*\\.eyJ[A-Za-z0-9_-]*\\.[A-Za-z0-9_-]*$",
3
+ "k8s-endpoint": "^https:\\/\\/[A-Z0-9]+\\.[a-z0-9-]+\\.eks\\.amazonaws\\.com$",
4
+ "k8s-endpoint-kubeconfig": {
5
+ "pattern": "^https:\\/\\/[0-9A-F]{32}\\.[a-z]{2}-[a-z]+-\\d\\.eks\\.amazonaws\\.com$",
6
+ "contextual": false
7
+ },
8
+ "k8s-node": "^ip-(?:\\d{1,3}-){3}\\d{1,3}\\.[a-z0-9-]+\\.compute\\.internal$"
9
+ }
@@ -1,16 +1,15 @@
1
1
  import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
2
- import { getConfig, resetConfig, isPatternEnabled } from "../config";
3
- import * as fs from "fs";
2
+ import {
3
+ getConfig,
4
+ resetConfig,
5
+ isPatternEnabled,
6
+ setFsAdapter,
7
+ resetFsAdapter,
8
+ } from "../config";
4
9
  import * as path from "path";
5
10
 
6
- vi.mock("fs", async () => {
7
- const actual = await vi.importActual<typeof fs>("fs");
8
- return {
9
- ...actual,
10
- existsSync: vi.fn(),
11
- readFileSync: vi.fn(),
12
- };
13
- });
11
+ const mockExistsSync = vi.fn();
12
+ const mockReadFileSync = vi.fn();
14
13
 
15
14
  describe("Config", () => {
16
15
  const originalEnv = { ...process.env };
@@ -18,14 +17,19 @@ describe("Config", () => {
18
17
 
19
18
  beforeEach(() => {
20
19
  resetConfig();
21
- vi.mocked(fs.existsSync).mockReturnValue(false);
22
- vi.mocked(fs.readFileSync).mockReturnValue("{}");
20
+ setFsAdapter({
21
+ existsSync: mockExistsSync,
22
+ readFileSync: mockReadFileSync,
23
+ });
24
+ mockExistsSync.mockReturnValue(false);
25
+ mockReadFileSync.mockReturnValue("{}");
23
26
  });
24
27
 
25
28
  afterEach(() => {
26
29
  process.env = { ...originalEnv };
27
30
  process.cwd = originalCwd;
28
31
  resetConfig();
32
+ resetFsAdapter();
29
33
  vi.clearAllMocks();
30
34
  });
31
35
 
@@ -116,10 +120,10 @@ describe("Config", () => {
116
120
  it("should find config in parent directory when cwd is subdirectory", () => {
117
121
  process.cwd = () => "/project/packages/app";
118
122
 
119
- vi.mocked(fs.existsSync).mockImplementation((p) => {
123
+ mockExistsSync.mockImplementation((p: string) => {
120
124
  return p === path.join("/project", ".wont-let-you-see.json");
121
125
  });
122
- vi.mocked(fs.readFileSync).mockReturnValue(
126
+ mockReadFileSync.mockReturnValue(
123
127
  JSON.stringify({ enabled: false, customPatterns: ["secret123"] }),
124
128
  );
125
129
 
@@ -131,10 +135,10 @@ describe("Config", () => {
131
135
  it("should find config in grandparent directory", () => {
132
136
  process.cwd = () => "/project/packages/app/src/components";
133
137
 
134
- vi.mocked(fs.existsSync).mockImplementation((p) => {
138
+ mockExistsSync.mockImplementation((p: string) => {
135
139
  return p === path.join("/project", ".wont-let-you-see.json");
136
140
  });
137
- vi.mocked(fs.readFileSync).mockReturnValue(
141
+ mockReadFileSync.mockReturnValue(
138
142
  JSON.stringify({ revealedPatterns: ["ipv4"] }),
139
143
  );
140
144
 
@@ -145,13 +149,13 @@ describe("Config", () => {
145
149
  it("should prefer config in closer ancestor over distant ancestor", () => {
146
150
  process.cwd = () => "/project/packages/app";
147
151
 
148
- vi.mocked(fs.existsSync).mockImplementation((p) => {
152
+ mockExistsSync.mockImplementation((p: string) => {
149
153
  return (
150
154
  p === path.join("/project/packages/app", ".wont-let-you-see.json") ||
151
155
  p === path.join("/project", ".wont-let-you-see.json")
152
156
  );
153
157
  });
154
- vi.mocked(fs.readFileSync).mockImplementation((p) => {
158
+ mockReadFileSync.mockImplementation((p: string) => {
155
159
  if (
156
160
  p === path.join("/project/packages/app", ".wont-let-you-see.json")
157
161
  ) {
@@ -167,13 +171,13 @@ describe("Config", () => {
167
171
  it("should prefer cwd config over parent config", () => {
168
172
  process.cwd = () => "/project/subdir";
169
173
 
170
- vi.mocked(fs.existsSync).mockImplementation((p) => {
174
+ mockExistsSync.mockImplementation((p: string) => {
171
175
  return (
172
176
  p === path.join("/project/subdir", ".wont-let-you-see.json") ||
173
177
  p === path.join("/project", ".wont-let-you-see.json")
174
178
  );
175
179
  });
176
- vi.mocked(fs.readFileSync).mockImplementation((p) => {
180
+ mockReadFileSync.mockImplementation((p: string) => {
177
181
  if (p === path.join("/project/subdir", ".wont-let-you-see.json")) {
178
182
  return JSON.stringify({ enabled: false });
179
183
  }