@mnemonik/shared 7.144.2 → 7.145.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/ast/grammars.d.ts +6 -6
- package/dist/ast/grammars.d.ts.map +1 -1
- package/dist/ast/grammars.js +51 -10
- package/dist/ast/grammars.js.map +1 -1
- package/dist/sourceRedaction.d.ts.map +1 -1
- package/dist/sourceRedaction.js +743 -64
- package/dist/sourceRedaction.js.map +1 -1
- package/package.json +2 -1
- package/queries/clojure.tags.scm +1 -0
- package/queries/dart.tags.scm +3 -0
- package/queries/erlang.tags.scm +2 -0
- package/queries/graphql.tags.scm +3 -0
- package/queries/haskell.tags.scm +3 -0
- package/queries/julia.tags.scm +1 -0
- package/queries/kotlin.tags.scm +3 -0
- package/queries/ocaml.tags.scm +2 -0
- package/queries/ocaml_interface.tags.scm +1 -0
- package/queries/perl.tags.scm +2 -0
- package/queries/protobuf.tags.scm +5 -0
- package/queries/r.tags.scm +2 -0
- package/queries/zig.tags.scm +2 -0
- package/scripts/vendor-grammars.ts +551 -35
- package/src/ast/grammars.ts +51 -10
- package/src/sourceRedaction.ts +765 -12
- package/wasm/bash.wasm.gz +0 -0
- package/wasm/c-sharp.wasm.gz +0 -0
- package/wasm/c.wasm.gz +0 -0
- package/wasm/clojure.LICENSE +21 -0
- package/wasm/clojure.tags.scm +1 -0
- package/wasm/clojure.wasm.gz +0 -0
- package/wasm/cmake.wasm.gz +0 -0
- package/wasm/cpp.wasm.gz +0 -0
- package/wasm/dart.LICENSE +20 -0
- package/wasm/dart.tags.scm +3 -0
- package/wasm/dart.wasm.gz +0 -0
- package/wasm/elixir.wasm.gz +0 -0
- package/wasm/erlang.LICENSE +201 -0
- package/wasm/erlang.tags.scm +2 -0
- package/wasm/erlang.wasm.gz +0 -0
- package/wasm/go.wasm.gz +0 -0
- package/wasm/graphql.LICENSE +21 -0
- package/wasm/graphql.tags.scm +3 -0
- package/wasm/graphql.wasm.gz +0 -0
- package/wasm/groovy.wasm.gz +0 -0
- package/wasm/haskell.LICENSE +21 -0
- package/wasm/haskell.tags.scm +3 -0
- package/wasm/haskell.wasm.gz +0 -0
- package/wasm/java.wasm.gz +0 -0
- package/wasm/javascript.wasm.gz +0 -0
- package/wasm/julia.LICENSE +21 -0
- package/wasm/julia.tags.scm +1 -0
- package/wasm/julia.wasm.gz +0 -0
- package/wasm/kotlin.LICENSE +9 -0
- package/wasm/kotlin.tags.scm +3 -0
- package/wasm/kotlin.wasm.gz +0 -0
- package/wasm/lua.wasm.gz +0 -0
- package/wasm/nix.wasm.gz +0 -0
- package/wasm/objcpp.wasm.gz +0 -0
- package/wasm/ocaml.LICENSE +21 -0
- package/wasm/ocaml.tags.scm +2 -0
- package/wasm/ocaml.wasm.gz +0 -0
- package/wasm/ocaml_interface.LICENSE +21 -0
- package/wasm/ocaml_interface.tags.scm +1 -0
- package/wasm/ocaml_interface.wasm.gz +0 -0
- package/wasm/perl.LICENSE +7 -0
- package/wasm/perl.tags.scm +2 -0
- package/wasm/perl.wasm.gz +0 -0
- package/wasm/php.wasm.gz +0 -0
- package/wasm/powershell.wasm.gz +0 -0
- package/wasm/protobuf.LICENSE +21 -0
- package/wasm/protobuf.tags.scm +5 -0
- package/wasm/protobuf.wasm.gz +0 -0
- package/wasm/python.wasm.gz +0 -0
- package/wasm/r.LICENSE +9 -0
- package/wasm/r.tags.scm +2 -0
- package/wasm/r.wasm.gz +0 -0
- package/wasm/ruby.wasm.gz +0 -0
- package/wasm/rust.wasm.gz +0 -0
- package/wasm/scala.wasm.gz +0 -0
- package/wasm/solidity.wasm.gz +0 -0
- package/wasm/sql.wasm.gz +0 -0
- package/wasm/swift.wasm.gz +0 -0
- package/wasm/terraform.wasm.gz +0 -0
- package/wasm/tsx.wasm.gz +0 -0
- package/wasm/typescript.wasm.gz +0 -0
- package/wasm/zig.LICENSE +29 -0
- package/wasm/zig.tags.scm +2 -0
- package/wasm/zig.wasm.gz +0 -0
- package/wasm/bash.wasm +0 -0
- package/wasm/c-sharp.wasm +0 -0
- package/wasm/c.wasm +0 -0
- package/wasm/cmake.wasm +0 -0
- package/wasm/cpp.wasm +0 -0
- package/wasm/elixir.wasm +0 -0
- package/wasm/go.wasm +0 -0
- package/wasm/groovy.wasm +0 -0
- package/wasm/java.wasm +0 -0
- package/wasm/javascript.wasm +0 -0
- package/wasm/lua.wasm +0 -0
- package/wasm/nix.wasm +0 -0
- package/wasm/objcpp.wasm +0 -0
- package/wasm/php.wasm +0 -0
- package/wasm/powershell.wasm +0 -0
- package/wasm/python.wasm +0 -0
- package/wasm/ruby.wasm +0 -0
- package/wasm/rust.wasm +0 -0
- package/wasm/scala.wasm +0 -0
- package/wasm/solidity.wasm +0 -0
- package/wasm/sql.wasm +0 -0
- package/wasm/swift.wasm +0 -0
- package/wasm/terraform.wasm +0 -0
- package/wasm/tsx.wasm +0 -0
- package/wasm/typescript.wasm +0 -0
package/dist/sourceRedaction.js
CHANGED
|
@@ -13,45 +13,103 @@ export async function scrubSourceSecrets(text, filePath) {
|
|
|
13
13
|
const extension = extname(filePath).toLowerCase();
|
|
14
14
|
if (extension === '.vue')
|
|
15
15
|
return scrubVueSource(text);
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
16
|
+
if (extension === '.svelte')
|
|
17
|
+
return scrubSvelteSource(text);
|
|
18
|
+
const bundledLanguages = {
|
|
19
|
+
'.go': 'go',
|
|
20
|
+
'.java': 'java',
|
|
21
|
+
'.cs': 'csharp',
|
|
22
|
+
'.scala': 'scala',
|
|
23
|
+
'.sc': 'scala',
|
|
24
|
+
'.rb': 'ruby',
|
|
25
|
+
'.php': 'php',
|
|
26
|
+
'.ex': 'elixir',
|
|
27
|
+
'.exs': 'elixir',
|
|
28
|
+
'.sol': 'solidity',
|
|
29
|
+
'.kt': 'kotlin',
|
|
30
|
+
'.kts': 'kotlin',
|
|
31
|
+
'.dart': 'dart',
|
|
32
|
+
'.erl': 'erlang',
|
|
33
|
+
'.hrl': 'erlang',
|
|
34
|
+
'.hs': 'haskell',
|
|
35
|
+
'.ml': 'ocaml',
|
|
36
|
+
'.mli': 'ocaml_interface',
|
|
37
|
+
'.clj': 'clojure',
|
|
38
|
+
'.cljs': 'clojure',
|
|
39
|
+
'.cljc': 'clojure',
|
|
40
|
+
'.jl': 'julia',
|
|
41
|
+
'.pl': 'perl',
|
|
42
|
+
'.pm': 'perl',
|
|
43
|
+
'.r': 'r',
|
|
44
|
+
'.zig': 'zig',
|
|
45
|
+
'.proto': 'protobuf',
|
|
46
|
+
'.graphql': 'graphql',
|
|
47
|
+
'.gql': 'graphql',
|
|
48
|
+
};
|
|
49
|
+
const bundledLanguage = bundledLanguages[extension];
|
|
50
|
+
const language = bundledLanguage ??
|
|
51
|
+
(extension === '.cmake' || basename(filePath) === 'CMakeLists.txt'
|
|
52
|
+
? 'cmake'
|
|
53
|
+
: ['.ts', '.mts', '.cts'].includes(extension)
|
|
54
|
+
? 'typescript'
|
|
55
|
+
: extension === '.tsx'
|
|
56
|
+
? 'tsx'
|
|
57
|
+
: ['.js', '.jsx', '.mjs', '.cjs'].includes(extension)
|
|
58
|
+
? 'javascript'
|
|
59
|
+
: ['.py', '.pyi', '.bzl'].includes(extension)
|
|
60
|
+
? 'python'
|
|
61
|
+
: ['.sh', '.bash', '.zsh'].includes(extension)
|
|
62
|
+
? 'bash'
|
|
63
|
+
: extension === '.swift'
|
|
64
|
+
? 'swift'
|
|
65
|
+
: extension === '.sql'
|
|
66
|
+
? 'sql'
|
|
67
|
+
: extension === '.lua'
|
|
68
|
+
? 'lua'
|
|
69
|
+
: extension === '.rs'
|
|
70
|
+
? 'rust'
|
|
71
|
+
: extension === '.nix'
|
|
72
|
+
? 'nix'
|
|
73
|
+
: extension === '.tf'
|
|
74
|
+
? 'terraform'
|
|
75
|
+
: ['.m', '.mm'].includes(extension)
|
|
76
|
+
? 'objcpp'
|
|
77
|
+
: extension === '.c'
|
|
78
|
+
? 'c'
|
|
79
|
+
: ['.h', '.cpp', '.cc', '.cxx', '.hpp', '.hh', '.hxx'].includes(extension)
|
|
80
|
+
? 'cpp'
|
|
81
|
+
: ['.groovy', '.gradle'].includes(extension)
|
|
82
|
+
? 'groovy'
|
|
83
|
+
: ['.ps1', '.psm1'].includes(extension)
|
|
84
|
+
? 'powershell'
|
|
85
|
+
: null);
|
|
86
|
+
const fallback = (source = text, payloadsScrubbed = false) => {
|
|
87
|
+
// A completed AST payload pass must not be reinterpreted as prose (e.g. proto field numbers).
|
|
88
|
+
const clean = payloadsScrubbed ? source : scrubPayload(source);
|
|
53
89
|
if (!text)
|
|
54
90
|
return clean;
|
|
91
|
+
if (bundledLanguage) {
|
|
92
|
+
const marker = `/* mnemonik:${bundledLanguage}-source-fidelity-unavailable */`;
|
|
93
|
+
const nativeMarker = {
|
|
94
|
+
erlang: `% mnemonik:${bundledLanguage}-source-fidelity-unavailable`,
|
|
95
|
+
haskell: `-- mnemonik:${bundledLanguage}-source-fidelity-unavailable`,
|
|
96
|
+
ocaml: `(* mnemonik:${bundledLanguage}-source-fidelity-unavailable *)`,
|
|
97
|
+
ocaml_interface: `(* mnemonik:${bundledLanguage}-source-fidelity-unavailable *)`,
|
|
98
|
+
clojure: `; mnemonik:${bundledLanguage}-source-fidelity-unavailable`,
|
|
99
|
+
zig: `// mnemonik:${bundledLanguage}-source-fidelity-unavailable`,
|
|
100
|
+
};
|
|
101
|
+
const comment = nativeMarker[bundledLanguage] ??
|
|
102
|
+
(['ruby', 'elixir', 'julia', 'perl', 'r', 'graphql'].includes(bundledLanguage)
|
|
103
|
+
? marker.replace('/* ', '# ').replace(' */', '')
|
|
104
|
+
: bundledLanguage === 'php'
|
|
105
|
+
? `<?php ${marker} ?>`
|
|
106
|
+
: marker);
|
|
107
|
+
return clean.startsWith(comment) ? clean : comment + '\n' + clean;
|
|
108
|
+
}
|
|
109
|
+
if (extension === '.bzl') {
|
|
110
|
+
const marker = '# mnemonik:starlark-source-fidelity-unavailable\n';
|
|
111
|
+
return clean.startsWith(marker) ? clean : marker + clean;
|
|
112
|
+
}
|
|
55
113
|
if (language === 'powershell') {
|
|
56
114
|
const marker = '# mnemonik:powershell-source-fidelity-unavailable\n';
|
|
57
115
|
return clean.startsWith(marker) ? clean : marker + clean;
|
|
@@ -81,35 +139,55 @@ export async function scrubSourceSecrets(text, filePath) {
|
|
|
81
139
|
parser = new Parser();
|
|
82
140
|
parser.setLanguage(grammar.ok.language);
|
|
83
141
|
tree = parser.parse(text);
|
|
84
|
-
if (!tree
|
|
142
|
+
if (!tree)
|
|
85
143
|
return fallback();
|
|
144
|
+
if (tree.rootNode.hasError) {
|
|
145
|
+
// These bindings have no prose-style credential '=' label. Recover AST-proven payloads;
|
|
146
|
+
// opaque ERROR spans are scrubbed conservatively and always remain unavailable.
|
|
147
|
+
return bundledLanguage && ['clojure', 'r', 'protobuf', 'graphql'].includes(bundledLanguage)
|
|
148
|
+
? fallback(redactBundledNode(tree.rootNode, bundledLanguage, true), true)
|
|
149
|
+
: fallback();
|
|
150
|
+
}
|
|
86
151
|
const root = tree.rootNode;
|
|
87
152
|
if (language === 'objcpp' && objcppSyntaxIssue(root))
|
|
88
153
|
return fallback();
|
|
89
|
-
const redacted =
|
|
90
|
-
?
|
|
91
|
-
: language === '
|
|
92
|
-
?
|
|
93
|
-
: language === '
|
|
94
|
-
?
|
|
95
|
-
: language === '
|
|
96
|
-
?
|
|
97
|
-
: language === '
|
|
98
|
-
?
|
|
99
|
-
: language === '
|
|
100
|
-
?
|
|
101
|
-
: language === '
|
|
102
|
-
?
|
|
103
|
-
: language === '
|
|
104
|
-
?
|
|
105
|
-
:
|
|
106
|
-
?
|
|
107
|
-
:
|
|
108
|
-
?
|
|
109
|
-
: language === '
|
|
110
|
-
?
|
|
111
|
-
:
|
|
112
|
-
|
|
154
|
+
const redacted = bundledLanguage
|
|
155
|
+
? redactBundledNode(root, bundledLanguage)
|
|
156
|
+
: language === 'cmake'
|
|
157
|
+
? redactCmakeNode(root)
|
|
158
|
+
: language === 'bash'
|
|
159
|
+
? redactShellNode(root, commandLineCredentialRanges(text))
|
|
160
|
+
: language === 'swift'
|
|
161
|
+
? redactSwiftNode(root)
|
|
162
|
+
: language === 'sql'
|
|
163
|
+
? redactSqlNode(root)
|
|
164
|
+
: language === 'lua'
|
|
165
|
+
? redactLuaNode(root)
|
|
166
|
+
: language === 'rust'
|
|
167
|
+
? redactRustNode(root)
|
|
168
|
+
: language === 'nix'
|
|
169
|
+
? redactNixNode(root)
|
|
170
|
+
: language === 'terraform'
|
|
171
|
+
? redactTerraformNode(root)
|
|
172
|
+
: ['objcpp', 'c', 'cpp'].includes(language)
|
|
173
|
+
? redactCFamilyNode(root)
|
|
174
|
+
: language === 'groovy'
|
|
175
|
+
? redactGroovyNode(root)
|
|
176
|
+
: language === 'powershell'
|
|
177
|
+
? redactPowershellNode(root)
|
|
178
|
+
: redactNode(root);
|
|
179
|
+
const clean = text.slice(0, root.startIndex) + redacted + text.slice(root.endIndex);
|
|
180
|
+
if (bundledLanguage && clean !== text) {
|
|
181
|
+
const proof = parser.parse(clean);
|
|
182
|
+
try {
|
|
183
|
+
if (!proof || proof.rootNode.hasError)
|
|
184
|
+
return fallback(clean);
|
|
185
|
+
}
|
|
186
|
+
finally {
|
|
187
|
+
proof?.delete();
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return clean;
|
|
113
191
|
}
|
|
114
192
|
catch {
|
|
115
193
|
return fallback();
|
|
@@ -119,6 +197,453 @@ export async function scrubSourceSecrets(text, filePath) {
|
|
|
119
197
|
parser?.delete();
|
|
120
198
|
}
|
|
121
199
|
}
|
|
200
|
+
/** Bundled L1 grammars expose literal payloads separately from executable expressions. */
|
|
201
|
+
function redactBundledNode(root, language, incomplete = false) {
|
|
202
|
+
const labeled = new Set();
|
|
203
|
+
const heredocs = [];
|
|
204
|
+
const label = (node) => {
|
|
205
|
+
if (!node)
|
|
206
|
+
return false;
|
|
207
|
+
if (language === 'julia' && node.type === 'typed_expression')
|
|
208
|
+
return label(node.firstNamedChild);
|
|
209
|
+
if (language === 'perl' && node.type === 'variable_declaration')
|
|
210
|
+
return label(node.childForFieldName('variable'));
|
|
211
|
+
if (language === 'perl' && node.type === 'scalar')
|
|
212
|
+
return label(node.firstNamedChild);
|
|
213
|
+
if (language === 'kotlin' &&
|
|
214
|
+
['navigation_expression', 'directly_assignable_expression'].includes(node.type)) {
|
|
215
|
+
const last = node.lastNamedChild;
|
|
216
|
+
return label(last?.type === 'navigation_suffix' ? last.firstNamedChild : node.firstNamedChild);
|
|
217
|
+
}
|
|
218
|
+
if (language === 'dart' && node.type === 'assignable_expression') {
|
|
219
|
+
const last = node.lastNamedChild;
|
|
220
|
+
return label(last?.type === 'unconditional_assignable_selector'
|
|
221
|
+
? last.firstNamedChild
|
|
222
|
+
: node.firstNamedChild);
|
|
223
|
+
}
|
|
224
|
+
if ([
|
|
225
|
+
'expression',
|
|
226
|
+
'literal_element',
|
|
227
|
+
'variable_name',
|
|
228
|
+
'assignable_expression',
|
|
229
|
+
'directly_assignable_expression',
|
|
230
|
+
].includes(node.type))
|
|
231
|
+
return label(node.firstNamedChild);
|
|
232
|
+
const field = node.childForFieldName('field') ??
|
|
233
|
+
node.childForFieldName('member') ??
|
|
234
|
+
node.childForFieldName('property') ??
|
|
235
|
+
node.childForFieldName('name') ??
|
|
236
|
+
node;
|
|
237
|
+
return SECRET_LABEL_RE.test(field.text
|
|
238
|
+
.trim()
|
|
239
|
+
.replace(/^[$@]+|["':]+$/g, '')
|
|
240
|
+
.replace(/^["']/, ''));
|
|
241
|
+
};
|
|
242
|
+
const mark = (key, value) => {
|
|
243
|
+
if (!key || !value)
|
|
244
|
+
return;
|
|
245
|
+
if (label(key))
|
|
246
|
+
labeled.add(value.id);
|
|
247
|
+
else if (key.type === 'tuple_pattern' && value.type === 'tuple_expression')
|
|
248
|
+
key.namedChildren.forEach((part, i) => mark(part, value.namedChildren[i] ?? null));
|
|
249
|
+
};
|
|
250
|
+
const payload = (text, sensitive) => scrubPayload(text)
|
|
251
|
+
.split(/(\r\n|[\r\n])/)
|
|
252
|
+
.map((part, i) => i % 2
|
|
253
|
+
? part
|
|
254
|
+
: part.replace(/^(\s*)([\s\S]*?)(\s*)$/, (_match, lead, body, tail) => lead + literalPayload(body, sensitive) + tail))
|
|
255
|
+
.join('');
|
|
256
|
+
const visit = (node, sensitive = false) => {
|
|
257
|
+
sensitive ||= labeled.has(node.id);
|
|
258
|
+
const original = node.text;
|
|
259
|
+
const type = node.type;
|
|
260
|
+
if (incomplete && type === 'ERROR')
|
|
261
|
+
return redactSecretValue(original);
|
|
262
|
+
if ([
|
|
263
|
+
'comment',
|
|
264
|
+
'line_comment',
|
|
265
|
+
'block_comment',
|
|
266
|
+
'documentation_comment',
|
|
267
|
+
'multiline_comment',
|
|
268
|
+
].includes(type)) {
|
|
269
|
+
if (language === 'clojure') {
|
|
270
|
+
if (original.startsWith(';'))
|
|
271
|
+
return ';' + scrubPayload(original.slice(1));
|
|
272
|
+
if (original.startsWith('#!'))
|
|
273
|
+
return '#!' + scrubPayload(original.slice(2));
|
|
274
|
+
// Reader-discard and (comment ...) contain forms, not line-comment payloads.
|
|
275
|
+
let clean = '';
|
|
276
|
+
let offset = node.startIndex;
|
|
277
|
+
for (const child of node.namedChildren) {
|
|
278
|
+
clean +=
|
|
279
|
+
original.slice(offset - node.startIndex, child.startIndex - node.startIndex) +
|
|
280
|
+
visit(child);
|
|
281
|
+
offset = child.endIndex;
|
|
282
|
+
}
|
|
283
|
+
return clean + original.slice(offset - node.startIndex);
|
|
284
|
+
}
|
|
285
|
+
if (language === 'erlang')
|
|
286
|
+
return '%' + scrubPayload(original.slice(1));
|
|
287
|
+
if (language === 'haskell')
|
|
288
|
+
return original.startsWith('{-')
|
|
289
|
+
? '{-' + scrubPayload(original.slice(2, -2)) + '-}'
|
|
290
|
+
: '--' + scrubPayload(original.slice(2));
|
|
291
|
+
if (language === 'ocaml' || language === 'ocaml_interface')
|
|
292
|
+
return '(*' + scrubPayload(original.slice(2, -2)) + '*)';
|
|
293
|
+
if (original.startsWith('#'))
|
|
294
|
+
return '#' + scrubPayload(original.slice(1));
|
|
295
|
+
return original.startsWith('/*')
|
|
296
|
+
? '/*' + scrubPayload(original.slice(2, -2)) + '*/'
|
|
297
|
+
: '//' + scrubPayload(original.slice(2));
|
|
298
|
+
}
|
|
299
|
+
if (['erlang', 'haskell', 'ocaml', 'ocaml_interface', 'clojure'].includes(language) &&
|
|
300
|
+
['char', 'character'].includes(type)) {
|
|
301
|
+
if (sensitive || scrubPayload(original) !== original)
|
|
302
|
+
throw new Error('Restricted literal payload');
|
|
303
|
+
return original;
|
|
304
|
+
}
|
|
305
|
+
if ((language === 'ocaml' || language === 'ocaml_interface') && type === 'quoted_string') {
|
|
306
|
+
const opening = /^\{([a-z_]*)\|/.exec(original);
|
|
307
|
+
const closing = opening ? '|' + opening[1] + '}' : '';
|
|
308
|
+
if (!opening || !original.endsWith(closing))
|
|
309
|
+
throw new Error('Opaque quoted literal boundary');
|
|
310
|
+
return (opening[0] +
|
|
311
|
+
payload(original.slice(opening[0].length, -closing.length), sensitive) +
|
|
312
|
+
closing);
|
|
313
|
+
}
|
|
314
|
+
if (language === 'perl' && type === 'interpolated_string_literal') {
|
|
315
|
+
const content = node.childForFieldName('content');
|
|
316
|
+
return content
|
|
317
|
+
? original.slice(0, content.startIndex - node.startIndex) +
|
|
318
|
+
visit(content, sensitive) +
|
|
319
|
+
original.slice(content.endIndex - node.startIndex)
|
|
320
|
+
: original;
|
|
321
|
+
}
|
|
322
|
+
if (language === 'perl' && type === 'string_content') {
|
|
323
|
+
let clean = '';
|
|
324
|
+
let offset = node.startIndex;
|
|
325
|
+
for (const child of node.namedChildren.filter((c) => !['escape_sequence', 'escaped_delimiter'].includes(c.type))) {
|
|
326
|
+
clean +=
|
|
327
|
+
payload(original.slice(offset - node.startIndex, child.startIndex - node.startIndex), sensitive) + visit(child);
|
|
328
|
+
offset = child.endIndex;
|
|
329
|
+
}
|
|
330
|
+
return clean + payload(original.slice(offset - node.startIndex), sensitive);
|
|
331
|
+
}
|
|
332
|
+
if (type === 'heredoc_beginning') {
|
|
333
|
+
heredocs.push(sensitive);
|
|
334
|
+
return original;
|
|
335
|
+
}
|
|
336
|
+
if (language === 'ruby' && type === 'heredoc_body')
|
|
337
|
+
sensitive = heredocs.shift() ?? false;
|
|
338
|
+
if (type === 'hex_string_literal')
|
|
339
|
+
return sensitive
|
|
340
|
+
? original.replace(/(["'])([0-9a-fA-F_\s]*)\1/g, (_match, quote, body) => quote + body.replace(/[0-9a-fA-F_]/g, '') + quote)
|
|
341
|
+
: original;
|
|
342
|
+
// shortcut: restricted character/symbol literals use fallback; add native placeholders if needed.
|
|
343
|
+
if (['character_literal', 'rune_literal', 'simple_symbol', 'atom'].includes(type)) {
|
|
344
|
+
if (sensitive || scrubPayload(original) !== original)
|
|
345
|
+
throw new Error('Restricted literal payload');
|
|
346
|
+
return original;
|
|
347
|
+
}
|
|
348
|
+
if (sensitive &&
|
|
349
|
+
/^(?:int|float|imaginary|integer|real|decimal_integer|hex_integer|octal_integer|binary_integer|decimal_floating_point|hex_floating_point|number)(?:_literal)?$/.test(type))
|
|
350
|
+
return '"' + redactSecretValue(original) + '"';
|
|
351
|
+
const string = [
|
|
352
|
+
'string',
|
|
353
|
+
'interpreted_string_literal',
|
|
354
|
+
'raw_string_literal',
|
|
355
|
+
'verbatim_string_literal',
|
|
356
|
+
'encapsed_string',
|
|
357
|
+
'unicode_string_literal',
|
|
358
|
+
'string_value',
|
|
359
|
+
'interpolated_string',
|
|
360
|
+
'sigil',
|
|
361
|
+
].includes(type) ||
|
|
362
|
+
(type === 'string_literal' && language !== 'solidity');
|
|
363
|
+
if (string) {
|
|
364
|
+
const first = node.firstChild;
|
|
365
|
+
const last = node.lastChild;
|
|
366
|
+
const quotedStart = node.childForFieldName('quoted_start') ??
|
|
367
|
+
(language === 'r' ? node.childForFieldName('open') : null);
|
|
368
|
+
const quotedEnd = node.childForFieldName('quoted_end') ??
|
|
369
|
+
(language === 'r' ? node.childForFieldName('close') : null);
|
|
370
|
+
const opening = quotedStart ?? first;
|
|
371
|
+
const closing = quotedEnd ?? last;
|
|
372
|
+
const match = /^(?:r|@|unicode)?("{3,}|'{3,}|"|'|`)/.exec(original);
|
|
373
|
+
const start = quotedStart
|
|
374
|
+
? quotedStart.endIndex
|
|
375
|
+
: opening && /^(?:%[qQwWiIxrs]?[^\w\s]|["'`]+)$/.test(opening.text)
|
|
376
|
+
? opening.endIndex
|
|
377
|
+
: match
|
|
378
|
+
? node.startIndex + match[0].length
|
|
379
|
+
: null;
|
|
380
|
+
const end = quotedEnd
|
|
381
|
+
? quotedEnd.startIndex
|
|
382
|
+
: match?.[1]
|
|
383
|
+
? node.endIndex - match[1].length
|
|
384
|
+
: closing && closing.id !== opening?.id
|
|
385
|
+
? closing.startIndex
|
|
386
|
+
: null;
|
|
387
|
+
if (start === null || end === null || end < start)
|
|
388
|
+
throw new Error('Opaque literal boundary');
|
|
389
|
+
const substitutions = node.children.filter((child) => child.startIndex >= start &&
|
|
390
|
+
child.endIndex <= end &&
|
|
391
|
+
![
|
|
392
|
+
'string_content',
|
|
393
|
+
'content',
|
|
394
|
+
'string_fragment',
|
|
395
|
+
'multiline_string_fragment',
|
|
396
|
+
'raw_string_content',
|
|
397
|
+
'raw_string_literal_content',
|
|
398
|
+
'interpreted_string_literal_content',
|
|
399
|
+
'string_literal_content',
|
|
400
|
+
'quoted_content',
|
|
401
|
+
'escape_sequence',
|
|
402
|
+
].includes(child.type));
|
|
403
|
+
let result = original.slice(0, start - node.startIndex);
|
|
404
|
+
let offset = start;
|
|
405
|
+
for (const child of substitutions) {
|
|
406
|
+
result += payload(original.slice(offset - node.startIndex, child.startIndex - node.startIndex), sensitive);
|
|
407
|
+
result += visit(child); // Runtime interpolation never inherits credential sensitivity.
|
|
408
|
+
offset = child.endIndex;
|
|
409
|
+
}
|
|
410
|
+
return (result +
|
|
411
|
+
payload(original.slice(offset - node.startIndex, end - node.startIndex), sensitive) +
|
|
412
|
+
original.slice(end - node.startIndex));
|
|
413
|
+
}
|
|
414
|
+
if (['string_content', 'heredoc_content', 'quoted_content', 'nowdoc_body'].includes(type))
|
|
415
|
+
return payload(original, sensitive);
|
|
416
|
+
if (language === 'julia' && type === 'assignment')
|
|
417
|
+
mark(node.firstNamedChild, node.lastNamedChild);
|
|
418
|
+
if (language === 'r' && type === 'binary_operator') {
|
|
419
|
+
const op = node.childForFieldName('operator')?.text;
|
|
420
|
+
if (['<-', '<<-', '=', ':='].includes(op ?? ''))
|
|
421
|
+
mark(node.childForFieldName('lhs'), node.childForFieldName('rhs'));
|
|
422
|
+
if (['->', '->>'].includes(op ?? ''))
|
|
423
|
+
mark(node.childForFieldName('rhs'), node.childForFieldName('lhs'));
|
|
424
|
+
}
|
|
425
|
+
if (language === 'r' && ['parameter', 'argument'].includes(type))
|
|
426
|
+
mark(node.childForFieldName('name'), node.childForFieldName('default') ?? node.childForFieldName('value'));
|
|
427
|
+
if (language === 'zig' && ['assignment_statement', 'field_declaration'].includes(type))
|
|
428
|
+
mark(node.childForFieldName('name'), node.childForFieldName('expression') ?? node.childForFieldName('default'));
|
|
429
|
+
if (language === 'protobuf' && type === 'field') {
|
|
430
|
+
const name = node.namedChildren.find((c) => c.type === 'identifier');
|
|
431
|
+
for (const option of node.namedChildren.find((c) => c.type === 'field_options')
|
|
432
|
+
?.namedChildren ?? []) {
|
|
433
|
+
if (option.type !== 'field_option' ||
|
|
434
|
+
option.namedChildren.find((c) => c.type === 'identifier')?.text !== 'default')
|
|
435
|
+
continue;
|
|
436
|
+
const constant = option.namedChildren.find((c) => c.type === 'constant');
|
|
437
|
+
mark(name ?? null, constant?.namedChildren.find((c) => c.type === 'string') ?? null);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
if (language === 'graphql' && type === 'object_field')
|
|
441
|
+
mark(node.namedChildren.find((c) => c.type === 'name') ?? null, node.namedChildren.find((c) => c.type === 'value') ?? null);
|
|
442
|
+
if (language === 'graphql' &&
|
|
443
|
+
['input_value_definition', 'variable_definition'].includes(type)) {
|
|
444
|
+
const name = type === 'input_value_definition'
|
|
445
|
+
? node.namedChildren.find((c) => c.type === 'name')
|
|
446
|
+
: node.namedChildren.find((c) => c.type === 'variable')?.firstNamedChild;
|
|
447
|
+
const value = node.namedChildren.find((c) => c.type === 'default_value')?.firstNamedChild;
|
|
448
|
+
mark(name ?? null, value ?? null);
|
|
449
|
+
}
|
|
450
|
+
if (language === 'erlang' && type === 'match_expr')
|
|
451
|
+
mark(node.childForFieldName('lhs'), node.childForFieldName('rhs'));
|
|
452
|
+
if (language === 'haskell' && type === 'bind') {
|
|
453
|
+
const name = node.childForFieldName('name');
|
|
454
|
+
mark(name, node.childForFieldName('expression'));
|
|
455
|
+
for (const match of node.childrenForFieldName('match'))
|
|
456
|
+
mark(name, match.childForFieldName('expression'));
|
|
457
|
+
}
|
|
458
|
+
if ((language === 'ocaml' || language === 'ocaml_interface') && type === 'let_binding')
|
|
459
|
+
mark(node.childForFieldName('pattern'), node.childForFieldName('body'));
|
|
460
|
+
if (language === 'clojure' && type === 'list') {
|
|
461
|
+
const forms = node.namedChildren.filter((c) => c.type !== 'comment');
|
|
462
|
+
if (forms[0]?.type === 'symbol' &&
|
|
463
|
+
forms[0].text === 'def' &&
|
|
464
|
+
forms[1]?.type === 'symbol' &&
|
|
465
|
+
forms.length >= 3)
|
|
466
|
+
mark(forms[1], forms.at(-1) ?? null);
|
|
467
|
+
if (forms[0]?.type === 'symbol' && forms[0].text === 'let' && forms[1]?.type === 'vector') {
|
|
468
|
+
const bindings = forms[1].namedChildren.filter((c) => c.type !== 'comment');
|
|
469
|
+
for (let i = 0; i + 1 < bindings.length; i += 2)
|
|
470
|
+
mark(bindings[i] ?? null, bindings[i + 1] ?? null);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
if (language === 'kotlin' && type === 'assignment')
|
|
474
|
+
mark(node.firstNamedChild, node.lastNamedChild);
|
|
475
|
+
if (language === 'dart' && type === 'assignment_expression_without_cascade')
|
|
476
|
+
mark(node.childForFieldName('left'), node.childForFieldName('right'));
|
|
477
|
+
if (language === 'kotlin' && type === 'property_declaration') {
|
|
478
|
+
const key = node.namedChildren.find((c) => c.type === 'variable_declaration')?.firstNamedChild;
|
|
479
|
+
const equals = node.children.findIndex((c) => c.type === '=');
|
|
480
|
+
if (equals >= 0)
|
|
481
|
+
mark(key ?? null, node.children[equals + 1] ?? null);
|
|
482
|
+
}
|
|
483
|
+
if (language === 'dart' &&
|
|
484
|
+
[
|
|
485
|
+
'initialized_variable_definition',
|
|
486
|
+
'initialized_identifier',
|
|
487
|
+
'static_final_declaration',
|
|
488
|
+
].includes(type)) {
|
|
489
|
+
const equals = node.children.findIndex((c) => c.type === '=');
|
|
490
|
+
if (equals >= 0)
|
|
491
|
+
mark(node.childForFieldName('name') ?? node.firstNamedChild, node.children[equals + 1] ?? null);
|
|
492
|
+
}
|
|
493
|
+
// These grammars place defaults beside the parameter rather than inside it.
|
|
494
|
+
if ((language === 'kotlin' && type === 'function_value_parameters') ||
|
|
495
|
+
(language === 'dart' && type === 'optional_formal_parameters')) {
|
|
496
|
+
node.children.forEach((child, i, children) => {
|
|
497
|
+
if (!['=', ':'].includes(child.type))
|
|
498
|
+
return;
|
|
499
|
+
const parameter = children[i - 1];
|
|
500
|
+
mark(parameter?.childForFieldName('name') ?? parameter?.firstNamedChild ?? null, children[i + 1] ?? null);
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
if (language === 'dart' && type === 'named_argument')
|
|
504
|
+
mark(node.firstNamedChild?.firstNamedChild ?? null, node.lastNamedChild);
|
|
505
|
+
if (language === 'kotlin' &&
|
|
506
|
+
type === 'value_argument' &&
|
|
507
|
+
node.children.some((c) => c.type === '='))
|
|
508
|
+
mark(node.firstNamedChild, node.lastNamedChild);
|
|
509
|
+
if (type === 'var_spec' || type === 'const_spec') {
|
|
510
|
+
const names = node.childrenForFieldName('name');
|
|
511
|
+
const values = node.childForFieldName('value')?.namedChildren ?? [];
|
|
512
|
+
names.forEach((name, i) => mark(name, values[i] ?? null));
|
|
513
|
+
}
|
|
514
|
+
else if (language === 'go' &&
|
|
515
|
+
['short_var_declaration', 'assignment_statement'].includes(type)) {
|
|
516
|
+
const names = node.childForFieldName('left')?.namedChildren ?? [];
|
|
517
|
+
const values = node.childForFieldName('right')?.namedChildren ?? [];
|
|
518
|
+
names.forEach((name, i) => mark(name, values[i] ?? null));
|
|
519
|
+
}
|
|
520
|
+
else if (type === 'variable_declarator') {
|
|
521
|
+
mark(node.childForFieldName('name'), node.childForFieldName('value') ?? node.namedChildren[1] ?? null);
|
|
522
|
+
}
|
|
523
|
+
else if (language === 'php' && type === 'property_element') {
|
|
524
|
+
mark(node.childForFieldName('name'), node.childForFieldName('default_value'));
|
|
525
|
+
}
|
|
526
|
+
else if (language === 'csharp' && type === 'property_declaration') {
|
|
527
|
+
mark(node.childForFieldName('name'), node.childForFieldName('value'));
|
|
528
|
+
}
|
|
529
|
+
else if (['val_definition', 'var_definition'].includes(type)) {
|
|
530
|
+
mark(node.childForFieldName('pattern'), node.childForFieldName('value'));
|
|
531
|
+
}
|
|
532
|
+
else if (['assignment', 'assignment_expression', 'operator_assignment'].includes(type) ||
|
|
533
|
+
(language === 'elixir' &&
|
|
534
|
+
type === 'binary_operator' &&
|
|
535
|
+
node.childForFieldName('operator')?.text === '=')) {
|
|
536
|
+
mark(node.childForFieldName('left'), node.childForFieldName('right'));
|
|
537
|
+
}
|
|
538
|
+
else if (['pair', 'keyed_element'].includes(type)) {
|
|
539
|
+
mark(node.childForFieldName('key'), node.childForFieldName('value'));
|
|
540
|
+
}
|
|
541
|
+
else if (language === 'php' &&
|
|
542
|
+
type === 'array_element_initializer' &&
|
|
543
|
+
node.children.some((c) => c.type === '=>')) {
|
|
544
|
+
mark(node.namedChildren[0] ?? null, node.namedChildren[1] ?? null);
|
|
545
|
+
}
|
|
546
|
+
else if (type === 'state_variable_declaration') {
|
|
547
|
+
mark(node.childForFieldName('name'), node.childForFieldName('value'));
|
|
548
|
+
}
|
|
549
|
+
else if (language === 'solidity' && type === 'variable_declaration_statement') {
|
|
550
|
+
mark(node.namedChildren
|
|
551
|
+
.find((c) => c.type === 'variable_declaration')
|
|
552
|
+
?.childForFieldName('name') ?? null, node.childForFieldName('value'));
|
|
553
|
+
}
|
|
554
|
+
if ([
|
|
555
|
+
'parameter',
|
|
556
|
+
'optional_parameter',
|
|
557
|
+
'simple_parameter',
|
|
558
|
+
'class_parameter',
|
|
559
|
+
'named_argument',
|
|
560
|
+
].includes(type)) {
|
|
561
|
+
const equals = node.children.findIndex((child) => child.type === '=');
|
|
562
|
+
if (equals >= 0)
|
|
563
|
+
mark(node.childForFieldName('name') ?? node.firstNamedChild, node.children[equals + 1] ?? null);
|
|
564
|
+
}
|
|
565
|
+
let result = '';
|
|
566
|
+
let offset = node.startIndex;
|
|
567
|
+
for (const child of node.namedChildren) {
|
|
568
|
+
result += original.slice(offset - node.startIndex, child.startIndex - node.startIndex);
|
|
569
|
+
const carry = sensitive &&
|
|
570
|
+
(((type !== 'list' || language !== 'clojure') &&
|
|
571
|
+
[
|
|
572
|
+
'expression',
|
|
573
|
+
'expression_list',
|
|
574
|
+
'literal_element',
|
|
575
|
+
'literal_value',
|
|
576
|
+
'array',
|
|
577
|
+
'array_creation_expression',
|
|
578
|
+
'hash',
|
|
579
|
+
'list',
|
|
580
|
+
'keywords',
|
|
581
|
+
'tuple_expression',
|
|
582
|
+
'initializer_expression',
|
|
583
|
+
'collection_expression',
|
|
584
|
+
'parenthesized_expression',
|
|
585
|
+
'unary_expression',
|
|
586
|
+
'interpolated_string_expression',
|
|
587
|
+
'string_literal',
|
|
588
|
+
'heredoc',
|
|
589
|
+
'heredoc_body',
|
|
590
|
+
'nowdoc',
|
|
591
|
+
'regex',
|
|
592
|
+
'quoted_atom',
|
|
593
|
+
'delimited_symbol',
|
|
594
|
+
].includes(type)) ||
|
|
595
|
+
(language === 'clojure' && ['vector', 'hash_map', 'set'].includes(type)) ||
|
|
596
|
+
(language === 'erlang' && type === 'binary' && child.type === 'bin_element') ||
|
|
597
|
+
(language === 'erlang' &&
|
|
598
|
+
type === 'bin_element' &&
|
|
599
|
+
child.id === node.childForFieldName('element')?.id) ||
|
|
600
|
+
(language === 'haskell' &&
|
|
601
|
+
type === 'conditional' &&
|
|
602
|
+
[node.childForFieldName('then')?.id, node.childForFieldName('else')?.id].includes(child.id)) ||
|
|
603
|
+
(['ocaml', 'ocaml_interface'].includes(language) &&
|
|
604
|
+
((type === 'if_expression' && ['then_clause', 'else_clause'].includes(child.type)) ||
|
|
605
|
+
(['then_clause', 'else_clause'].includes(type) &&
|
|
606
|
+
child.id === node.childForFieldName('expression')?.id))) ||
|
|
607
|
+
(language === 'haskell' && type === 'literal') ||
|
|
608
|
+
(language === 'graphql' &&
|
|
609
|
+
['value', 'list_value', 'object_value', 'object_field'].includes(type)) ||
|
|
610
|
+
(['kotlin', 'dart'].includes(language) &&
|
|
611
|
+
[
|
|
612
|
+
'collection_literal',
|
|
613
|
+
'list_literal',
|
|
614
|
+
'set_or_map_literal',
|
|
615
|
+
'additive_expression',
|
|
616
|
+
'if_null_expression',
|
|
617
|
+
'elvis_expression',
|
|
618
|
+
].includes(type)) ||
|
|
619
|
+
(type === 'composite_literal' && child.id === node.childForFieldName('body')?.id) ||
|
|
620
|
+
(type === 'array_element_initializer' && child.id === node.lastNamedChild?.id) ||
|
|
621
|
+
(['binary_expression', 'binary_operator'].includes(type) &&
|
|
622
|
+
['+', '.', '<>', '||', '??'].includes(node.childForFieldName('operator')?.text ?? '')) ||
|
|
623
|
+
(['conditional_expression', 'ternary_expression'].includes(type) &&
|
|
624
|
+
child.id !== node.childForFieldName('condition')?.id) ||
|
|
625
|
+
(['pair', 'keyed_element'].includes(type) &&
|
|
626
|
+
child.id === node.childForFieldName('value')?.id) ||
|
|
627
|
+
([
|
|
628
|
+
'method_invocation',
|
|
629
|
+
'member_access_expression',
|
|
630
|
+
'field_expression',
|
|
631
|
+
'field_access',
|
|
632
|
+
'member_expression',
|
|
633
|
+
].includes(type) &&
|
|
634
|
+
child.id === node.childForFieldName('object')?.id) ||
|
|
635
|
+
(['invocation_expression', 'call_expression', 'call'].includes(type) &&
|
|
636
|
+
[
|
|
637
|
+
node.childForFieldName('function')?.id,
|
|
638
|
+
node.childForFieldName('receiver')?.id,
|
|
639
|
+
].includes(child.id)));
|
|
640
|
+
result += visit(child, carry);
|
|
641
|
+
offset = child.endIndex;
|
|
642
|
+
}
|
|
643
|
+
return result + original.slice(offset - node.startIndex);
|
|
644
|
+
};
|
|
645
|
+
return visit(root);
|
|
646
|
+
}
|
|
122
647
|
/** CMake substitutions remain source expressions; only AST argument/comment payloads are scrubbed. */
|
|
123
648
|
function redactCmakeNode(node, sensitive = false) {
|
|
124
649
|
const original = node.text;
|
|
@@ -733,7 +1258,9 @@ function sqlSecretLabel(node) {
|
|
|
733
1258
|
return sqlSecretLabel(node.childForFieldName('name') ?? node.firstNamedChild);
|
|
734
1259
|
// This dialect-neutral grammar sometimes calls PostgreSQL quoted identifiers literals.
|
|
735
1260
|
if (node.type === 'identifier' || (node.type === 'literal' && node.text.startsWith('"')))
|
|
736
|
-
return SECRET_LABEL_RE.test(node.text.
|
|
1261
|
+
return SECRET_LABEL_RE.test(node.text.startsWith('`')
|
|
1262
|
+
? node.text.slice(1, -1).replaceAll('``', '`')
|
|
1263
|
+
: node.text.replace(/^"|"$/g, '').replaceAll('""', '"'));
|
|
737
1264
|
return false;
|
|
738
1265
|
}
|
|
739
1266
|
function redactSqlNode(node, sensitive = false) {
|
|
@@ -777,6 +1304,13 @@ function redactSqlNode(node, sensitive = false) {
|
|
|
777
1304
|
const columnLabel = node.type === 'column_definition' && sqlSecretLabel(node.childForFieldName('name'));
|
|
778
1305
|
const argumentLabel = node.type === 'function_argument' &&
|
|
779
1306
|
sqlSecretLabel(node.namedChildren.find((child) => child.type === 'identifier') ?? null);
|
|
1307
|
+
const argumentDefaultMarker = argumentLabel
|
|
1308
|
+
? node.children.find((child) => ['keyword_default', '='].includes(child.type))
|
|
1309
|
+
: undefined;
|
|
1310
|
+
const argumentDefaultValue = argumentDefaultMarker
|
|
1311
|
+
? node.namedChildren.find((child) => child.startIndex >= argumentDefaultMarker.endIndex &&
|
|
1312
|
+
!['comment', 'marginalia'].includes(child.type))
|
|
1313
|
+
: undefined;
|
|
780
1314
|
const aliasLabel = node.type === 'term' && sqlSecretLabel(node.childForFieldName('alias'));
|
|
781
1315
|
const insert = node.type === 'list' && node.parent?.type === 'insert' ? node.parent : null;
|
|
782
1316
|
const columns = insert?.namedChildren.find((child) => child.type === 'list');
|
|
@@ -802,7 +1336,7 @@ function redactSqlNode(node, sensitive = false) {
|
|
|
802
1336
|
(labeled && child.id === right?.id) ||
|
|
803
1337
|
(node.type === 'binary_expression' && sqlSecretLabel(right) && child.id === left?.id) ||
|
|
804
1338
|
(columnLabel && previous?.type === 'keyword_default') ||
|
|
805
|
-
|
|
1339
|
+
child.id === argumentDefaultValue?.id ||
|
|
806
1340
|
(aliasLabel && child.id === node.childForFieldName('value')?.id) ||
|
|
807
1341
|
child.id === node.childForFieldName('password')?.id ||
|
|
808
1342
|
sqlSecretLabel(columnValues[index] ?? null));
|
|
@@ -1256,4 +1790,149 @@ async function scrubVueSource(text) {
|
|
|
1256
1790
|
parser.delete();
|
|
1257
1791
|
}
|
|
1258
1792
|
}
|
|
1793
|
+
/** Official component offsets delimit payloads; existing JS/TS rules handle script expressions. */
|
|
1794
|
+
async function scrubSvelteSource(text) {
|
|
1795
|
+
const marker = '<!-- mnemonik:svelte-source-fidelity-unavailable -->\n';
|
|
1796
|
+
const fallback = () => (text.startsWith(marker) ? '' : marker) + scrubPayload(text);
|
|
1797
|
+
if (!text)
|
|
1798
|
+
return text;
|
|
1799
|
+
if (text.includes('mnemonik:svelte-source-fidelity-unavailable'))
|
|
1800
|
+
return fallback();
|
|
1801
|
+
if (Buffer.byteLength(text, 'utf8') > MAX_AST_PARSE_BYTES)
|
|
1802
|
+
return fallback();
|
|
1803
|
+
let parser;
|
|
1804
|
+
try {
|
|
1805
|
+
const deadline = performance.now() + 4_000;
|
|
1806
|
+
const { parse } = await import('svelte/compiler');
|
|
1807
|
+
const ast = parse(text, { modern: true });
|
|
1808
|
+
const edits = [];
|
|
1809
|
+
let faithful = true;
|
|
1810
|
+
parser = new Parser();
|
|
1811
|
+
const js = await loadGrammar('javascript');
|
|
1812
|
+
const ts = await loadGrammar('typescript');
|
|
1813
|
+
if ('unavailable' in js || 'unavailable' in ts)
|
|
1814
|
+
return fallback();
|
|
1815
|
+
parser.setLanguage(js.ok.language);
|
|
1816
|
+
const activeParser = parser;
|
|
1817
|
+
const syntax = (source, expression = false, sensitive = false) => {
|
|
1818
|
+
const prefix = expression ? `const ${sensitive ? 'password' : '__mnemonik_svelte'} = (` : '';
|
|
1819
|
+
const suffix = expression ? '\n);' : '';
|
|
1820
|
+
const full = prefix + source + suffix;
|
|
1821
|
+
const tree = activeParser.parse(full, null, {
|
|
1822
|
+
progressCallback: () => performance.now() > deadline,
|
|
1823
|
+
});
|
|
1824
|
+
try {
|
|
1825
|
+
if (!tree || tree.rootNode.hasError || tree.rootNode.descendantCount > 200_000) {
|
|
1826
|
+
faithful = false;
|
|
1827
|
+
return sensitive ? literalPayload(source, true) : scrubPayload(source);
|
|
1828
|
+
}
|
|
1829
|
+
const clean = full.slice(0, tree.rootNode.startIndex) +
|
|
1830
|
+
redactNode(tree.rootNode) +
|
|
1831
|
+
full.slice(tree.rootNode.endIndex);
|
|
1832
|
+
return clean.slice(prefix.length, clean.length - suffix.length);
|
|
1833
|
+
}
|
|
1834
|
+
finally {
|
|
1835
|
+
tree?.delete();
|
|
1836
|
+
}
|
|
1837
|
+
};
|
|
1838
|
+
const edit = (start, end, clean) => {
|
|
1839
|
+
if (typeof start !== 'number' ||
|
|
1840
|
+
typeof end !== 'number' ||
|
|
1841
|
+
start < 0 ||
|
|
1842
|
+
end < start ||
|
|
1843
|
+
end > text.length)
|
|
1844
|
+
throw new Error('Component offsets unavailable');
|
|
1845
|
+
edits.push({ start, end, clean: clean(text.slice(start, end)) });
|
|
1846
|
+
};
|
|
1847
|
+
let templateTs = false;
|
|
1848
|
+
for (const script of [ast.module, ast.instance]) {
|
|
1849
|
+
if (!script)
|
|
1850
|
+
continue;
|
|
1851
|
+
const lang = script.attributes.find((a) => a.name === 'lang');
|
|
1852
|
+
const language = !lang
|
|
1853
|
+
? 'js'
|
|
1854
|
+
: Array.isArray(lang.value) && lang.value.length === 1 && lang.value[0]?.type === 'Text'
|
|
1855
|
+
? lang.value[0].data
|
|
1856
|
+
: null;
|
|
1857
|
+
if (!['js', 'ts'].includes(language ?? '') || script.attributes.some((a) => a.name === 'src'))
|
|
1858
|
+
return fallback();
|
|
1859
|
+
parser.setLanguage(language === 'ts' ? ts.ok.language : js.ok.language);
|
|
1860
|
+
const offsets = script.content;
|
|
1861
|
+
edit(offsets.start, offsets.end, (raw) => syntax(raw));
|
|
1862
|
+
if (script === ast.instance)
|
|
1863
|
+
templateTs = language === 'ts';
|
|
1864
|
+
}
|
|
1865
|
+
parser.setLanguage(templateTs ? ts.ok.language : js.ok.language);
|
|
1866
|
+
const pending = [
|
|
1867
|
+
{ value: ast.fragment, sensitive: false },
|
|
1868
|
+
];
|
|
1869
|
+
let count = 0;
|
|
1870
|
+
while (pending.length) {
|
|
1871
|
+
if (++count > 200_000 || performance.now() > deadline)
|
|
1872
|
+
return fallback();
|
|
1873
|
+
const entry = pending.pop();
|
|
1874
|
+
if (!entry || !entry.value || typeof entry.value !== 'object')
|
|
1875
|
+
continue;
|
|
1876
|
+
if (Array.isArray(entry.value)) {
|
|
1877
|
+
for (const value of entry.value)
|
|
1878
|
+
pending.push({ value, sensitive: entry.sensitive });
|
|
1879
|
+
continue;
|
|
1880
|
+
}
|
|
1881
|
+
const node = entry.value;
|
|
1882
|
+
const type = typeof node.type === 'string' ? node.type : '';
|
|
1883
|
+
if (type === 'Text') {
|
|
1884
|
+
edit(node.start, node.end, (raw) => literalPayload(raw, entry.sensitive));
|
|
1885
|
+
continue;
|
|
1886
|
+
}
|
|
1887
|
+
if (type === 'Comment') {
|
|
1888
|
+
edit(node.start, node.end, (raw) => '<!--' + scrubPayload(raw.slice(4, -3)) + '-->');
|
|
1889
|
+
continue;
|
|
1890
|
+
}
|
|
1891
|
+
if (/Expression$/.test(type) ||
|
|
1892
|
+
['Identifier', 'Literal', 'TemplateLiteral', 'VariableDeclaration'].includes(type)) {
|
|
1893
|
+
edit(node.start, node.end, (raw) => syntax(raw, type !== 'VariableDeclaration', entry.sensitive));
|
|
1894
|
+
continue;
|
|
1895
|
+
}
|
|
1896
|
+
const sensitive = entry.sensitive ||
|
|
1897
|
+
(type === 'Attribute' && typeof node.name === 'string' && SECRET_LABEL_RE.test(node.name));
|
|
1898
|
+
for (const [key, value] of Object.entries(node))
|
|
1899
|
+
if (key !== 'parent' && value && typeof value === 'object')
|
|
1900
|
+
pending.push({ value, sensitive });
|
|
1901
|
+
}
|
|
1902
|
+
if (ast.css)
|
|
1903
|
+
edit(ast.css.start, ast.css.end, (raw) => {
|
|
1904
|
+
const clean = scrubPayload(raw);
|
|
1905
|
+
if (clean !== raw)
|
|
1906
|
+
faithful = false;
|
|
1907
|
+
return clean;
|
|
1908
|
+
});
|
|
1909
|
+
// Unparsed tags and boundaries cannot establish fidelity if policy changes them.
|
|
1910
|
+
edits.sort((a, b) => a.start - b.start);
|
|
1911
|
+
let offset = 0;
|
|
1912
|
+
let clean = '';
|
|
1913
|
+
for (const e of edits) {
|
|
1914
|
+
if (e.start < offset)
|
|
1915
|
+
return fallback();
|
|
1916
|
+
const gap = text.slice(offset, e.start);
|
|
1917
|
+
const cleanGap = scrubPayload(gap);
|
|
1918
|
+
if (gap !== cleanGap)
|
|
1919
|
+
faithful = false;
|
|
1920
|
+
clean += cleanGap + e.clean;
|
|
1921
|
+
offset = e.end;
|
|
1922
|
+
}
|
|
1923
|
+
const tail = text.slice(offset);
|
|
1924
|
+
const cleanTail = scrubPayload(tail);
|
|
1925
|
+
if (tail !== cleanTail)
|
|
1926
|
+
faithful = false;
|
|
1927
|
+
clean += cleanTail;
|
|
1928
|
+
parse(clean, { modern: true });
|
|
1929
|
+
return faithful ? clean : marker + clean;
|
|
1930
|
+
}
|
|
1931
|
+
catch {
|
|
1932
|
+
return fallback();
|
|
1933
|
+
}
|
|
1934
|
+
finally {
|
|
1935
|
+
parser?.delete();
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1259
1938
|
//# sourceMappingURL=sourceRedaction.js.map
|