@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/src/masker.ts CHANGED
@@ -1,41 +1,7 @@
1
- import { AWS_PATTERNS, K8S_PATTERNS, COMMON_PATTERNS } from "./patterns";
1
+ import { loadPatterns, type LoadedPattern } from "./patterns";
2
2
  import { addEntry, getOriginal } from "./mapping";
3
3
  import { getConfig, isPatternEnabled } from "./config";
4
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
5
  function removeAnchors(source: string): string {
40
6
  let result = source.replace(/^\^/, "").replace(/\$$/, "");
41
7
  if (!result.endsWith("\\b")) {
@@ -44,34 +10,58 @@ function removeAnchors(source: string): string {
44
10
  return result;
45
11
  }
46
12
 
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
- }
13
+ const REGEX_PREFIX = "regex:";
56
14
 
15
+ function applyCustomPatterns(
16
+ sessionId: string,
17
+ text: string,
18
+ customPatterns: string[],
19
+ ): string {
57
20
  let result = text;
58
21
 
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);
22
+ for (const customPattern of customPatterns) {
23
+ if (customPattern.startsWith(REGEX_PREFIX)) {
24
+ const regexStr = customPattern.slice(REGEX_PREFIX.length);
25
+ const regex = new RegExp(removeAnchors(regexStr), "g");
26
+ const matches = new Set<string>();
27
+ let match;
28
+ while ((match = regex.exec(result)) !== null) {
29
+ matches.add(match[0]);
30
+ }
31
+ for (const value of matches) {
32
+ const token = addEntry(sessionId, "custom", value);
33
+ result = result.split(value).join(token);
34
+ }
35
+ } else {
36
+ const escaped = customPattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
37
+ const literalRegex = new RegExp(escaped + "(?![\\w])", "g");
38
+ if (literalRegex.test(result)) {
39
+ const token = addEntry(sessionId, "custom", customPattern);
40
+ result = result.replace(literalRegex, token);
41
+ }
63
42
  }
64
43
  }
65
44
 
66
- for (const { pattern, type, isContextual } of PATTERN_ORDER) {
67
- if (!isPatternEnabled(type)) {
45
+ return result;
46
+ }
47
+
48
+ function applyLoadedPatterns(
49
+ sessionId: string,
50
+ text: string,
51
+ patterns: LoadedPattern[],
52
+ ): string {
53
+ let result = text;
54
+
55
+ for (const { name, pattern, isContextual } of patterns) {
56
+ if (!isPatternEnabled(name)) {
68
57
  continue;
69
58
  }
59
+
70
60
  if (isContextual) {
71
61
  result = result.replace(
72
62
  new RegExp(pattern.source, "g"),
73
63
  (match, capturedValue) => {
74
- const token = addEntry(sessionId, type, capturedValue);
64
+ const token = addEntry(sessionId, name, capturedValue);
75
65
  return match.replace(capturedValue, token);
76
66
  },
77
67
  );
@@ -85,7 +75,7 @@ export function mask(sessionId: string, text: string): string {
85
75
  }
86
76
 
87
77
  for (const value of matches) {
88
- const token = addEntry(sessionId, type, value);
78
+ const token = addEntry(sessionId, name, value);
89
79
  result = result.split(value).join(token);
90
80
  }
91
81
  }
@@ -94,6 +84,26 @@ export function mask(sessionId: string, text: string): string {
94
84
  return result;
95
85
  }
96
86
 
87
+ export function mask(sessionId: string, text: string): string {
88
+ if (!text || typeof text !== "string") {
89
+ return text;
90
+ }
91
+
92
+ const config = getConfig();
93
+ if (!config.enabled) {
94
+ return text;
95
+ }
96
+
97
+ let result = text;
98
+
99
+ result = applyCustomPatterns(sessionId, result, config.customPatterns);
100
+
101
+ const patterns = loadPatterns();
102
+ result = applyLoadedPatterns(sessionId, result, patterns);
103
+
104
+ return result;
105
+ }
106
+
97
107
  export function unmask(sessionId: string, text: string): string {
98
108
  if (!text || typeof text !== "string") {
99
109
  return text;
package/src/patterns.ts CHANGED
@@ -1,45 +1,108 @@
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;
1
+ import { readdirSync, readFileSync } from "fs";
2
+ import { join, dirname } from "path";
3
+ import { fileURLToPath } from "url";
4
+
5
+ /**
6
+ * Pattern definition in JSON files.
7
+ * Can be:
8
+ * - A regex string: "^vpc-[0-9a-f]{8,17}$"
9
+ * - An object with pattern and optional contextual flag: { "pattern": "...", "contextual": true }
10
+ * - An object with exact string match: { "exact": "literal-value" }
11
+ */
12
+ export type PatternDefinition =
13
+ | string
14
+ | { pattern: string; contextual?: boolean }
15
+ | { exact: string };
16
+
17
+ export interface PatternFile {
18
+ [patternName: string]: PatternDefinition;
19
+ }
20
+
21
+ export interface LoadedPattern {
22
+ name: string;
23
+ pattern: RegExp;
24
+ isContextual: boolean;
25
+ isExact: boolean;
26
+ }
27
+
28
+ let cachedPatterns: LoadedPattern[] | null = null;
29
+
30
+ function getPackagePatternsDir(): string {
31
+ const currentFile = fileURLToPath(import.meta.url);
32
+ const srcDir = dirname(currentFile);
33
+ const packageRoot = dirname(srcDir);
34
+ return join(packageRoot, "patterns");
35
+ }
36
+
37
+ function parsePatternDefinition(
38
+ name: string,
39
+ definition: PatternDefinition,
40
+ ): LoadedPattern {
41
+ if (typeof definition === "string") {
42
+ return {
43
+ name,
44
+ pattern: new RegExp(definition),
45
+ isContextual: false,
46
+ isExact: false,
47
+ };
48
+ }
49
+
50
+ if ("exact" in definition) {
51
+ const escaped = definition.exact.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
52
+ return {
53
+ name,
54
+ pattern: new RegExp(escaped),
55
+ isContextual: false,
56
+ isExact: true,
57
+ };
58
+ }
59
+
60
+ return {
61
+ name,
62
+ pattern: new RegExp(definition.pattern),
63
+ isContextual: definition.contextual ?? false,
64
+ isExact: false,
65
+ };
66
+ }
67
+
68
+ function loadPatternFile(filePath: string): LoadedPattern[] {
69
+ const content = readFileSync(filePath, "utf-8");
70
+ const data: PatternFile = JSON.parse(content);
71
+ const patterns: LoadedPattern[] = [];
72
+
73
+ for (const [name, definition] of Object.entries(data)) {
74
+ patterns.push(parsePatternDefinition(name, definition));
75
+ }
76
+
77
+ return patterns;
78
+ }
79
+
80
+ export function loadPatterns(): LoadedPattern[] {
81
+ if (cachedPatterns) {
82
+ return cachedPatterns;
83
+ }
84
+
85
+ const patternsDir = getPackagePatternsDir();
86
+ const patterns: LoadedPattern[] = [];
87
+
88
+ const files = readdirSync(patternsDir)
89
+ .filter((f) => f.endsWith(".json"))
90
+ .sort();
91
+
92
+ for (const file of files) {
93
+ const filePath = join(patternsDir, file);
94
+ const filePatterns = loadPatternFile(filePath);
95
+ patterns.push(...filePatterns);
96
+ }
97
+
98
+ cachedPatterns = patterns;
99
+ return patterns;
100
+ }
101
+
102
+ export function resetPatternCache(): void {
103
+ cachedPatterns = null;
104
+ }
105
+
106
+ export function getPatternByName(name: string): LoadedPattern | undefined {
107
+ return loadPatterns().find((p) => p.name === name);
108
+ }