@mnemonik/shared 7.144.1 → 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.
Files changed (113) hide show
  1. package/dist/ast/grammars.d.ts +6 -6
  2. package/dist/ast/grammars.d.ts.map +1 -1
  3. package/dist/ast/grammars.js +51 -10
  4. package/dist/ast/grammars.js.map +1 -1
  5. package/dist/sourceRedaction.d.ts.map +1 -1
  6. package/dist/sourceRedaction.js +743 -64
  7. package/dist/sourceRedaction.js.map +1 -1
  8. package/package.json +2 -1
  9. package/queries/clojure.tags.scm +1 -0
  10. package/queries/dart.tags.scm +3 -0
  11. package/queries/erlang.tags.scm +2 -0
  12. package/queries/graphql.tags.scm +3 -0
  13. package/queries/haskell.tags.scm +3 -0
  14. package/queries/julia.tags.scm +1 -0
  15. package/queries/kotlin.tags.scm +3 -0
  16. package/queries/ocaml.tags.scm +2 -0
  17. package/queries/ocaml_interface.tags.scm +1 -0
  18. package/queries/perl.tags.scm +2 -0
  19. package/queries/protobuf.tags.scm +5 -0
  20. package/queries/r.tags.scm +2 -0
  21. package/queries/zig.tags.scm +2 -0
  22. package/scripts/vendor-grammars.ts +568 -36
  23. package/src/ast/grammars.ts +51 -10
  24. package/src/sourceRedaction.ts +765 -12
  25. package/wasm/bash.wasm.gz +0 -0
  26. package/wasm/c-sharp.wasm.gz +0 -0
  27. package/wasm/c.wasm.gz +0 -0
  28. package/wasm/clojure.LICENSE +21 -0
  29. package/wasm/clojure.tags.scm +1 -0
  30. package/wasm/clojure.wasm.gz +0 -0
  31. package/wasm/cmake.wasm.gz +0 -0
  32. package/wasm/cpp.wasm.gz +0 -0
  33. package/wasm/dart.LICENSE +20 -0
  34. package/wasm/dart.tags.scm +3 -0
  35. package/wasm/dart.wasm.gz +0 -0
  36. package/wasm/elixir.wasm.gz +0 -0
  37. package/wasm/erlang.LICENSE +201 -0
  38. package/wasm/erlang.tags.scm +2 -0
  39. package/wasm/erlang.wasm.gz +0 -0
  40. package/wasm/go.wasm.gz +0 -0
  41. package/wasm/graphql.LICENSE +21 -0
  42. package/wasm/graphql.tags.scm +3 -0
  43. package/wasm/graphql.wasm.gz +0 -0
  44. package/wasm/groovy.wasm.gz +0 -0
  45. package/wasm/haskell.LICENSE +21 -0
  46. package/wasm/haskell.tags.scm +3 -0
  47. package/wasm/haskell.wasm.gz +0 -0
  48. package/wasm/java.wasm.gz +0 -0
  49. package/wasm/javascript.wasm.gz +0 -0
  50. package/wasm/julia.LICENSE +21 -0
  51. package/wasm/julia.tags.scm +1 -0
  52. package/wasm/julia.wasm.gz +0 -0
  53. package/wasm/kotlin.LICENSE +9 -0
  54. package/wasm/kotlin.tags.scm +3 -0
  55. package/wasm/kotlin.wasm.gz +0 -0
  56. package/wasm/lua.wasm.gz +0 -0
  57. package/wasm/nix.wasm.gz +0 -0
  58. package/wasm/objcpp.wasm.gz +0 -0
  59. package/wasm/ocaml.LICENSE +21 -0
  60. package/wasm/ocaml.tags.scm +2 -0
  61. package/wasm/ocaml.wasm.gz +0 -0
  62. package/wasm/ocaml_interface.LICENSE +21 -0
  63. package/wasm/ocaml_interface.tags.scm +1 -0
  64. package/wasm/ocaml_interface.wasm.gz +0 -0
  65. package/wasm/perl.LICENSE +7 -0
  66. package/wasm/perl.tags.scm +2 -0
  67. package/wasm/perl.wasm.gz +0 -0
  68. package/wasm/php.wasm.gz +0 -0
  69. package/wasm/powershell.wasm.gz +0 -0
  70. package/wasm/protobuf.LICENSE +21 -0
  71. package/wasm/protobuf.tags.scm +5 -0
  72. package/wasm/protobuf.wasm.gz +0 -0
  73. package/wasm/python.wasm.gz +0 -0
  74. package/wasm/r.LICENSE +9 -0
  75. package/wasm/r.tags.scm +2 -0
  76. package/wasm/r.wasm.gz +0 -0
  77. package/wasm/ruby.wasm.gz +0 -0
  78. package/wasm/rust.wasm.gz +0 -0
  79. package/wasm/scala.wasm.gz +0 -0
  80. package/wasm/solidity.wasm.gz +0 -0
  81. package/wasm/sql.wasm.gz +0 -0
  82. package/wasm/swift.wasm.gz +0 -0
  83. package/wasm/terraform.wasm.gz +0 -0
  84. package/wasm/tsx.wasm.gz +0 -0
  85. package/wasm/typescript.wasm.gz +0 -0
  86. package/wasm/zig.LICENSE +29 -0
  87. package/wasm/zig.tags.scm +2 -0
  88. package/wasm/zig.wasm.gz +0 -0
  89. package/wasm/bash.wasm +0 -0
  90. package/wasm/c-sharp.wasm +0 -0
  91. package/wasm/c.wasm +0 -0
  92. package/wasm/cmake.wasm +0 -0
  93. package/wasm/cpp.wasm +0 -0
  94. package/wasm/elixir.wasm +0 -0
  95. package/wasm/go.wasm +0 -0
  96. package/wasm/groovy.wasm +0 -0
  97. package/wasm/java.wasm +0 -0
  98. package/wasm/javascript.wasm +0 -0
  99. package/wasm/lua.wasm +0 -0
  100. package/wasm/nix.wasm +0 -0
  101. package/wasm/objcpp.wasm +0 -0
  102. package/wasm/php.wasm +0 -0
  103. package/wasm/powershell.wasm +0 -0
  104. package/wasm/python.wasm +0 -0
  105. package/wasm/ruby.wasm +0 -0
  106. package/wasm/rust.wasm +0 -0
  107. package/wasm/scala.wasm +0 -0
  108. package/wasm/solidity.wasm +0 -0
  109. package/wasm/sql.wasm +0 -0
  110. package/wasm/swift.wasm +0 -0
  111. package/wasm/terraform.wasm +0 -0
  112. package/wasm/tsx.wasm +0 -0
  113. package/wasm/typescript.wasm +0 -0
@@ -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
- const language = extension === '.cmake' || basename(filePath) === 'CMakeLists.txt'
17
- ? 'cmake'
18
- : ['.ts', '.mts', '.cts'].includes(extension)
19
- ? 'typescript'
20
- : extension === '.tsx'
21
- ? 'tsx'
22
- : ['.js', '.jsx', '.mjs', '.cjs'].includes(extension)
23
- ? 'javascript'
24
- : ['.py', '.pyi'].includes(extension)
25
- ? 'python'
26
- : ['.sh', '.bash', '.zsh'].includes(extension)
27
- ? 'bash'
28
- : extension === '.swift'
29
- ? 'swift'
30
- : extension === '.sql'
31
- ? 'sql'
32
- : extension === '.lua'
33
- ? 'lua'
34
- : extension === '.rs'
35
- ? 'rust'
36
- : extension === '.nix'
37
- ? 'nix'
38
- : extension === '.tf'
39
- ? 'terraform'
40
- : ['.m', '.mm'].includes(extension)
41
- ? 'objcpp'
42
- : extension === '.c'
43
- ? 'c'
44
- : ['.h', '.cpp', '.cc', '.cxx', '.hpp', '.hh', '.hxx'].includes(extension)
45
- ? 'cpp'
46
- : ['.groovy', '.gradle'].includes(extension)
47
- ? 'groovy'
48
- : ['.ps1', '.psm1'].includes(extension)
49
- ? 'powershell'
50
- : null;
51
- const fallback = () => {
52
- const clean = scrubPayload(text);
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 || tree.rootNode.hasError)
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 = language === 'cmake'
90
- ? redactCmakeNode(root)
91
- : language === 'bash'
92
- ? redactShellNode(root, commandLineCredentialRanges(text))
93
- : language === 'swift'
94
- ? redactSwiftNode(root)
95
- : language === 'sql'
96
- ? redactSqlNode(root)
97
- : language === 'lua'
98
- ? redactLuaNode(root)
99
- : language === 'rust'
100
- ? redactRustNode(root)
101
- : language === 'nix'
102
- ? redactNixNode(root)
103
- : language === 'terraform'
104
- ? redactTerraformNode(root)
105
- : ['objcpp', 'c', 'cpp'].includes(language)
106
- ? redactCFamilyNode(root)
107
- : language === 'groovy'
108
- ? redactGroovyNode(root)
109
- : language === 'powershell'
110
- ? redactPowershellNode(root)
111
- : redactNode(root);
112
- return text.slice(0, root.startIndex) + redacted + text.slice(root.endIndex);
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.replace(/^"|"$/g, '').replaceAll('""', '"'));
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
- (argumentLabel && child.type === 'literal') ||
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