@valoir/rizz-brain 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/dist/index.d.ts +474 -0
- package/dist/index.js +13064 -0
- package/dist/sensitivity.d.ts +17 -0
- package/dist/sensitivity.js +223 -0
- package/package.json +22 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface SensitivePathClassification {
|
|
2
|
+
readonly isSensitive: boolean;
|
|
3
|
+
readonly redactedId: string;
|
|
4
|
+
readonly label: string;
|
|
5
|
+
readonly reason: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function normalizeSensitivePath(value: string): string;
|
|
8
|
+
export declare function redactedSensitiveReference(value: string): string;
|
|
9
|
+
export declare function classifySensitivePath(value: string): SensitivePathClassification;
|
|
10
|
+
export declare function shouldOmitSensitivePath(value: string): boolean;
|
|
11
|
+
export declare function redactSecretValues(value: string): string;
|
|
12
|
+
export declare function redactSensitiveText(value: string): string;
|
|
13
|
+
export declare function sensitiveIdentityKey(value: string): string;
|
|
14
|
+
export declare function redactedReferenceCount(value: unknown): number;
|
|
15
|
+
export declare function containsSensitiveReference(value: unknown): boolean;
|
|
16
|
+
export declare function unredactedSensitiveReferenceCount(value: unknown): number;
|
|
17
|
+
//# sourceMappingURL=sensitivity.d.ts.map
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
const REDACTED_PREFIX = 'redacted:sensitive-file:';
|
|
3
|
+
const SECRET_VALUE_PATTERNS = [
|
|
4
|
+
/\bsk-or-v1-[a-z0-9]{16,}\b/gi,
|
|
5
|
+
/\bsk-[a-z0-9][a-z0-9_-]{8,}\b/gi,
|
|
6
|
+
/\bgh[pousr]_[a-z0-9_]{20,}\b/gi,
|
|
7
|
+
/\bBearer\s+[a-z0-9._~+/-]+=*/gi,
|
|
8
|
+
];
|
|
9
|
+
const PRIVATE_FILE_NAMES = new Set([
|
|
10
|
+
'.env',
|
|
11
|
+
'.npmrc',
|
|
12
|
+
'.netrc',
|
|
13
|
+
'credentials',
|
|
14
|
+
'id_dsa',
|
|
15
|
+
'id_ecdsa',
|
|
16
|
+
'id_ed25519',
|
|
17
|
+
'id_rsa',
|
|
18
|
+
'known_hosts',
|
|
19
|
+
]);
|
|
20
|
+
const PRIVATE_EXTENSIONS = new Set(['.pem', '.key', '.cert', '.crt', '.cer', '.p12', '.pfx']);
|
|
21
|
+
const PUBLIC_SECURITY_TERMS = new Set([
|
|
22
|
+
'secret-safe',
|
|
23
|
+
'secret-safe reliability',
|
|
24
|
+
'secret_safe_reliability',
|
|
25
|
+
]);
|
|
26
|
+
const SENSITIVE_SEGMENT_PATTERN = /(^|[-_.])(secret|secrets|credential|credentials|token|tokens|password|passwords|passwd|client_secret|service-account|private-key)([-_.]|$)/i;
|
|
27
|
+
const PRIVATE_ABSOLUTE_PATH_PATTERN = /^(?:\/Users\/|\/home\/|\/private\/|\/tmp\/|\/var\/folders\/)/i;
|
|
28
|
+
const SENSITIVE_TEXT_CANDIDATE = /(?:\/Users\/[^\s"'<>]+|\/home\/[^\s"'<>]+|\/private\/[^\s"'<>]+|\/tmp\/[^\s"'<>]+|\/var\/folders\/[^\s"'<>]+|(?:[A-Za-z0-9@._~+:-]+\/)*[A-Za-z0-9@._~+:-]*(?:sk-or-v1-[A-Za-z0-9]+|secret|secrets|credential|credentials|token|tokens|password|passwords|passwd|client_secret|service-account|private-key|id_rsa|id_dsa|id_ecdsa|id_ed25519|\.env(?:\.[A-Za-z0-9_-]+)?|\.npmrc|\.netrc|[A-Za-z0-9_.-]+\.(?:pem|key|cert|crt|cer|p12|pfx))[A-Za-z0-9@._~+:-]*)/gi;
|
|
29
|
+
const TRAILING_CANDIDATE_PUNCTUATION = /[),.;!?]+$/;
|
|
30
|
+
const LEADING_CANDIDATE_PUNCTUATION = /^[([{'"`]+/;
|
|
31
|
+
export function normalizeSensitivePath(value) {
|
|
32
|
+
return value.trim().replace(/\\/g, '/').replace(/^\.\//, '').replace(/\/+/g, '/');
|
|
33
|
+
}
|
|
34
|
+
function hashSensitiveValue(value) {
|
|
35
|
+
return createHash('sha256').update(normalizeSensitivePath(value)).digest('hex').slice(0, 12);
|
|
36
|
+
}
|
|
37
|
+
export function redactedSensitiveReference(value) {
|
|
38
|
+
return `${REDACTED_PREFIX}${hashSensitiveValue(value)}`;
|
|
39
|
+
}
|
|
40
|
+
function emptyClassification(value) {
|
|
41
|
+
return {
|
|
42
|
+
isSensitive: false,
|
|
43
|
+
redactedId: redactedSensitiveReference(value),
|
|
44
|
+
label: value,
|
|
45
|
+
reason: 'not sensitive',
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function hasSecretLikeValue(value) {
|
|
49
|
+
return SECRET_VALUE_PATTERNS.some((pattern) => {
|
|
50
|
+
pattern.lastIndex = 0;
|
|
51
|
+
return pattern.test(value);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
function extensionOf(name) {
|
|
55
|
+
const index = name.lastIndexOf('.');
|
|
56
|
+
return index <= 0 ? '' : name.slice(index).toLowerCase();
|
|
57
|
+
}
|
|
58
|
+
function splitCandidate(value) {
|
|
59
|
+
const prefix = value.match(LEADING_CANDIDATE_PUNCTUATION)?.[0] ?? '';
|
|
60
|
+
const withoutPrefix = value.slice(prefix.length);
|
|
61
|
+
const suffix = withoutPrefix.match(TRAILING_CANDIDATE_PUNCTUATION)?.[0] ?? '';
|
|
62
|
+
return {
|
|
63
|
+
prefix,
|
|
64
|
+
candidate: withoutPrefix.slice(0, withoutPrefix.length - suffix.length),
|
|
65
|
+
suffix,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function isLikelyPathOrFileName(value) {
|
|
69
|
+
const normalized = normalizeSensitivePath(value);
|
|
70
|
+
if (normalized === '' || normalized.includes(REDACTED_PREFIX))
|
|
71
|
+
return false;
|
|
72
|
+
if (isPublicSecurityTerm(normalized))
|
|
73
|
+
return false;
|
|
74
|
+
if (PRIVATE_ABSOLUTE_PATH_PATTERN.test(normalized))
|
|
75
|
+
return true;
|
|
76
|
+
if (normalized.includes('/'))
|
|
77
|
+
return true;
|
|
78
|
+
const leaf = normalized
|
|
79
|
+
.split('/')
|
|
80
|
+
.filter((segment) => segment !== '')
|
|
81
|
+
.at(-1) ?? normalized;
|
|
82
|
+
const lowerLeaf = leaf.toLowerCase();
|
|
83
|
+
if (lowerLeaf === '.env.example')
|
|
84
|
+
return true;
|
|
85
|
+
if (PRIVATE_FILE_NAMES.has(lowerLeaf))
|
|
86
|
+
return true;
|
|
87
|
+
if (lowerLeaf.startsWith('.env.'))
|
|
88
|
+
return true;
|
|
89
|
+
if (PRIVATE_EXTENSIONS.has(extensionOf(lowerLeaf)))
|
|
90
|
+
return true;
|
|
91
|
+
if (hasSecretLikeValue(normalized) && extensionOf(lowerLeaf) !== '')
|
|
92
|
+
return true;
|
|
93
|
+
if (extensionOf(lowerLeaf) !== '' && SENSITIVE_SEGMENT_PATTERN.test(lowerLeaf))
|
|
94
|
+
return true;
|
|
95
|
+
return SENSITIVE_SEGMENT_PATTERN.test(lowerLeaf) && /[-_.]/.test(lowerLeaf);
|
|
96
|
+
}
|
|
97
|
+
function isPublicSecurityTerm(value) {
|
|
98
|
+
return PUBLIC_SECURITY_TERMS.has(normalizeSensitivePath(value).toLowerCase());
|
|
99
|
+
}
|
|
100
|
+
export function classifySensitivePath(value) {
|
|
101
|
+
const normalized = normalizeSensitivePath(value);
|
|
102
|
+
if (normalized === '' || normalized.includes(REDACTED_PREFIX)) {
|
|
103
|
+
return emptyClassification(normalized);
|
|
104
|
+
}
|
|
105
|
+
if (isPublicSecurityTerm(normalized))
|
|
106
|
+
return emptyClassification(normalized);
|
|
107
|
+
const segments = normalized.split('/').filter((segment) => segment !== '');
|
|
108
|
+
const leaf = segments[segments.length - 1] ?? normalized;
|
|
109
|
+
const lowerLeaf = leaf.toLowerCase();
|
|
110
|
+
const lowerSegments = segments.map((segment) => segment.toLowerCase());
|
|
111
|
+
const redactedId = redactedSensitiveReference(normalized);
|
|
112
|
+
const sensitive = (reason) => ({
|
|
113
|
+
isSensitive: true,
|
|
114
|
+
redactedId,
|
|
115
|
+
label: `sensitive file redacted (${redactedId})`,
|
|
116
|
+
reason,
|
|
117
|
+
});
|
|
118
|
+
if (hasSecretLikeValue(normalized))
|
|
119
|
+
return sensitive('secret-like token in path');
|
|
120
|
+
if (PRIVATE_ABSOLUTE_PATH_PATTERN.test(normalized))
|
|
121
|
+
return sensitive('private absolute path');
|
|
122
|
+
if (lowerLeaf === '.env.example')
|
|
123
|
+
return emptyClassification(normalized);
|
|
124
|
+
if (PRIVATE_FILE_NAMES.has(lowerLeaf))
|
|
125
|
+
return sensitive(`private filename ${lowerLeaf}`);
|
|
126
|
+
if (lowerLeaf.startsWith('.env.'))
|
|
127
|
+
return sensitive('private environment filename');
|
|
128
|
+
if (PRIVATE_EXTENSIONS.has(extensionOf(lowerLeaf)))
|
|
129
|
+
return sensitive('private key or certificate filename');
|
|
130
|
+
if (lowerSegments.includes('.aws') && lowerLeaf === 'credentials') {
|
|
131
|
+
return sensitive('cloud credential path');
|
|
132
|
+
}
|
|
133
|
+
if (lowerSegments.some((segment) => SENSITIVE_SEGMENT_PATTERN.test(segment))) {
|
|
134
|
+
return sensitive('sensitive path segment');
|
|
135
|
+
}
|
|
136
|
+
return emptyClassification(normalized);
|
|
137
|
+
}
|
|
138
|
+
export function shouldOmitSensitivePath(value) {
|
|
139
|
+
const normalized = normalizeSensitivePath(value);
|
|
140
|
+
const leaf = normalized
|
|
141
|
+
.split('/')
|
|
142
|
+
.filter((segment) => segment !== '')
|
|
143
|
+
.at(-1) ?? normalized;
|
|
144
|
+
const lowerLeaf = leaf.toLowerCase();
|
|
145
|
+
if (lowerLeaf === '.env.example')
|
|
146
|
+
return false;
|
|
147
|
+
if (PRIVATE_FILE_NAMES.has(lowerLeaf))
|
|
148
|
+
return true;
|
|
149
|
+
if (lowerLeaf.startsWith('.env.'))
|
|
150
|
+
return true;
|
|
151
|
+
if (PRIVATE_EXTENSIONS.has(extensionOf(lowerLeaf)))
|
|
152
|
+
return true;
|
|
153
|
+
if (normalized.toLowerCase().endsWith('/.aws/credentials'))
|
|
154
|
+
return true;
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
export function redactSecretValues(value) {
|
|
158
|
+
return SECRET_VALUE_PATTERNS.reduce((text, pattern) => text.replace(pattern, (match) => match.startsWith('Bearer ') ? 'Bearer [redacted secret]' : '[redacted secret]'), value);
|
|
159
|
+
}
|
|
160
|
+
export function redactSensitiveText(value) {
|
|
161
|
+
if (value.startsWith(REDACTED_PREFIX))
|
|
162
|
+
return value;
|
|
163
|
+
const normalized = normalizeSensitivePath(value);
|
|
164
|
+
if (!/\s/.test(normalized)) {
|
|
165
|
+
if (isPublicSecurityTerm(normalized))
|
|
166
|
+
return value;
|
|
167
|
+
const classification = classifySensitivePath(normalized);
|
|
168
|
+
if (classification.isSensitive)
|
|
169
|
+
return classification.redactedId;
|
|
170
|
+
}
|
|
171
|
+
const pathRedacted = value.replace(SENSITIVE_TEXT_CANDIDATE, (match, offset, text) => {
|
|
172
|
+
if (match.startsWith(REDACTED_PREFIX))
|
|
173
|
+
return match;
|
|
174
|
+
const { prefix, candidate, suffix } = splitCandidate(match);
|
|
175
|
+
const nextCharacter = text.slice(offset + match.length, offset + match.length + 1);
|
|
176
|
+
const previousText = text.slice(Math.max(0, offset - 16), offset);
|
|
177
|
+
if (nextCharacter === '=' && !candidate.includes('/'))
|
|
178
|
+
return match;
|
|
179
|
+
if (/\bBearer\s+$/i.test(previousText))
|
|
180
|
+
return match;
|
|
181
|
+
if (!isLikelyPathOrFileName(candidate))
|
|
182
|
+
return match;
|
|
183
|
+
const matchClassification = classifySensitivePath(candidate);
|
|
184
|
+
return matchClassification.isSensitive
|
|
185
|
+
? `${prefix}${matchClassification.redactedId}${suffix}`
|
|
186
|
+
: match;
|
|
187
|
+
});
|
|
188
|
+
return redactSecretValues(pathRedacted);
|
|
189
|
+
}
|
|
190
|
+
export function sensitiveIdentityKey(value) {
|
|
191
|
+
const classification = classifySensitivePath(value);
|
|
192
|
+
return classification.isSensitive ? classification.redactedId : normalizeSensitivePath(value);
|
|
193
|
+
}
|
|
194
|
+
export function redactedReferenceCount(value) {
|
|
195
|
+
if (typeof value === 'string') {
|
|
196
|
+
const matches = value.match(new RegExp(REDACTED_PREFIX.replace(/[|\\{}()[\]^$+?.]/g, '\\$&'), 'g'));
|
|
197
|
+
const directSensitive = isLikelyPathOrFileName(value) && classifySensitivePath(value).isSensitive ? 1 : 0;
|
|
198
|
+
return (matches?.length ?? 0) + directSensitive;
|
|
199
|
+
}
|
|
200
|
+
if (Array.isArray(value)) {
|
|
201
|
+
return value.reduce((count, item) => count + redactedReferenceCount(item), 0);
|
|
202
|
+
}
|
|
203
|
+
if (value !== null && typeof value === 'object') {
|
|
204
|
+
return Object.entries(value).reduce((count, [key, item]) => count + redactedReferenceCount(key) + redactedReferenceCount(item), 0);
|
|
205
|
+
}
|
|
206
|
+
return 0;
|
|
207
|
+
}
|
|
208
|
+
export function containsSensitiveReference(value) {
|
|
209
|
+
return redactedReferenceCount(value) > 0;
|
|
210
|
+
}
|
|
211
|
+
export function unredactedSensitiveReferenceCount(value) {
|
|
212
|
+
if (typeof value === 'string') {
|
|
213
|
+
return isLikelyPathOrFileName(value) && classifySensitivePath(value).isSensitive ? 1 : 0;
|
|
214
|
+
}
|
|
215
|
+
if (Array.isArray(value)) {
|
|
216
|
+
return value.reduce((count, item) => count + unredactedSensitiveReferenceCount(item), 0);
|
|
217
|
+
}
|
|
218
|
+
if (value !== null && typeof value === 'object') {
|
|
219
|
+
return Object.entries(value).reduce((count, [key, item]) => count + unredactedSensitiveReferenceCount(key) + unredactedSensitiveReferenceCount(item), 0);
|
|
220
|
+
}
|
|
221
|
+
return 0;
|
|
222
|
+
}
|
|
223
|
+
//# sourceMappingURL=sensitivity.js.map
|
package/package.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@valoir/rizz-brain",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Project Intelligence Engine and local knowledge-store artifact generator for Rizz.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"files": [
|
|
11
|
+
"dist/**/*.js",
|
|
12
|
+
"dist/**/*.d.ts",
|
|
13
|
+
"!dist/**/*.test.*",
|
|
14
|
+
"package.json"
|
|
15
|
+
],
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsc -b"
|
|
21
|
+
}
|
|
22
|
+
}
|