@uniglot/wont-let-you-see 0.1.0 → 0.2.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/CODEOWNERS +1 -0
- 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 +6 -0
- package/dist/index.js +53 -18
- package/package.json +1 -1
- package/src/__tests__/config.test.ts +95 -2
- package/src/__tests__/patterns.test.ts +98 -23
- package/src/config.ts +61 -14
- package/src/masker.ts +6 -0
- package/src/patterns.ts +7 -7
- package/dist/patterns.js +0 -5233
|
@@ -0,0 +1 @@
|
|
|
1
|
+
* @uniglot
|
|
@@ -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
|
@@ -92,6 +92,12 @@ The plugin automatically masks output from:
|
|
|
92
92
|
| `ebs` | EBS Volume IDs | `vol-0123456789abcdef0` |
|
|
93
93
|
| `snapshot` | EBS Snapshot IDs | `snap-0123456789abcdef0` |
|
|
94
94
|
| `eni` | Network Interface IDs | `eni-0123456789abcdef0` |
|
|
95
|
+
| `vpc-endpoint` | VPC Endpoint IDs | `vpce-0123456789abcdef0` |
|
|
96
|
+
| `transit-gateway` | Transit Gateway IDs | `tgw-0123456789abcdef0` |
|
|
97
|
+
| `customer-gateway` | Customer Gateway IDs | `cgw-0123456789abcdef0` |
|
|
98
|
+
| `vpn-gateway` | VPN Gateway IDs | `vgw-0123456789abcdef0` |
|
|
99
|
+
| `vpn-connection` | VPN Connection IDs | `vpn-0123456789abcdef0` |
|
|
100
|
+
| `ecr-repo` | ECR Repository URIs | `123456789012.dkr.ecr.us-west-2.amazonaws.com/my-repo` |
|
|
95
101
|
|
|
96
102
|
### Kubernetes Resources (EKS)
|
|
97
103
|
|
package/dist/index.js
CHANGED
|
@@ -14,7 +14,13 @@ var AWS_PATTERNS = {
|
|
|
14
14
|
ebs: /^vol-[0-9a-f]{8,17}$/,
|
|
15
15
|
snapshot: /^snap-[0-9a-f]{8,17}$/,
|
|
16
16
|
eni: /^eni-[0-9a-f]{8,17}$/,
|
|
17
|
+
vpcEndpoint: /^vpce-[0-9a-f]{8,17}$/,
|
|
18
|
+
transitGateway: /^tgw-[0-9a-f]{8,17}$/,
|
|
19
|
+
customerGateway: /^cgw-[0-9a-f]{8,17}$/,
|
|
20
|
+
vpnGateway: /^vgw-[0-9a-f]{8,17}$/,
|
|
21
|
+
vpnConnection: /^vpn-[0-9a-f]{8,17}$/,
|
|
17
22
|
accountId: /"(?:OwnerId|AccountId|Owner|account_id)":\s*"(\d{12})"/,
|
|
23
|
+
ecrRepoUri: /^(?:\d{12}|#\(custom-\d+\))\.dkr\.ecr\.[a-z0-9-]+\.amazonaws\.com\/[a-z0-9._\/-]+$/,
|
|
18
24
|
accessKeyId: /(?:^|[^A-Z0-9])(?:AKIA|ABIA|ACCA|ASIA)[A-Z0-9]{16}(?:[^A-Z0-9]|$)/
|
|
19
25
|
};
|
|
20
26
|
var K8S_PATTERNS = {
|
|
@@ -28,11 +34,6 @@ var COMMON_PATTERNS = {
|
|
|
28
34
|
privateKey: /-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/,
|
|
29
35
|
apiKeyField: /"(?:api_key|apiKey|secret_key|secretKey|access_token|auth_token|password|token)":\s*"([^"]+)"/
|
|
30
36
|
};
|
|
31
|
-
var PATTERNS = {
|
|
32
|
-
...AWS_PATTERNS,
|
|
33
|
-
...K8S_PATTERNS,
|
|
34
|
-
...COMMON_PATTERNS
|
|
35
|
-
};
|
|
36
37
|
|
|
37
38
|
// src/mapping.ts
|
|
38
39
|
import {
|
|
@@ -113,27 +114,55 @@ function getOriginal(sessionId, token) {
|
|
|
113
114
|
}
|
|
114
115
|
|
|
115
116
|
// src/config.ts
|
|
116
|
-
import {
|
|
117
|
-
|
|
117
|
+
import {
|
|
118
|
+
existsSync as nodeExistsSync,
|
|
119
|
+
readFileSync as nodeReadFileSync
|
|
120
|
+
} from "fs";
|
|
121
|
+
import { join as join2, dirname } from "path";
|
|
118
122
|
import { homedir as homedir2 } from "os";
|
|
123
|
+
var fsAdapter = {
|
|
124
|
+
existsSync: nodeExistsSync,
|
|
125
|
+
readFileSync: nodeReadFileSync
|
|
126
|
+
};
|
|
119
127
|
var DEFAULT_CONFIG = {
|
|
120
128
|
enabled: true,
|
|
121
129
|
revealedPatterns: [],
|
|
122
130
|
customPatterns: []
|
|
123
131
|
};
|
|
124
132
|
var CONFIG_FILENAME = ".wont-let-you-see.json";
|
|
125
|
-
function
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
} catch {}
|
|
133
|
+
function findConfigInAncestors(startDir) {
|
|
134
|
+
const home = homedir2();
|
|
135
|
+
let currentDir = startDir;
|
|
136
|
+
while (true) {
|
|
137
|
+
const configPath = join2(currentDir, CONFIG_FILENAME);
|
|
138
|
+
if (fsAdapter.existsSync(configPath)) {
|
|
139
|
+
return configPath;
|
|
140
|
+
}
|
|
141
|
+
if (currentDir === home) {
|
|
142
|
+
break;
|
|
136
143
|
}
|
|
144
|
+
const parentDir = dirname(currentDir);
|
|
145
|
+
if (parentDir === currentDir) {
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
currentDir = parentDir;
|
|
149
|
+
}
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
function loadJsonConfig() {
|
|
153
|
+
const ancestorConfig = findConfigInAncestors(process.cwd());
|
|
154
|
+
if (ancestorConfig) {
|
|
155
|
+
try {
|
|
156
|
+
const content = fsAdapter.readFileSync(ancestorConfig, "utf-8");
|
|
157
|
+
return JSON.parse(content);
|
|
158
|
+
} catch {}
|
|
159
|
+
}
|
|
160
|
+
const homeConfig = join2(homedir2(), CONFIG_FILENAME);
|
|
161
|
+
if (fsAdapter.existsSync(homeConfig)) {
|
|
162
|
+
try {
|
|
163
|
+
const content = fsAdapter.readFileSync(homeConfig, "utf-8");
|
|
164
|
+
return JSON.parse(content);
|
|
165
|
+
} catch {}
|
|
137
166
|
}
|
|
138
167
|
return {};
|
|
139
168
|
}
|
|
@@ -189,6 +218,12 @@ var PATTERN_ORDER = [
|
|
|
189
218
|
{ pattern: AWS_PATTERNS.natGateway, type: "nat-gateway" },
|
|
190
219
|
{ pattern: AWS_PATTERNS.networkAcl, type: "network-acl" },
|
|
191
220
|
{ pattern: AWS_PATTERNS.eni, type: "eni" },
|
|
221
|
+
{ pattern: AWS_PATTERNS.vpcEndpoint, type: "vpc-endpoint" },
|
|
222
|
+
{ pattern: AWS_PATTERNS.transitGateway, type: "transit-gateway" },
|
|
223
|
+
{ pattern: AWS_PATTERNS.customerGateway, type: "customer-gateway" },
|
|
224
|
+
{ pattern: AWS_PATTERNS.vpnGateway, type: "vpn-gateway" },
|
|
225
|
+
{ pattern: AWS_PATTERNS.vpnConnection, type: "vpn-connection" },
|
|
226
|
+
{ pattern: AWS_PATTERNS.ecrRepoUri, type: "ecr-repo" },
|
|
192
227
|
{ pattern: AWS_PATTERNS.ami, type: "ami" },
|
|
193
228
|
{ pattern: AWS_PATTERNS.ec2Instance, type: "ec2-instance" },
|
|
194
229
|
{ pattern: AWS_PATTERNS.ebs, type: "ebs" },
|
package/package.json
CHANGED
|
@@ -1,16 +1,36 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
-
import {
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
getConfig,
|
|
4
|
+
resetConfig,
|
|
5
|
+
isPatternEnabled,
|
|
6
|
+
setFsAdapter,
|
|
7
|
+
resetFsAdapter,
|
|
8
|
+
} from "../config";
|
|
9
|
+
import * as path from "path";
|
|
10
|
+
|
|
11
|
+
const mockExistsSync = vi.fn();
|
|
12
|
+
const mockReadFileSync = vi.fn();
|
|
3
13
|
|
|
4
14
|
describe("Config", () => {
|
|
5
15
|
const originalEnv = { ...process.env };
|
|
16
|
+
const originalCwd = process.cwd;
|
|
6
17
|
|
|
7
18
|
beforeEach(() => {
|
|
8
19
|
resetConfig();
|
|
20
|
+
setFsAdapter({
|
|
21
|
+
existsSync: mockExistsSync,
|
|
22
|
+
readFileSync: mockReadFileSync,
|
|
23
|
+
});
|
|
24
|
+
mockExistsSync.mockReturnValue(false);
|
|
25
|
+
mockReadFileSync.mockReturnValue("{}");
|
|
9
26
|
});
|
|
10
27
|
|
|
11
28
|
afterEach(() => {
|
|
12
29
|
process.env = { ...originalEnv };
|
|
30
|
+
process.cwd = originalCwd;
|
|
13
31
|
resetConfig();
|
|
32
|
+
resetFsAdapter();
|
|
33
|
+
vi.clearAllMocks();
|
|
14
34
|
});
|
|
15
35
|
|
|
16
36
|
describe("getConfig", () => {
|
|
@@ -95,4 +115,77 @@ describe("Config", () => {
|
|
|
95
115
|
expect(isPatternEnabled("vpc")).toBe(true);
|
|
96
116
|
});
|
|
97
117
|
});
|
|
118
|
+
|
|
119
|
+
describe("ancestor directory config lookup", () => {
|
|
120
|
+
it("should find config in parent directory when cwd is subdirectory", () => {
|
|
121
|
+
process.cwd = () => "/project/packages/app";
|
|
122
|
+
|
|
123
|
+
mockExistsSync.mockImplementation((p: string) => {
|
|
124
|
+
return p === path.join("/project", ".wont-let-you-see.json");
|
|
125
|
+
});
|
|
126
|
+
mockReadFileSync.mockReturnValue(
|
|
127
|
+
JSON.stringify({ enabled: false, customPatterns: ["secret123"] }),
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const config = getConfig();
|
|
131
|
+
expect(config.enabled).toBe(false);
|
|
132
|
+
expect(config.customPatterns).toEqual(["secret123"]);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("should find config in grandparent directory", () => {
|
|
136
|
+
process.cwd = () => "/project/packages/app/src/components";
|
|
137
|
+
|
|
138
|
+
mockExistsSync.mockImplementation((p: string) => {
|
|
139
|
+
return p === path.join("/project", ".wont-let-you-see.json");
|
|
140
|
+
});
|
|
141
|
+
mockReadFileSync.mockReturnValue(
|
|
142
|
+
JSON.stringify({ revealedPatterns: ["ipv4"] }),
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
const config = getConfig();
|
|
146
|
+
expect(config.revealedPatterns).toEqual(["ipv4"]);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("should prefer config in closer ancestor over distant ancestor", () => {
|
|
150
|
+
process.cwd = () => "/project/packages/app";
|
|
151
|
+
|
|
152
|
+
mockExistsSync.mockImplementation((p: string) => {
|
|
153
|
+
return (
|
|
154
|
+
p === path.join("/project/packages/app", ".wont-let-you-see.json") ||
|
|
155
|
+
p === path.join("/project", ".wont-let-you-see.json")
|
|
156
|
+
);
|
|
157
|
+
});
|
|
158
|
+
mockReadFileSync.mockImplementation((p: string) => {
|
|
159
|
+
if (
|
|
160
|
+
p === path.join("/project/packages/app", ".wont-let-you-see.json")
|
|
161
|
+
) {
|
|
162
|
+
return JSON.stringify({ customPatterns: ["app-secret"] });
|
|
163
|
+
}
|
|
164
|
+
return JSON.stringify({ customPatterns: ["root-secret"] });
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
const config = getConfig();
|
|
168
|
+
expect(config.customPatterns).toEqual(["app-secret"]);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("should prefer cwd config over parent config", () => {
|
|
172
|
+
process.cwd = () => "/project/subdir";
|
|
173
|
+
|
|
174
|
+
mockExistsSync.mockImplementation((p: string) => {
|
|
175
|
+
return (
|
|
176
|
+
p === path.join("/project/subdir", ".wont-let-you-see.json") ||
|
|
177
|
+
p === path.join("/project", ".wont-let-you-see.json")
|
|
178
|
+
);
|
|
179
|
+
});
|
|
180
|
+
mockReadFileSync.mockImplementation((p: string) => {
|
|
181
|
+
if (p === path.join("/project/subdir", ".wont-let-you-see.json")) {
|
|
182
|
+
return JSON.stringify({ enabled: false });
|
|
183
|
+
}
|
|
184
|
+
return JSON.stringify({ enabled: true });
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
const config = getConfig();
|
|
188
|
+
expect(config.enabled).toBe(false);
|
|
189
|
+
});
|
|
190
|
+
});
|
|
98
191
|
});
|
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest";
|
|
2
|
-
import {
|
|
3
|
-
AWS_PATTERNS,
|
|
4
|
-
K8S_PATTERNS,
|
|
5
|
-
COMMON_PATTERNS,
|
|
6
|
-
PATTERNS,
|
|
7
|
-
} from "../patterns";
|
|
2
|
+
import { AWS_PATTERNS, K8S_PATTERNS, COMMON_PATTERNS } from "../patterns";
|
|
8
3
|
|
|
9
4
|
describe("AWS_PATTERNS", () => {
|
|
10
5
|
describe("ARN", () => {
|
|
@@ -65,6 +60,96 @@ describe("AWS_PATTERNS", () => {
|
|
|
65
60
|
});
|
|
66
61
|
});
|
|
67
62
|
|
|
63
|
+
describe("VPC networking resources", () => {
|
|
64
|
+
it("should match vpc endpoint", () => {
|
|
65
|
+
expect(AWS_PATTERNS.vpcEndpoint.test("vpce-0123456789abcdef0")).toBe(
|
|
66
|
+
true,
|
|
67
|
+
);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("should match transit gateway", () => {
|
|
71
|
+
expect(AWS_PATTERNS.transitGateway.test("tgw-0123456789abcdef0")).toBe(
|
|
72
|
+
true,
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("should match customer gateway", () => {
|
|
77
|
+
expect(AWS_PATTERNS.customerGateway.test("cgw-0123456789abcdef0")).toBe(
|
|
78
|
+
true,
|
|
79
|
+
);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("should match vpn gateway", () => {
|
|
83
|
+
expect(AWS_PATTERNS.vpnGateway.test("vgw-0123456789abcdef0")).toBe(true);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("should match vpn connection", () => {
|
|
87
|
+
expect(AWS_PATTERNS.vpnConnection.test("vpn-0123456789abcdef0")).toBe(
|
|
88
|
+
true,
|
|
89
|
+
);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("should NOT match invalid vpc endpoint", () => {
|
|
93
|
+
expect(AWS_PATTERNS.vpcEndpoint.test("vpce-invalid")).toBe(false);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe("ECR resources", () => {
|
|
98
|
+
it("should match ECR repo URI", () => {
|
|
99
|
+
expect(
|
|
100
|
+
AWS_PATTERNS.ecrRepoUri.test(
|
|
101
|
+
"123456789012.dkr.ecr.us-west-2.amazonaws.com/my-repo",
|
|
102
|
+
),
|
|
103
|
+
).toBe(true);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("should match ECR repo URI with nested path", () => {
|
|
107
|
+
expect(
|
|
108
|
+
AWS_PATTERNS.ecrRepoUri.test(
|
|
109
|
+
"123456789012.dkr.ecr.eu-central-1.amazonaws.com/org/app/service",
|
|
110
|
+
),
|
|
111
|
+
).toBe(true);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("should match ECR repo URI with dots and underscores", () => {
|
|
115
|
+
expect(
|
|
116
|
+
AWS_PATTERNS.ecrRepoUri.test(
|
|
117
|
+
"123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/my_repo.name",
|
|
118
|
+
),
|
|
119
|
+
).toBe(true);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("should NOT match invalid ECR URI (wrong account ID length)", () => {
|
|
123
|
+
expect(
|
|
124
|
+
AWS_PATTERNS.ecrRepoUri.test(
|
|
125
|
+
"12345.dkr.ecr.us-west-2.amazonaws.com/my-repo",
|
|
126
|
+
),
|
|
127
|
+
).toBe(false);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("should NOT match non-ECR docker registry", () => {
|
|
131
|
+
expect(AWS_PATTERNS.ecrRepoUri.test("docker.io/library/nginx")).toBe(
|
|
132
|
+
false,
|
|
133
|
+
);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("should match ECR repo URI with masked account ID", () => {
|
|
137
|
+
expect(
|
|
138
|
+
AWS_PATTERNS.ecrRepoUri.test(
|
|
139
|
+
"#(custom-1).dkr.ecr.us-west-2.amazonaws.com/my-repo",
|
|
140
|
+
),
|
|
141
|
+
).toBe(true);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("should match ECR repo URI with masked account ID (higher number)", () => {
|
|
145
|
+
expect(
|
|
146
|
+
AWS_PATTERNS.ecrRepoUri.test(
|
|
147
|
+
"#(custom-123).dkr.ecr.us-east-1.amazonaws.com/app/service",
|
|
148
|
+
),
|
|
149
|
+
).toBe(true);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
68
153
|
describe("Account ID (contextual)", () => {
|
|
69
154
|
it("should match OwnerId field", () => {
|
|
70
155
|
expect(AWS_PATTERNS.accountId.test('"OwnerId": "123456789012"')).toBe(
|
|
@@ -188,28 +273,18 @@ MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC
|
|
|
188
273
|
});
|
|
189
274
|
});
|
|
190
275
|
|
|
191
|
-
describe("PATTERNS (combined)", () => {
|
|
192
|
-
it("should include all AWS patterns", () => {
|
|
193
|
-
expect(PATTERNS.vpc).toBe(AWS_PATTERNS.vpc);
|
|
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);
|
|
199
|
-
});
|
|
200
|
-
|
|
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
276
|
describe("ReDoS safety", () => {
|
|
208
277
|
it("should handle pathological input quickly", () => {
|
|
209
278
|
const pathological = "a".repeat(1000) + "b";
|
|
210
279
|
const start = performance.now();
|
|
211
280
|
|
|
212
|
-
|
|
281
|
+
const allPatterns = [
|
|
282
|
+
...Object.values(AWS_PATTERNS),
|
|
283
|
+
...Object.values(K8S_PATTERNS),
|
|
284
|
+
...Object.values(COMMON_PATTERNS),
|
|
285
|
+
];
|
|
286
|
+
|
|
287
|
+
for (const pattern of allPatterns) {
|
|
213
288
|
pattern.test(pathological);
|
|
214
289
|
}
|
|
215
290
|
|
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: [],
|
|
@@ -16,19 +37,45 @@ const DEFAULT_CONFIG: Config = {
|
|
|
16
37
|
|
|
17
38
|
const CONFIG_FILENAME = ".wont-let-you-see.json";
|
|
18
39
|
|
|
19
|
-
function
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
try {
|
|
28
|
-
const content = readFileSync(configPath, "utf-8");
|
|
29
|
-
return JSON.parse(content);
|
|
30
|
-
} catch {}
|
|
40
|
+
function findConfigInAncestors(startDir: string): string | null {
|
|
41
|
+
const home = homedir();
|
|
42
|
+
let currentDir = startDir;
|
|
43
|
+
|
|
44
|
+
while (true) {
|
|
45
|
+
const configPath = join(currentDir, CONFIG_FILENAME);
|
|
46
|
+
if (fsAdapter.existsSync(configPath)) {
|
|
47
|
+
return configPath;
|
|
31
48
|
}
|
|
49
|
+
|
|
50
|
+
if (currentDir === home) {
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const parentDir = dirname(currentDir);
|
|
55
|
+
if (parentDir === currentDir) {
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
currentDir = parentDir;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function loadJsonConfig(): Partial<Config> {
|
|
65
|
+
const ancestorConfig = findConfigInAncestors(process.cwd());
|
|
66
|
+
if (ancestorConfig) {
|
|
67
|
+
try {
|
|
68
|
+
const content = fsAdapter.readFileSync(ancestorConfig, "utf-8");
|
|
69
|
+
return JSON.parse(content);
|
|
70
|
+
} catch {}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const homeConfig = join(homedir(), CONFIG_FILENAME);
|
|
74
|
+
if (fsAdapter.existsSync(homeConfig)) {
|
|
75
|
+
try {
|
|
76
|
+
const content = fsAdapter.readFileSync(homeConfig, "utf-8");
|
|
77
|
+
return JSON.parse(content);
|
|
78
|
+
} catch {}
|
|
32
79
|
}
|
|
33
80
|
|
|
34
81
|
return {};
|
package/src/masker.ts
CHANGED
|
@@ -21,6 +21,12 @@ const PATTERN_ORDER: PatternConfig[] = [
|
|
|
21
21
|
{ pattern: AWS_PATTERNS.natGateway, type: "nat-gateway" },
|
|
22
22
|
{ pattern: AWS_PATTERNS.networkAcl, type: "network-acl" },
|
|
23
23
|
{ pattern: AWS_PATTERNS.eni, type: "eni" },
|
|
24
|
+
{ pattern: AWS_PATTERNS.vpcEndpoint, type: "vpc-endpoint" },
|
|
25
|
+
{ pattern: AWS_PATTERNS.transitGateway, type: "transit-gateway" },
|
|
26
|
+
{ pattern: AWS_PATTERNS.customerGateway, type: "customer-gateway" },
|
|
27
|
+
{ pattern: AWS_PATTERNS.vpnGateway, type: "vpn-gateway" },
|
|
28
|
+
{ pattern: AWS_PATTERNS.vpnConnection, type: "vpn-connection" },
|
|
29
|
+
{ pattern: AWS_PATTERNS.ecrRepoUri, type: "ecr-repo" },
|
|
24
30
|
{ pattern: AWS_PATTERNS.ami, type: "ami" },
|
|
25
31
|
{ pattern: AWS_PATTERNS.ec2Instance, type: "ec2-instance" },
|
|
26
32
|
{ pattern: AWS_PATTERNS.ebs, type: "ebs" },
|
package/src/patterns.ts
CHANGED
|
@@ -14,7 +14,14 @@ export const AWS_PATTERNS = {
|
|
|
14
14
|
ebs: /^vol-[0-9a-f]{8,17}$/,
|
|
15
15
|
snapshot: /^snap-[0-9a-f]{8,17}$/,
|
|
16
16
|
eni: /^eni-[0-9a-f]{8,17}$/,
|
|
17
|
+
vpcEndpoint: /^vpce-[0-9a-f]{8,17}$/,
|
|
18
|
+
transitGateway: /^tgw-[0-9a-f]{8,17}$/,
|
|
19
|
+
customerGateway: /^cgw-[0-9a-f]{8,17}$/,
|
|
20
|
+
vpnGateway: /^vgw-[0-9a-f]{8,17}$/,
|
|
21
|
+
vpnConnection: /^vpn-[0-9a-f]{8,17}$/,
|
|
17
22
|
accountId: /"(?:OwnerId|AccountId|Owner|account_id)":\s*"(\d{12})"/,
|
|
23
|
+
ecrRepoUri:
|
|
24
|
+
/^(?:\d{12}|#\(custom-\d+\))\.dkr\.ecr\.[a-z0-9-]+\.amazonaws\.com\/[a-z0-9._\/-]+$/,
|
|
18
25
|
accessKeyId:
|
|
19
26
|
/(?:^|[^A-Z0-9])(?:AKIA|ABIA|ACCA|ASIA)[A-Z0-9]{16}(?:[^A-Z0-9]|$)/,
|
|
20
27
|
} as const;
|
|
@@ -36,10 +43,3 @@ export const COMMON_PATTERNS = {
|
|
|
36
43
|
apiKeyField:
|
|
37
44
|
/"(?:api_key|apiKey|secret_key|secretKey|access_token|auth_token|password|token)":\s*"([^"]+)"/,
|
|
38
45
|
} as const;
|
|
39
|
-
|
|
40
|
-
// Combined for backward compatibility
|
|
41
|
-
export const PATTERNS = {
|
|
42
|
-
...AWS_PATTERNS,
|
|
43
|
-
...K8S_PATTERNS,
|
|
44
|
-
...COMMON_PATTERNS,
|
|
45
|
-
} as const;
|