@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
package/src/masker.ts
CHANGED
|
@@ -1,41 +1,7 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
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
|
|
60
|
-
if (
|
|
61
|
-
const
|
|
62
|
-
|
|
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
|
-
|
|
67
|
-
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
+
}
|