@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
|
@@ -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
|
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
|
|
32
|
-
| ------------------------------------ |
|
|
33
|
-
| `WONT_LET_YOU_SEE_ENABLED` | Set to `false` or `0` to disable masking
|
|
34
|
-
| `WONT_LET_YOU_SEE_REVEALED_PATTERNS` | Comma-separated list of pattern types to reveal
|
|
35
|
-
| `WONT_LET_YOU_SEE_CUSTOM_PATTERNS` | Comma-separated list of custom
|
|
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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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 =
|
|
51
|
-
if (existsSync(
|
|
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
|
|
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(
|
|
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 {
|
|
117
|
-
|
|
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 =
|
|
130
|
-
if (
|
|
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 =
|
|
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 =
|
|
180
|
+
const content = fsAdapter.readFileSync(ancestorConfig, "utf-8");
|
|
149
181
|
return JSON.parse(content);
|
|
150
182
|
} catch {}
|
|
151
183
|
}
|
|
152
|
-
const homeConfig =
|
|
153
|
-
if (
|
|
184
|
+
const homeConfig = join3(homedir2(), CONFIG_FILENAME);
|
|
185
|
+
if (fsAdapter.existsSync(homeConfig)) {
|
|
154
186
|
try {
|
|
155
|
-
const content =
|
|
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
|
-
|
|
233
|
-
|
|
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
|
|
242
|
-
if (
|
|
243
|
-
const
|
|
244
|
-
|
|
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
|
-
|
|
248
|
-
|
|
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,
|
|
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,
|
|
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
|
@@ -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 {
|
|
3
|
-
|
|
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.
|
|
7
|
-
|
|
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
|
-
|
|
22
|
-
|
|
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
|
-
|
|
123
|
+
mockExistsSync.mockImplementation((p: string) => {
|
|
120
124
|
return p === path.join("/project", ".wont-let-you-see.json");
|
|
121
125
|
});
|
|
122
|
-
|
|
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
|
-
|
|
138
|
+
mockExistsSync.mockImplementation((p: string) => {
|
|
135
139
|
return p === path.join("/project", ".wont-let-you-see.json");
|
|
136
140
|
});
|
|
137
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|