@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,218 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import {
3
+ AWS_PATTERNS,
4
+ K8S_PATTERNS,
5
+ COMMON_PATTERNS,
6
+ PATTERNS,
7
+ } from "../patterns";
8
+
9
+ describe("AWS_PATTERNS", () => {
10
+ describe("ARN", () => {
11
+ it("should match aws partition", () => {
12
+ expect(
13
+ AWS_PATTERNS.arn.test("arn:aws:iam::123456789012:user/admin"),
14
+ ).toBe(true);
15
+ });
16
+
17
+ it("should match aws-cn partition", () => {
18
+ expect(AWS_PATTERNS.arn.test("arn:aws-cn:s3:::my-bucket")).toBe(true);
19
+ });
20
+
21
+ it("should match aws-us-gov partition", () => {
22
+ expect(
23
+ AWS_PATTERNS.arn.test(
24
+ "arn:aws-us-gov:ec2:us-gov-west-1:123456789012:instance/i-123",
25
+ ),
26
+ ).toBe(true);
27
+ });
28
+ });
29
+
30
+ describe("VPC resources", () => {
31
+ it("should match vpc", () => {
32
+ expect(AWS_PATTERNS.vpc.test("vpc-0123456789abcdef0")).toBe(true);
33
+ });
34
+
35
+ it("should match subnet", () => {
36
+ expect(AWS_PATTERNS.subnet.test("subnet-0123456789abcdef0")).toBe(true);
37
+ });
38
+
39
+ it("should match security group", () => {
40
+ expect(AWS_PATTERNS.securityGroup.test("sg-0123456789abcdef0")).toBe(
41
+ true,
42
+ );
43
+ });
44
+
45
+ it("should match nat gateway", () => {
46
+ expect(AWS_PATTERNS.natGateway.test("nat-0123456789abcdef0")).toBe(true);
47
+ });
48
+
49
+ it("should match network acl", () => {
50
+ expect(AWS_PATTERNS.networkAcl.test("acl-0123456789abcdef0")).toBe(true);
51
+ });
52
+
53
+ it("should match eni", () => {
54
+ expect(AWS_PATTERNS.eni.test("eni-0123456789abcdef0")).toBe(true);
55
+ });
56
+ });
57
+
58
+ describe("EC2 resources", () => {
59
+ it("should match ebs volume", () => {
60
+ expect(AWS_PATTERNS.ebs.test("vol-0123456789abcdef0")).toBe(true);
61
+ });
62
+
63
+ it("should match snapshot", () => {
64
+ expect(AWS_PATTERNS.snapshot.test("snap-0123456789abcdef0")).toBe(true);
65
+ });
66
+ });
67
+
68
+ describe("Account ID (contextual)", () => {
69
+ it("should match OwnerId field", () => {
70
+ expect(AWS_PATTERNS.accountId.test('"OwnerId": "123456789012"')).toBe(
71
+ true,
72
+ );
73
+ });
74
+
75
+ it("should match account_id field (terraform style)", () => {
76
+ expect(AWS_PATTERNS.accountId.test('"account_id": "123456789012"')).toBe(
77
+ true,
78
+ );
79
+ });
80
+
81
+ it("should NOT match bare number", () => {
82
+ expect(AWS_PATTERNS.accountId.test("123456789012")).toBe(false);
83
+ });
84
+ });
85
+
86
+ describe("Access Key ID", () => {
87
+ it("should match AKIA prefix", () => {
88
+ expect(AWS_PATTERNS.accessKeyId.test(" AKIAIOSFODNN7EXAMPLE ")).toBe(
89
+ true,
90
+ );
91
+ });
92
+
93
+ it("should match ASIA prefix (temporary)", () => {
94
+ expect(AWS_PATTERNS.accessKeyId.test(" ASIAISAMPLEKEYID1234 ")).toBe(
95
+ true,
96
+ );
97
+ });
98
+
99
+ it("should NOT match random string", () => {
100
+ expect(AWS_PATTERNS.accessKeyId.test("RANDOMSTRING12345678")).toBe(false);
101
+ });
102
+ });
103
+ });
104
+
105
+ describe("K8S_PATTERNS", () => {
106
+ describe("Service Account Token", () => {
107
+ it("should match JWT format", () => {
108
+ const jwt =
109
+ "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImRlZmF1bHQtdG9rZW4tYWJjZGUiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiZGVmYXVsdCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjEyMzQ1Njc4LTEyMzQtMTIzNC0xMjM0LTEyMzQ1Njc4OTAxMiIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZWZhdWx0OmRlZmF1bHQifQ.signature";
110
+ expect(K8S_PATTERNS.serviceAccountToken.test(jwt)).toBe(true);
111
+ });
112
+ });
113
+
114
+ describe("Node Names", () => {
115
+ it("should match AWS node name", () => {
116
+ expect(
117
+ K8S_PATTERNS.nodeNameAws.test(
118
+ "ip-10-0-1-123.us-west-2.compute.internal",
119
+ ),
120
+ ).toBe(true);
121
+ });
122
+ });
123
+
124
+ describe("Cluster Endpoints", () => {
125
+ it("should match EKS endpoint", () => {
126
+ expect(
127
+ K8S_PATTERNS.clusterEndpoint.test(
128
+ "https://ABCDEF1234567890ABCD.us-west-2.eks.amazonaws.com",
129
+ ),
130
+ ).toBe(true);
131
+ });
132
+ });
133
+ });
134
+
135
+ describe("COMMON_PATTERNS", () => {
136
+ describe("IPv4", () => {
137
+ it("should match IPv4", () => {
138
+ expect(COMMON_PATTERNS.ipv4.test("192.168.1.1")).toBe(true);
139
+ });
140
+
141
+ it("should not match CIDR notation", () => {
142
+ expect(COMMON_PATTERNS.ipv4.test("10.0.0.0/8")).toBe(false);
143
+ });
144
+ });
145
+
146
+ describe("Private Key", () => {
147
+ const rsaKey = `-----BEGIN RSA PRIVATE KEY-----
148
+ MIIEpAIBAAKCAQEA0Z3VS5JJcds3xfn/ygWyF8PbnGy0AHB7MxszR0vOo
149
+ -----END RSA PRIVATE KEY-----`;
150
+
151
+ const genericKey = `-----BEGIN PRIVATE KEY-----
152
+ MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC
153
+ -----END PRIVATE KEY-----`;
154
+
155
+ it("should match entire RSA private key block", () => {
156
+ expect(COMMON_PATTERNS.privateKey.test(rsaKey)).toBe(true);
157
+ });
158
+
159
+ it("should match entire generic private key block", () => {
160
+ expect(COMMON_PATTERNS.privateKey.test(genericKey)).toBe(true);
161
+ });
162
+
163
+ it("should not match header only", () => {
164
+ expect(
165
+ COMMON_PATTERNS.privateKey.test("-----BEGIN RSA PRIVATE KEY-----"),
166
+ ).toBe(false);
167
+ });
168
+ });
169
+
170
+ describe("API Key Field (contextual)", () => {
171
+ it("should match api_key field", () => {
172
+ expect(
173
+ COMMON_PATTERNS.apiKeyField.test('"api_key": "sk-1234567890abcdef"'),
174
+ ).toBe(true);
175
+ });
176
+
177
+ it("should match password field", () => {
178
+ expect(
179
+ COMMON_PATTERNS.apiKeyField.test('"password": "supersecret123"'),
180
+ ).toBe(true);
181
+ });
182
+
183
+ it("should match token field", () => {
184
+ expect(
185
+ COMMON_PATTERNS.apiKeyField.test('"token": "ghp_xxxxxxxxxxxx"'),
186
+ ).toBe(true);
187
+ });
188
+ });
189
+ });
190
+
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
+ describe("ReDoS safety", () => {
208
+ it("should handle pathological input quickly", () => {
209
+ const pathological = "a".repeat(1000) + "b";
210
+ const start = performance.now();
211
+
212
+ for (const pattern of Object.values(PATTERNS)) {
213
+ pattern.test(pathological);
214
+ }
215
+
216
+ expect(performance.now() - start).toBeLessThan(100);
217
+ });
218
+ });
package/src/config.ts ADDED
@@ -0,0 +1,93 @@
1
+ import { existsSync, readFileSync } from "fs";
2
+ import { join } from "path";
3
+ import { homedir } from "os";
4
+
5
+ export interface Config {
6
+ enabled: boolean;
7
+ revealedPatterns: string[];
8
+ customPatterns: string[];
9
+ }
10
+
11
+ const DEFAULT_CONFIG: Config = {
12
+ enabled: true,
13
+ revealedPatterns: [],
14
+ customPatterns: [],
15
+ };
16
+
17
+ const CONFIG_FILENAME = ".wont-let-you-see.json";
18
+
19
+ function loadJsonConfig(): Partial<Config> {
20
+ const paths = [
21
+ join(process.cwd(), CONFIG_FILENAME),
22
+ join(homedir(), CONFIG_FILENAME),
23
+ ];
24
+
25
+ for (const configPath of paths) {
26
+ if (existsSync(configPath)) {
27
+ try {
28
+ const content = readFileSync(configPath, "utf-8");
29
+ return JSON.parse(content);
30
+ } catch {}
31
+ }
32
+ }
33
+
34
+ return {};
35
+ }
36
+
37
+ function loadEnvConfig(): Partial<Config> {
38
+ const config: Partial<Config> = {};
39
+
40
+ const enabled = process.env.WONT_LET_YOU_SEE_ENABLED;
41
+ if (enabled !== undefined) {
42
+ config.enabled = enabled.toLowerCase() !== "false" && enabled !== "0";
43
+ }
44
+
45
+ const revealedPatterns = process.env.WONT_LET_YOU_SEE_REVEALED_PATTERNS;
46
+ if (revealedPatterns !== undefined) {
47
+ config.revealedPatterns = revealedPatterns
48
+ .split(",")
49
+ .map((p) => p.trim())
50
+ .filter(Boolean);
51
+ }
52
+
53
+ const customPatterns = process.env.WONT_LET_YOU_SEE_CUSTOM_PATTERNS;
54
+ if (customPatterns !== undefined) {
55
+ config.customPatterns = customPatterns
56
+ .split(",")
57
+ .map((p) => p.trim())
58
+ .filter(Boolean);
59
+ }
60
+
61
+ return config;
62
+ }
63
+
64
+ let cachedConfig: Config | null = null;
65
+
66
+ export function getConfig(): Config {
67
+ if (cachedConfig) {
68
+ return cachedConfig;
69
+ }
70
+
71
+ const jsonConfig = loadJsonConfig();
72
+ const envConfig = loadEnvConfig();
73
+
74
+ cachedConfig = {
75
+ ...DEFAULT_CONFIG,
76
+ ...jsonConfig,
77
+ ...envConfig,
78
+ };
79
+
80
+ return cachedConfig;
81
+ }
82
+
83
+ export function resetConfig(): void {
84
+ cachedConfig = null;
85
+ }
86
+
87
+ export function isPatternEnabled(patternType: string): boolean {
88
+ const config = getConfig();
89
+ if (!config.enabled) {
90
+ return false;
91
+ }
92
+ return !config.revealedPatterns.includes(patternType);
93
+ }
package/src/index.ts ADDED
@@ -0,0 +1,44 @@
1
+ import type { Plugin, Hooks, PluginInput } from "@opencode-ai/plugin";
2
+ import { unmask, mask } from "./masker";
3
+
4
+ const INFRA_COMMAND_PATTERN = /\b(aws|terraform|kubectl|helm)\s/;
5
+
6
+ export const plugin: Plugin = async (input: PluginInput): Promise<Hooks> => {
7
+ const infraCommands = new Map<string, boolean>();
8
+
9
+ return {
10
+ "tool.execute.before": async (hookInput, output) => {
11
+ if (hookInput.tool !== "bash") {
12
+ return;
13
+ }
14
+
15
+ const command = output.args.command;
16
+ if (!INFRA_COMMAND_PATTERN.test(command)) {
17
+ return;
18
+ }
19
+
20
+ infraCommands.set(hookInput.callID, true);
21
+ output.args.command = unmask(hookInput.sessionID, command);
22
+ },
23
+
24
+ "tool.execute.after": async (hookInput, output) => {
25
+ if (hookInput.tool !== "bash") {
26
+ return;
27
+ }
28
+
29
+ if (!infraCommands.get(hookInput.callID)) {
30
+ return;
31
+ }
32
+
33
+ output.output = mask(hookInput.sessionID, output.output);
34
+ },
35
+
36
+ "chat.message": async (hookInput, output) => {
37
+ for (const part of output.parts) {
38
+ if (part.type === "text" && part.text) {
39
+ part.text = mask(hookInput.sessionID, part.text);
40
+ }
41
+ }
42
+ },
43
+ };
44
+ };
package/src/mapping.ts ADDED
@@ -0,0 +1,161 @@
1
+ import {
2
+ existsSync,
3
+ mkdirSync,
4
+ readFileSync,
5
+ writeFileSync,
6
+ renameSync,
7
+ } from "fs";
8
+ import { join } from "path";
9
+ import { homedir } from "os";
10
+
11
+ export interface MappingTable {
12
+ version: number;
13
+ entries: Record<string, string>;
14
+ }
15
+
16
+ interface SessionState {
17
+ mapping: MappingTable;
18
+ counters: Record<string, number>;
19
+ }
20
+
21
+ const sessionStates = new Map<string, SessionState>();
22
+
23
+ export function getSessionPath(sessionId: string): string {
24
+ const projectRoot = process.cwd();
25
+ const defaultPath = join(
26
+ projectRoot,
27
+ ".opencode",
28
+ "sessions",
29
+ sessionId,
30
+ "wont-let-you-see-mapping.json",
31
+ );
32
+
33
+ if (existsSync(join(projectRoot, ".opencode")) || !existsSync(homedir())) {
34
+ return defaultPath;
35
+ }
36
+
37
+ return join(
38
+ homedir(),
39
+ ".opencode",
40
+ "sessions",
41
+ sessionId,
42
+ "wont-let-you-see-mapping.json",
43
+ );
44
+ }
45
+
46
+ function ensureSessionDir(sessionPath: string): void {
47
+ const dir = sessionPath.substring(0, sessionPath.lastIndexOf("/"));
48
+ if (!existsSync(dir)) {
49
+ mkdirSync(dir, { recursive: true });
50
+ }
51
+ }
52
+
53
+ function getSessionState(sessionId: string): SessionState {
54
+ let state = sessionStates.get(sessionId);
55
+
56
+ if (!state) {
57
+ const sessionPath = getSessionPath(sessionId);
58
+
59
+ if (existsSync(sessionPath)) {
60
+ const mapping = JSON.parse(
61
+ readFileSync(sessionPath, "utf-8"),
62
+ ) as MappingTable;
63
+ const counters: Record<string, number> = {};
64
+
65
+ for (const token of Object.keys(mapping.entries)) {
66
+ const match = token.match(/^#\(([^-]+)-(\d+)\)$/);
67
+ if (match && match[1] && match[2]) {
68
+ const type = match[1];
69
+ const num = match[2];
70
+ counters[type] = Math.max(counters[type] || 0, parseInt(num, 10));
71
+ }
72
+ }
73
+
74
+ state = { mapping, counters };
75
+ } else {
76
+ state = {
77
+ mapping: { version: 1, entries: {} },
78
+ counters: {},
79
+ };
80
+ }
81
+
82
+ sessionStates.set(sessionId, state);
83
+ }
84
+
85
+ return state;
86
+ }
87
+
88
+ export function createMapping(sessionId: string): MappingTable {
89
+ const state: SessionState = {
90
+ mapping: { version: 1, entries: {} },
91
+ counters: {},
92
+ };
93
+
94
+ sessionStates.set(sessionId, state);
95
+ saveMapping(sessionId);
96
+
97
+ return state.mapping;
98
+ }
99
+
100
+ export function loadMapping(sessionId: string): MappingTable {
101
+ const state = getSessionState(sessionId);
102
+ return state.mapping;
103
+ }
104
+
105
+ export function saveMapping(sessionId: string): void {
106
+ const state = getSessionState(sessionId);
107
+ const sessionPath = getSessionPath(sessionId);
108
+
109
+ ensureSessionDir(sessionPath);
110
+
111
+ const tempPath = `${sessionPath}.tmp`;
112
+ writeFileSync(tempPath, JSON.stringify(state.mapping, null, 2), "utf-8");
113
+ renameSync(tempPath, sessionPath);
114
+ }
115
+
116
+ export function addEntry(
117
+ sessionId: string,
118
+ type: string,
119
+ originalValue: string,
120
+ ): string {
121
+ const state = getSessionState(sessionId);
122
+
123
+ for (const [token, value] of Object.entries(state.mapping.entries)) {
124
+ if (value === originalValue) {
125
+ return token;
126
+ }
127
+ }
128
+
129
+ const counter = (state.counters[type] || 0) + 1;
130
+ state.counters[type] = counter;
131
+
132
+ const token = `#(${type}-${counter})`;
133
+ state.mapping.entries[token] = originalValue;
134
+
135
+ saveMapping(sessionId);
136
+
137
+ return token;
138
+ }
139
+
140
+ export function getOriginal(
141
+ sessionId: string,
142
+ token: string,
143
+ ): string | undefined {
144
+ const state = getSessionState(sessionId);
145
+ return state.mapping.entries[token];
146
+ }
147
+
148
+ export function getToken(
149
+ sessionId: string,
150
+ originalValue: string,
151
+ ): string | undefined {
152
+ const state = getSessionState(sessionId);
153
+
154
+ for (const [token, value] of Object.entries(state.mapping.entries)) {
155
+ if (value === originalValue) {
156
+ return token;
157
+ }
158
+ }
159
+
160
+ return undefined;
161
+ }
package/src/masker.ts ADDED
@@ -0,0 +1,122 @@
1
+ import { AWS_PATTERNS, K8S_PATTERNS, COMMON_PATTERNS } from "./patterns";
2
+ import { addEntry, getOriginal } from "./mapping";
3
+ import { getConfig, isPatternEnabled } from "./config";
4
+
5
+ interface PatternConfig {
6
+ pattern: RegExp;
7
+ type: string;
8
+ isContextual?: boolean;
9
+ }
10
+
11
+ const PATTERN_ORDER: PatternConfig[] = [
12
+ { pattern: AWS_PATTERNS.eksCluster, type: "eks-cluster" },
13
+ { pattern: AWS_PATTERNS.arn, type: "arn" },
14
+ { pattern: AWS_PATTERNS.accountId, type: "account-id", isContextual: true },
15
+ { pattern: AWS_PATTERNS.accessKeyId, type: "access-key-id" },
16
+ { pattern: AWS_PATTERNS.vpc, type: "vpc" },
17
+ { pattern: AWS_PATTERNS.subnet, type: "subnet" },
18
+ { pattern: AWS_PATTERNS.securityGroup, type: "security-group" },
19
+ { pattern: AWS_PATTERNS.internetGateway, type: "internet-gateway" },
20
+ { pattern: AWS_PATTERNS.routeTable, type: "route-table" },
21
+ { pattern: AWS_PATTERNS.natGateway, type: "nat-gateway" },
22
+ { pattern: AWS_PATTERNS.networkAcl, type: "network-acl" },
23
+ { pattern: AWS_PATTERNS.eni, type: "eni" },
24
+ { pattern: AWS_PATTERNS.ami, type: "ami" },
25
+ { pattern: AWS_PATTERNS.ec2Instance, type: "ec2-instance" },
26
+ { pattern: AWS_PATTERNS.ebs, type: "ebs" },
27
+ { pattern: AWS_PATTERNS.snapshot, type: "snapshot" },
28
+
29
+ { pattern: K8S_PATTERNS.serviceAccountToken, type: "k8s-token" },
30
+ { pattern: K8S_PATTERNS.clusterEndpoint, type: "k8s-endpoint" },
31
+ { pattern: K8S_PATTERNS.kubeconfigServer, type: "k8s-endpoint" },
32
+ { pattern: K8S_PATTERNS.nodeNameAws, type: "k8s-node" },
33
+
34
+ { pattern: COMMON_PATTERNS.privateKey, type: "private-key" },
35
+ { pattern: COMMON_PATTERNS.apiKeyField, type: "api-key", isContextual: true },
36
+ { pattern: COMMON_PATTERNS.ipv4, type: "ipv4" },
37
+ ];
38
+
39
+ function removeAnchors(source: string): string {
40
+ let result = source.replace(/^\^/, "").replace(/\$$/, "");
41
+ if (!result.endsWith("\\b")) {
42
+ result += "\\b";
43
+ }
44
+ return result;
45
+ }
46
+
47
+ export function mask(sessionId: string, text: string): string {
48
+ if (!text || typeof text !== "string") {
49
+ return text;
50
+ }
51
+
52
+ const config = getConfig();
53
+ if (!config.enabled) {
54
+ return text;
55
+ }
56
+
57
+ let result = text;
58
+
59
+ for (const customPattern of config.customPatterns) {
60
+ if (result.includes(customPattern)) {
61
+ const token = addEntry(sessionId, "custom", customPattern);
62
+ result = result.split(customPattern).join(token);
63
+ }
64
+ }
65
+
66
+ for (const { pattern, type, isContextual } of PATTERN_ORDER) {
67
+ if (!isPatternEnabled(type)) {
68
+ continue;
69
+ }
70
+ if (isContextual) {
71
+ result = result.replace(
72
+ new RegExp(pattern.source, "g"),
73
+ (match, capturedValue) => {
74
+ const token = addEntry(sessionId, type, capturedValue);
75
+ return match.replace(capturedValue, token);
76
+ },
77
+ );
78
+ } else {
79
+ const matches = new Set<string>();
80
+ const globalPattern = new RegExp(removeAnchors(pattern.source), "g");
81
+
82
+ let match;
83
+ while ((match = globalPattern.exec(result)) !== null) {
84
+ matches.add(match[0]);
85
+ }
86
+
87
+ for (const value of matches) {
88
+ const token = addEntry(sessionId, type, value);
89
+ result = result.split(value).join(token);
90
+ }
91
+ }
92
+ }
93
+
94
+ return result;
95
+ }
96
+
97
+ export function unmask(sessionId: string, text: string): string {
98
+ if (!text || typeof text !== "string") {
99
+ return text;
100
+ }
101
+
102
+ let result = text;
103
+ const tokenPattern = /#\([^)]+\)/g;
104
+ const tokens = new Set<string>();
105
+
106
+ let match;
107
+ while ((match = tokenPattern.exec(text)) !== null) {
108
+ tokens.add(match[0]);
109
+ }
110
+
111
+ for (const token of tokens) {
112
+ const original = getOriginal(sessionId, token);
113
+
114
+ if (original === undefined) {
115
+ console.warn(`Unknown token: ${token}`);
116
+ } else {
117
+ result = result.split(token).join(original);
118
+ }
119
+ }
120
+
121
+ return result;
122
+ }
@@ -0,0 +1,45 @@
1
+ export const AWS_PATTERNS = {
2
+ arn: /^arn:(?:aws|aws-cn|aws-us-gov):[a-zA-Z0-9-]+:[a-z0-9-]*:(?:[0-9]{12})?:.+$/,
3
+ eksCluster:
4
+ /^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:
19
+ /(?:^|[^A-Z0-9])(?:AKIA|ABIA|ACCA|ASIA)[A-Z0-9]{16}(?:[^A-Z0-9]|$)/,
20
+ } as const;
21
+
22
+ export const K8S_PATTERNS = {
23
+ serviceAccountToken: /^eyJ[A-Za-z0-9_-]*\.eyJ[A-Za-z0-9_-]*\.[A-Za-z0-9_-]*$/,
24
+ nodeNameAws: /^ip-(?:\d{1,3}-){3}\d{1,3}\.[a-z0-9-]+\.compute\.internal$/,
25
+ clusterEndpoint: /^https:\/\/[A-Z0-9]+\.[a-z0-9-]+\.eks\.amazonaws\.com$/,
26
+ kubeconfigServer:
27
+ /^https:\/\/[0-9A-F]{32}\.[a-z]{2}-[a-z]+-\d\.eks\.amazonaws\.com$/i,
28
+ } as const;
29
+
30
+ export const COMMON_PATTERNS = {
31
+ 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]?)$/,
32
+ // Private key blocks (entire key including body and footer)
33
+ privateKey:
34
+ /-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/,
35
+ // Generic API keys/tokens (contextual - in JSON/YAML fields)
36
+ apiKeyField:
37
+ /"(?:api_key|apiKey|secret_key|secretKey|access_token|auth_token|password|token)":\s*"([^"]+)"/,
38
+ } as const;
39
+
40
+ // Combined for backward compatibility
41
+ export const PATTERNS = {
42
+ ...AWS_PATTERNS,
43
+ ...K8S_PATTERNS,
44
+ ...COMMON_PATTERNS,
45
+ } as const;