@mondaydotcomorg/atp-server 0.19.5 → 0.19.6
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/create-server.js +10 -10
- package/dist/create-server.js.map +1 -1
- package/dist/executor/ast-tracking-runtime.d.ts +6 -1
- package/dist/executor/ast-tracking-runtime.d.ts.map +1 -1
- package/dist/executor/ast-tracking-runtime.js +516 -242
- package/dist/executor/ast-tracking-runtime.js.map +1 -1
- package/package.json +7 -7
- package/src/create-server.ts +10 -10
- package/src/executor/ast-tracking-runtime.ts +516 -242
|
@@ -2,8 +2,12 @@
|
|
|
2
2
|
* AST Provenance Tracking Runtime for isolated-vm
|
|
3
3
|
* This code is injected into the isolate and runs INSIDE the sandbox
|
|
4
4
|
* It must be plain JavaScript with no imports
|
|
5
|
+
*
|
|
6
|
+
* This runtime provides comprehensive taint tracking by:
|
|
7
|
+
* 1. Deep-tainting all primitives from tool results
|
|
8
|
+
* 2. Intercepting native methods with re-entry protection
|
|
9
|
+
* 3. Supporting cross-execution tracking via hints
|
|
5
10
|
*/
|
|
6
|
-
// TODO: need to create atp.internal with internal functions like has to reduce complexity
|
|
7
11
|
export const AST_TRACKING_RUNTIME = `
|
|
8
12
|
// Pure JavaScript SHA-256 implementation for digest computation
|
|
9
13
|
function sha256(str) {
|
|
@@ -98,18 +102,26 @@ function sha256(str) {
|
|
|
98
102
|
return base64.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=/g, '');
|
|
99
103
|
}
|
|
100
104
|
|
|
105
|
+
// Re-entry guard to prevent infinite recursion in wrapped methods
|
|
106
|
+
let __inTrackingCall = false;
|
|
107
|
+
|
|
101
108
|
const __astTracker = {
|
|
102
109
|
metadata: new Map(),
|
|
103
110
|
nextId: 0,
|
|
104
111
|
hints: new Map(globalThis.__provenance_hints || []),
|
|
105
112
|
hintValues: new Map(globalThis.__provenance_hint_values || []),
|
|
113
|
+
// Track tainted objects via WeakMap for O(1) lookup
|
|
114
|
+
taintedObjects: new WeakMap(),
|
|
106
115
|
|
|
107
|
-
// SHA-256 digest computation to match server-side
|
|
108
116
|
computeDigest(value) {
|
|
117
|
+
if (__inTrackingCall) return null;
|
|
109
118
|
try {
|
|
119
|
+
__inTrackingCall = true;
|
|
110
120
|
const str = JSON.stringify(value);
|
|
121
|
+
__inTrackingCall = false;
|
|
111
122
|
return sha256(str);
|
|
112
123
|
} catch (e) {
|
|
124
|
+
__inTrackingCall = false;
|
|
113
125
|
return null;
|
|
114
126
|
}
|
|
115
127
|
},
|
|
@@ -117,7 +129,7 @@ const __astTracker = {
|
|
|
117
129
|
getId(value) {
|
|
118
130
|
if (typeof value === 'object' && value !== null) {
|
|
119
131
|
if (!value.__prov_id__) {
|
|
120
|
-
const id = 'tracked_' +
|
|
132
|
+
const id = 'tracked_' + Date.now() + '_' + (Math.random().toString(36).substring(2, 8));
|
|
121
133
|
try {
|
|
122
134
|
Object.defineProperty(value, '__prov_id__', {
|
|
123
135
|
value: id,
|
|
@@ -135,20 +147,87 @@ const __astTracker = {
|
|
|
135
147
|
return 'primitive_' + Date.now() + '_' + Math.random();
|
|
136
148
|
},
|
|
137
149
|
|
|
150
|
+
// Register a primitive value as tainted
|
|
151
|
+
registerTaintedPrimitive(value, metadata) {
|
|
152
|
+
// Accept any non-empty string or any valid number (including 0)
|
|
153
|
+
const isValidString = typeof value === 'string' && value !== '';
|
|
154
|
+
const isValidNumber = typeof value === 'number' && !Number.isNaN(value);
|
|
155
|
+
|
|
156
|
+
if (isValidString || isValidNumber) {
|
|
157
|
+
const taintedKey = 'tainted:' + String(value);
|
|
158
|
+
this.metadata.set(taintedKey, {
|
|
159
|
+
...metadata,
|
|
160
|
+
readers: metadata.readers || { type: 'restricted', readers: [] },
|
|
161
|
+
dependencies: metadata.dependencies || metadata.deps || []
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
// Deep taint an object - register all nested primitives
|
|
167
|
+
deepTaintObject(obj, metadata, visited = new WeakSet()) {
|
|
168
|
+
if (!obj || typeof obj !== 'object') return;
|
|
169
|
+
if (visited.has(obj)) return;
|
|
170
|
+
visited.add(obj);
|
|
171
|
+
|
|
172
|
+
// Mark object in WeakMap for fast lookup
|
|
173
|
+
this.taintedObjects.set(obj, metadata);
|
|
174
|
+
|
|
175
|
+
// Set __prov_id__ and register in metadata
|
|
176
|
+
const id = this.getId(obj);
|
|
177
|
+
this.metadata.set(id, metadata);
|
|
178
|
+
|
|
179
|
+
// Register all primitive properties
|
|
180
|
+
const keys = Object.keys(obj);
|
|
181
|
+
for (let i = 0; i < keys.length; i++) {
|
|
182
|
+
const key = keys[i];
|
|
183
|
+
const value = obj[key];
|
|
184
|
+
|
|
185
|
+
if (typeof value === 'string' || typeof value === 'number') {
|
|
186
|
+
this.registerTaintedPrimitive(value, metadata);
|
|
187
|
+
// Also store by object:key:value for additional lookup
|
|
188
|
+
const primitiveKey = id + ':' + key + ':' + String(value);
|
|
189
|
+
this.metadata.set(primitiveKey, metadata);
|
|
190
|
+
} else if (Array.isArray(value)) {
|
|
191
|
+
// Taint array and its elements
|
|
192
|
+
this.deepTaintObject(value, metadata, visited);
|
|
193
|
+
for (let j = 0; j < value.length; j++) {
|
|
194
|
+
const elem = value[j];
|
|
195
|
+
if (typeof elem === 'string' || typeof elem === 'number') {
|
|
196
|
+
this.registerTaintedPrimitive(elem, metadata);
|
|
197
|
+
} else if (typeof elem === 'object' && elem !== null) {
|
|
198
|
+
this.deepTaintObject(elem, metadata, visited);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
} else if (typeof value === 'object' && value !== null) {
|
|
202
|
+
this.deepTaintObject(value, metadata, visited);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
|
|
138
207
|
track(value, source, deps) {
|
|
139
208
|
try {
|
|
140
209
|
const id = this.getId(value);
|
|
141
|
-
|
|
142
|
-
|
|
210
|
+
const metadata = {
|
|
211
|
+
id,
|
|
212
|
+
source,
|
|
213
|
+
deps: deps || [],
|
|
214
|
+
readers: source.readers || { type: 'restricted', readers: ['tool:' + (source.tool || source.operation || 'unknown')] },
|
|
215
|
+
dependencies: []
|
|
216
|
+
};
|
|
217
|
+
this.metadata.set(id, metadata);
|
|
218
|
+
|
|
219
|
+
// Deep taint all nested primitives
|
|
220
|
+
if (value && typeof value === 'object') {
|
|
221
|
+
this.deepTaintObject(value, metadata);
|
|
222
|
+
}
|
|
223
|
+
|
|
143
224
|
return value;
|
|
144
225
|
} catch (error) {
|
|
145
|
-
console.error('[__track] Error:', error);
|
|
146
226
|
return value;
|
|
147
227
|
}
|
|
148
228
|
},
|
|
149
229
|
|
|
150
230
|
trackBinary(left, right, operator) {
|
|
151
|
-
// Perform the actual operation
|
|
152
231
|
let result;
|
|
153
232
|
switch (operator) {
|
|
154
233
|
case '+': result = left + right; break;
|
|
@@ -169,88 +248,13 @@ const __astTracker = {
|
|
|
169
248
|
default: result = left;
|
|
170
249
|
}
|
|
171
250
|
|
|
172
|
-
//
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
// Helper to check primitive provenance
|
|
177
|
-
const checkPrimitive = (value) => {
|
|
178
|
-
if (typeof value !== 'string' && typeof value !== 'number') {
|
|
179
|
-
return null;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// Check tainted key first
|
|
183
|
-
const taintedKey = 'tainted:' + String(value);
|
|
184
|
-
const taintedMeta = this.metadata.get(taintedKey);
|
|
185
|
-
if (taintedMeta && taintedMeta.source && taintedMeta.source.type === 'tool') {
|
|
186
|
-
return taintedMeta;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// Check hint-based tracking
|
|
190
|
-
const digest = this.computeDigest(value);
|
|
191
|
-
const hintMeta = this.hints.get(digest);
|
|
192
|
-
if (hintMeta && hintMeta.source && hintMeta.source.type === 'tool') {
|
|
193
|
-
return hintMeta;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// Check primitive map (id:key:value format)
|
|
197
|
-
for (const [key, meta] of this.metadata.entries()) {
|
|
198
|
-
if (!key.startsWith('tainted:') && key.includes(':')) {
|
|
199
|
-
const parts = key.split(':');
|
|
200
|
-
if (parts.length >= 3) {
|
|
201
|
-
const primitiveValue = parts.slice(2).join(':');
|
|
202
|
-
if (primitiveValue === String(value) && meta.source && meta.source.type === 'tool') {
|
|
203
|
-
return meta;
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
return null;
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
// Check left operand
|
|
213
|
-
if (typeof left === 'object' && left && left.__prov_id__) {
|
|
214
|
-
const leftMeta = this.metadata.get(left.__prov_id__);
|
|
215
|
-
if (leftMeta && leftMeta.source && leftMeta.source.type === 'tool') {
|
|
216
|
-
hasToolSource = true;
|
|
217
|
-
toolMetadata = leftMeta;
|
|
218
|
-
}
|
|
219
|
-
} else {
|
|
220
|
-
const primMeta = checkPrimitive(left);
|
|
221
|
-
if (primMeta) {
|
|
222
|
-
hasToolSource = true;
|
|
223
|
-
toolMetadata = primMeta;
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// Check right operand
|
|
228
|
-
if (!hasToolSource) {
|
|
229
|
-
if (typeof right === 'object' && right && right.__prov_id__) {
|
|
230
|
-
const rightMeta = this.metadata.get(right.__prov_id__);
|
|
231
|
-
if (rightMeta && rightMeta.source && rightMeta.source.type === 'tool') {
|
|
232
|
-
hasToolSource = true;
|
|
233
|
-
toolMetadata = rightMeta;
|
|
234
|
-
}
|
|
235
|
-
} else {
|
|
236
|
-
const primMeta = checkPrimitive(right);
|
|
237
|
-
if (primMeta) {
|
|
238
|
-
hasToolSource = true;
|
|
239
|
-
toolMetadata = primMeta;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
|
251
|
+
// Propagate taint from operands to result
|
|
252
|
+
const leftMeta = this.checkProvenance(left);
|
|
253
|
+
const rightMeta = this.checkProvenance(right);
|
|
254
|
+
const toolMetadata = leftMeta || rightMeta;
|
|
243
255
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
const taintedKey = 'tainted:' + String(result);
|
|
247
|
-
// Ensure metadata has all required fields, preserving readers from source
|
|
248
|
-
const fullMetadata = {
|
|
249
|
-
...toolMetadata,
|
|
250
|
-
readers: toolMetadata.readers || { type: 'restricted', readers: [] },
|
|
251
|
-
dependencies: toolMetadata.dependencies || toolMetadata.deps || []
|
|
252
|
-
};
|
|
253
|
-
this.metadata.set(taintedKey, fullMetadata);
|
|
256
|
+
if (toolMetadata && (typeof result === 'string' || typeof result === 'number')) {
|
|
257
|
+
this.registerTaintedPrimitive(result, toolMetadata);
|
|
254
258
|
}
|
|
255
259
|
|
|
256
260
|
return result;
|
|
@@ -261,28 +265,27 @@ const __astTracker = {
|
|
|
261
265
|
},
|
|
262
266
|
|
|
263
267
|
async trackMethod(object, method, args) {
|
|
264
|
-
//
|
|
268
|
+
// Wrap tainted primitives in arguments before calling API methods
|
|
269
|
+
const self = this;
|
|
265
270
|
function wrapTaintedInArgs(val, visited = new WeakSet()) {
|
|
266
271
|
if (val === null || val === undefined) return val;
|
|
267
272
|
|
|
268
|
-
|
|
269
|
-
const prov = this.checkProvenance(val);
|
|
273
|
+
const prov = self.checkProvenance(val);
|
|
270
274
|
if (prov && (typeof val === 'string' || typeof val === 'number')) {
|
|
271
|
-
// Wrap tainted primitive
|
|
272
275
|
return { __tainted_value: val, __prov_meta: prov };
|
|
273
276
|
}
|
|
274
277
|
|
|
275
|
-
// Recursively process objects/arrays
|
|
276
278
|
if (typeof val === 'object') {
|
|
277
279
|
if (visited.has(val)) return val;
|
|
278
280
|
visited.add(val);
|
|
279
281
|
|
|
280
282
|
if (Array.isArray(val)) {
|
|
281
|
-
return val.map(item => wrapTaintedInArgs
|
|
283
|
+
return val.map(item => wrapTaintedInArgs(item, visited));
|
|
282
284
|
} else {
|
|
283
285
|
const wrapped = {};
|
|
284
|
-
|
|
285
|
-
|
|
286
|
+
const keys = Object.keys(val);
|
|
287
|
+
for (let i = 0; i < keys.length; i++) {
|
|
288
|
+
wrapped[keys[i]] = wrapTaintedInArgs(val[keys[i]], visited);
|
|
286
289
|
}
|
|
287
290
|
return wrapped;
|
|
288
291
|
}
|
|
@@ -291,72 +294,38 @@ const __astTracker = {
|
|
|
291
294
|
return val;
|
|
292
295
|
}
|
|
293
296
|
|
|
294
|
-
|
|
295
|
-
const wrappedArgs = args.map(arg => wrapTaintedInArgs.call(this, arg));
|
|
297
|
+
const wrappedArgs = args.map(arg => wrapTaintedInArgs(arg));
|
|
296
298
|
|
|
297
|
-
// Call the method with wrapped arguments
|
|
298
299
|
if (typeof object === 'object' && object !== null && method in object) {
|
|
299
300
|
const result = await object[method](...wrappedArgs);
|
|
300
301
|
|
|
301
|
-
// Track the result
|
|
302
302
|
if (result && typeof result === 'object') {
|
|
303
303
|
const id = this.getId(result);
|
|
304
304
|
|
|
305
|
-
// Extract authorized readers from common param patterns (email, userId, username, user)
|
|
306
|
-
// Match server-side logic in sandbox-builder.ts (lines 459-470)
|
|
307
305
|
let authorizedReaders = [];
|
|
308
306
|
for (const arg of args) {
|
|
309
307
|
if (arg && typeof arg === 'object') {
|
|
310
|
-
// Check for user identifier fields (email, user, userId only - no generic 'id')
|
|
311
308
|
const value = arg.email || arg.user || arg.userId;
|
|
312
309
|
if (typeof value === 'string' && value.length > 0) {
|
|
313
310
|
authorizedReaders.push(value);
|
|
314
|
-
break;
|
|
311
|
+
break;
|
|
315
312
|
}
|
|
316
313
|
}
|
|
317
314
|
}
|
|
318
315
|
|
|
319
|
-
// If no email found, use tool-scoped authorization (matches server logic line 467-470)
|
|
320
316
|
if (authorizedReaders.length === 0) {
|
|
321
317
|
authorizedReaders = ['tool:' + method];
|
|
322
318
|
}
|
|
323
319
|
|
|
324
|
-
// Tool data should be restricted by default to prevent exfiltration
|
|
325
320
|
const metadata = {
|
|
326
321
|
id,
|
|
327
|
-
source: {
|
|
328
|
-
type: 'tool',
|
|
329
|
-
operation: method,
|
|
330
|
-
toolName: method,
|
|
331
|
-
timestamp: Date.now()
|
|
332
|
-
},
|
|
322
|
+
source: { type: 'tool', operation: method, toolName: method, timestamp: Date.now() },
|
|
333
323
|
readers: { type: 'restricted', readers: authorizedReaders },
|
|
334
|
-
deps: [
|
|
324
|
+
deps: [],
|
|
335
325
|
dependencies: []
|
|
336
326
|
};
|
|
337
327
|
this.metadata.set(id, metadata);
|
|
338
|
-
|
|
339
|
-
// Track primitive properties for token emission
|
|
340
|
-
for (const key in result) {
|
|
341
|
-
if (Object.prototype.hasOwnProperty.call(result, key)) {
|
|
342
|
-
const value = result[key];
|
|
343
|
-
if (typeof value === 'string' || typeof value === 'number') {
|
|
344
|
-
// Check if this primitive matches any hints
|
|
345
|
-
const digest = this.computeDigest(value);
|
|
346
|
-
const hintMeta = this.hints.get(digest);
|
|
347
|
-
|
|
348
|
-
const primitiveKey = id + ':' + key + ':' + String(value);
|
|
349
|
-
// Use hint metadata if available, otherwise use result metadata
|
|
350
|
-
this.metadata.set(primitiveKey, hintMeta || metadata);
|
|
351
|
-
|
|
352
|
-
// Also store by digest for cross-execution matching
|
|
353
|
-
if (hintMeta) {
|
|
354
|
-
const taintedKey = 'tainted:' + String(value);
|
|
355
|
-
this.metadata.set(taintedKey, hintMeta);
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
}
|
|
328
|
+
this.deepTaintObject(result, metadata);
|
|
360
329
|
}
|
|
361
330
|
|
|
362
331
|
return result;
|
|
@@ -366,81 +335,55 @@ const __astTracker = {
|
|
|
366
335
|
|
|
367
336
|
trackTemplate(expressions, quasis) {
|
|
368
337
|
let result = '';
|
|
369
|
-
let hasToolSource = false;
|
|
370
338
|
let toolMetadata = null;
|
|
371
339
|
|
|
372
|
-
// Helper to check primitive provenance
|
|
373
|
-
const checkPrimitive = (value) => {
|
|
374
|
-
if (typeof value !== 'string' && typeof value !== 'number') {
|
|
375
|
-
return null;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
// Check tainted key first
|
|
379
|
-
const taintedKey = 'tainted:' + String(value);
|
|
380
|
-
const taintedMeta = this.metadata.get(taintedKey);
|
|
381
|
-
if (taintedMeta && taintedMeta.source && taintedMeta.source.type === 'tool') {
|
|
382
|
-
return taintedMeta;
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
// Check hint-based tracking
|
|
386
|
-
const digest = this.computeDigest(value);
|
|
387
|
-
const hintMeta = this.hints.get(digest);
|
|
388
|
-
if (hintMeta && hintMeta.source && hintMeta.source.type === 'tool') {
|
|
389
|
-
return hintMeta;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
// Check primitive map (id:key:value format)
|
|
393
|
-
for (const [key, meta] of this.metadata.entries()) {
|
|
394
|
-
if (!key.startsWith('tainted:') && key.includes(':')) {
|
|
395
|
-
const parts = key.split(':');
|
|
396
|
-
if (parts.length >= 3) {
|
|
397
|
-
const primitiveValue = parts.slice(2).join(':');
|
|
398
|
-
if (primitiveValue === String(value) && meta.source && meta.source.type === 'tool') {
|
|
399
|
-
return meta;
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
return null;
|
|
406
|
-
};
|
|
407
|
-
|
|
408
340
|
for (let i = 0; i < quasis.length; i++) {
|
|
409
341
|
result += quasis[i] || '';
|
|
410
342
|
if (i < expressions.length) {
|
|
411
343
|
const expr = expressions[i];
|
|
412
344
|
result += String(expr);
|
|
413
345
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
// Check object provenance
|
|
417
|
-
if (typeof expr === 'object' && expr && expr.__prov_id__) {
|
|
418
|
-
const exprMeta = this.metadata.get(expr.__prov_id__);
|
|
419
|
-
if (exprMeta && exprMeta.source && exprMeta.source.type === 'tool') {
|
|
420
|
-
hasToolSource = true;
|
|
421
|
-
toolMetadata = exprMeta;
|
|
422
|
-
}
|
|
423
|
-
} else {
|
|
424
|
-
const primMeta = checkPrimitive(expr);
|
|
425
|
-
if (primMeta) {
|
|
426
|
-
hasToolSource = true;
|
|
427
|
-
toolMetadata = primMeta;
|
|
428
|
-
}
|
|
429
|
-
}
|
|
346
|
+
if (!toolMetadata) {
|
|
347
|
+
toolMetadata = this.checkProvenance(expr);
|
|
430
348
|
}
|
|
431
349
|
}
|
|
432
350
|
}
|
|
433
351
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
352
|
+
if (toolMetadata) {
|
|
353
|
+
this.registerTaintedPrimitive(result, toolMetadata);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return result;
|
|
357
|
+
},
|
|
358
|
+
|
|
359
|
+
// Propagate taint from source to result for native method calls
|
|
360
|
+
propagateTaint(source, args, result) {
|
|
361
|
+
if (__inTrackingCall) return result;
|
|
362
|
+
|
|
363
|
+
__inTrackingCall = true;
|
|
364
|
+
try {
|
|
365
|
+
// Check if source or any arg is tainted
|
|
366
|
+
let metadata = this.checkProvenanceInternal(source);
|
|
367
|
+
|
|
368
|
+
if (!metadata && args) {
|
|
369
|
+
for (let i = 0; i < args.length; i++) {
|
|
370
|
+
metadata = this.checkProvenanceInternal(args[i]);
|
|
371
|
+
if (metadata) break;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Propagate taint to result
|
|
376
|
+
if (metadata) {
|
|
377
|
+
if (typeof result === 'string' || typeof result === 'number') {
|
|
378
|
+
this.registerTaintedPrimitive(result, metadata);
|
|
379
|
+
} else if (Array.isArray(result)) {
|
|
380
|
+
this.deepTaintObject(result, metadata);
|
|
381
|
+
} else if (typeof result === 'object' && result !== null) {
|
|
382
|
+
this.deepTaintObject(result, metadata);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
} finally {
|
|
386
|
+
__inTrackingCall = false;
|
|
444
387
|
}
|
|
445
388
|
|
|
446
389
|
return result;
|
|
@@ -455,13 +398,16 @@ const __astTracker = {
|
|
|
455
398
|
return Array.from(this.metadata.entries());
|
|
456
399
|
},
|
|
457
400
|
|
|
458
|
-
//
|
|
459
|
-
|
|
460
|
-
if (value === null || value === undefined)
|
|
461
|
-
|
|
401
|
+
// Internal provenance check (no re-entry guard - caller must set it)
|
|
402
|
+
checkProvenanceInternal(value) {
|
|
403
|
+
if (value === null || value === undefined) return null;
|
|
404
|
+
|
|
405
|
+
// Fast path: check WeakMap for objects
|
|
406
|
+
if (typeof value === 'object' && this.taintedObjects.has(value)) {
|
|
407
|
+
return this.taintedObjects.get(value);
|
|
462
408
|
}
|
|
463
409
|
|
|
464
|
-
// Check
|
|
410
|
+
// Check object __prov_id__
|
|
465
411
|
if (typeof value === 'object' && value.__prov_id__) {
|
|
466
412
|
const meta = this.metadata.get(value.__prov_id__);
|
|
467
413
|
if (meta && meta.source && meta.source.type === 'tool') {
|
|
@@ -469,7 +415,7 @@ const __astTracker = {
|
|
|
469
415
|
}
|
|
470
416
|
}
|
|
471
417
|
|
|
472
|
-
// Check
|
|
418
|
+
// Check primitive taint
|
|
473
419
|
if (typeof value === 'string' || typeof value === 'number') {
|
|
474
420
|
const taintedKey = 'tainted:' + String(value);
|
|
475
421
|
const taintedMeta = this.metadata.get(taintedKey);
|
|
@@ -477,8 +423,10 @@ const __astTracker = {
|
|
|
477
423
|
return taintedMeta;
|
|
478
424
|
}
|
|
479
425
|
|
|
480
|
-
// Check primitive map
|
|
426
|
+
// Check primitive map (object:key:value format)
|
|
481
427
|
for (const [key, meta] of this.metadata.entries()) {
|
|
428
|
+
// Skip non-string keys (could be Symbols)
|
|
429
|
+
if (typeof key !== 'string') continue;
|
|
482
430
|
if (!key.startsWith('tainted:') && key.includes(':')) {
|
|
483
431
|
const parts = key.split(':');
|
|
484
432
|
if (parts.length >= 3) {
|
|
@@ -490,31 +438,362 @@ const __astTracker = {
|
|
|
490
438
|
}
|
|
491
439
|
}
|
|
492
440
|
|
|
493
|
-
// Check hints
|
|
441
|
+
// Check hints (cross-execution)
|
|
494
442
|
const digest = this.computeDigest(value);
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
443
|
+
if (digest) {
|
|
444
|
+
const hintMeta = this.hints.get(digest);
|
|
445
|
+
if (hintMeta && hintMeta.source && hintMeta.source.type === 'tool') {
|
|
446
|
+
return hintMeta;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Check substring containment in hint values
|
|
451
|
+
if (typeof value === 'string' && this.hintValues && this.hintValues.size > 0) {
|
|
452
|
+
for (const [hintValue, meta] of this.hintValues.entries()) {
|
|
453
|
+
if (value.includes(hintValue) && meta.source && meta.source.type === 'tool') {
|
|
454
|
+
return meta;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
498
457
|
}
|
|
499
458
|
}
|
|
500
459
|
|
|
501
|
-
//
|
|
460
|
+
// Recursively check object properties
|
|
502
461
|
if (typeof value === 'object') {
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
return nestedMeta;
|
|
508
|
-
}
|
|
509
|
-
}
|
|
462
|
+
const keys = Object.keys(value);
|
|
463
|
+
for (let i = 0; i < keys.length; i++) {
|
|
464
|
+
const nestedMeta = this.checkProvenanceInternal(value[keys[i]]);
|
|
465
|
+
if (nestedMeta) return nestedMeta;
|
|
510
466
|
}
|
|
511
467
|
}
|
|
512
468
|
|
|
513
469
|
return null;
|
|
470
|
+
},
|
|
471
|
+
|
|
472
|
+
// Public provenance check with re-entry guard
|
|
473
|
+
checkProvenance(value) {
|
|
474
|
+
if (__inTrackingCall) return null;
|
|
475
|
+
|
|
476
|
+
__inTrackingCall = true;
|
|
477
|
+
try {
|
|
478
|
+
return this.checkProvenanceInternal(value);
|
|
479
|
+
} finally {
|
|
480
|
+
__inTrackingCall = false;
|
|
481
|
+
}
|
|
514
482
|
}
|
|
515
483
|
};
|
|
516
484
|
|
|
517
|
-
//
|
|
485
|
+
// ============================================================================
|
|
486
|
+
// NATIVE METHOD INTERCEPTION
|
|
487
|
+
// We store originals and wrap methods with taint propagation
|
|
488
|
+
// ============================================================================
|
|
489
|
+
|
|
490
|
+
const __originals = {
|
|
491
|
+
String_toUpperCase: String.prototype.toUpperCase,
|
|
492
|
+
String_toLowerCase: String.prototype.toLowerCase,
|
|
493
|
+
String_slice: String.prototype.slice,
|
|
494
|
+
String_substring: String.prototype.substring,
|
|
495
|
+
String_substr: String.prototype.substr,
|
|
496
|
+
String_trim: String.prototype.trim,
|
|
497
|
+
String_trimStart: String.prototype.trimStart,
|
|
498
|
+
String_trimEnd: String.prototype.trimEnd,
|
|
499
|
+
String_replace: String.prototype.replace,
|
|
500
|
+
String_replaceAll: String.prototype.replaceAll,
|
|
501
|
+
String_split: String.prototype.split,
|
|
502
|
+
String_charAt: String.prototype.charAt,
|
|
503
|
+
String_concat: String.prototype.concat,
|
|
504
|
+
String_padStart: String.prototype.padStart,
|
|
505
|
+
String_padEnd: String.prototype.padEnd,
|
|
506
|
+
String_repeat: String.prototype.repeat,
|
|
507
|
+
String_normalize: String.prototype.normalize,
|
|
508
|
+
|
|
509
|
+
Array_map: Array.prototype.map,
|
|
510
|
+
Array_filter: Array.prototype.filter,
|
|
511
|
+
Array_reduce: Array.prototype.reduce,
|
|
512
|
+
Array_reduceRight: Array.prototype.reduceRight,
|
|
513
|
+
Array_join: Array.prototype.join,
|
|
514
|
+
Array_slice: Array.prototype.slice,
|
|
515
|
+
Array_concat: Array.prototype.concat,
|
|
516
|
+
Array_flat: Array.prototype.flat,
|
|
517
|
+
Array_flatMap: Array.prototype.flatMap,
|
|
518
|
+
Array_find: Array.prototype.find,
|
|
519
|
+
Array_every: Array.prototype.every,
|
|
520
|
+
Array_some: Array.prototype.some,
|
|
521
|
+
|
|
522
|
+
Number_toFixed: Number.prototype.toFixed,
|
|
523
|
+
Number_toPrecision: Number.prototype.toPrecision,
|
|
524
|
+
Number_toExponential: Number.prototype.toExponential,
|
|
525
|
+
|
|
526
|
+
JSON_stringify: JSON.stringify,
|
|
527
|
+
JSON_parse: JSON.parse,
|
|
528
|
+
|
|
529
|
+
String_ctor: String,
|
|
530
|
+
Number_ctor: Number,
|
|
531
|
+
parseInt_fn: parseInt,
|
|
532
|
+
parseFloat_fn: parseFloat,
|
|
533
|
+
};
|
|
534
|
+
|
|
535
|
+
// String method wrappers - use this.valueOf() to get primitive for provenance check
|
|
536
|
+
String.prototype.toUpperCase = function() {
|
|
537
|
+
const result = __originals.String_toUpperCase.call(this);
|
|
538
|
+
const primitiveThis = typeof this === 'object' ? this.valueOf() : this;
|
|
539
|
+
return __astTracker.propagateTaint(primitiveThis, [], result);
|
|
540
|
+
};
|
|
541
|
+
|
|
542
|
+
String.prototype.toLowerCase = function() {
|
|
543
|
+
const result = __originals.String_toLowerCase.call(this);
|
|
544
|
+
const primitiveThis = typeof this === 'object' ? this.valueOf() : this;
|
|
545
|
+
return __astTracker.propagateTaint(primitiveThis, [], result);
|
|
546
|
+
};
|
|
547
|
+
|
|
548
|
+
String.prototype.slice = function(start, end) {
|
|
549
|
+
const result = __originals.String_slice.call(this, start, end);
|
|
550
|
+
const primitiveThis = typeof this === 'object' ? this.valueOf() : this;
|
|
551
|
+
return __astTracker.propagateTaint(primitiveThis, [], result);
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
String.prototype.substring = function(start, end) {
|
|
555
|
+
const result = __originals.String_substring.call(this, start, end);
|
|
556
|
+
const primitiveThis = typeof this === 'object' ? this.valueOf() : this;
|
|
557
|
+
return __astTracker.propagateTaint(primitiveThis, [], result);
|
|
558
|
+
};
|
|
559
|
+
|
|
560
|
+
String.prototype.substr = function(start, length) {
|
|
561
|
+
const result = __originals.String_substr.call(this, start, length);
|
|
562
|
+
const primitiveThis = typeof this === 'object' ? this.valueOf() : this;
|
|
563
|
+
return __astTracker.propagateTaint(primitiveThis, [], result);
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
String.prototype.trim = function() {
|
|
567
|
+
const result = __originals.String_trim.call(this);
|
|
568
|
+
const primitiveThis = typeof this === 'object' ? this.valueOf() : this;
|
|
569
|
+
return __astTracker.propagateTaint(primitiveThis, [], result);
|
|
570
|
+
};
|
|
571
|
+
|
|
572
|
+
String.prototype.trimStart = function() {
|
|
573
|
+
const result = __originals.String_trimStart.call(this);
|
|
574
|
+
const primitiveThis = typeof this === 'object' ? this.valueOf() : this;
|
|
575
|
+
return __astTracker.propagateTaint(primitiveThis, [], result);
|
|
576
|
+
};
|
|
577
|
+
|
|
578
|
+
String.prototype.trimEnd = function() {
|
|
579
|
+
const result = __originals.String_trimEnd.call(this);
|
|
580
|
+
const primitiveThis = typeof this === 'object' ? this.valueOf() : this;
|
|
581
|
+
return __astTracker.propagateTaint(primitiveThis, [], result);
|
|
582
|
+
};
|
|
583
|
+
|
|
584
|
+
String.prototype.replace = function(searchValue, replaceValue) {
|
|
585
|
+
const result = __originals.String_replace.call(this, searchValue, replaceValue);
|
|
586
|
+
const primitiveThis = typeof this === 'object' ? this.valueOf() : this;
|
|
587
|
+
return __astTracker.propagateTaint(primitiveThis, [], result);
|
|
588
|
+
};
|
|
589
|
+
|
|
590
|
+
String.prototype.replaceAll = function(searchValue, replaceValue) {
|
|
591
|
+
const result = __originals.String_replaceAll.call(this, searchValue, replaceValue);
|
|
592
|
+
const primitiveThis = typeof this === 'object' ? this.valueOf() : this;
|
|
593
|
+
return __astTracker.propagateTaint(primitiveThis, [], result);
|
|
594
|
+
};
|
|
595
|
+
|
|
596
|
+
String.prototype.split = function(separator, limit) {
|
|
597
|
+
const result = __originals.String_split.call(this, separator, limit);
|
|
598
|
+
const primitiveThis = typeof this === 'object' ? this.valueOf() : this;
|
|
599
|
+
return __astTracker.propagateTaint(primitiveThis, [], result);
|
|
600
|
+
};
|
|
601
|
+
|
|
602
|
+
String.prototype.charAt = function(index) {
|
|
603
|
+
const result = __originals.String_charAt.call(this, index);
|
|
604
|
+
const primitiveThis = typeof this === 'object' ? this.valueOf() : this;
|
|
605
|
+
return __astTracker.propagateTaint(primitiveThis, [], result);
|
|
606
|
+
};
|
|
607
|
+
|
|
608
|
+
String.prototype.concat = function(...args) {
|
|
609
|
+
const result = __originals.String_concat.apply(this, args);
|
|
610
|
+
const primitiveThis = typeof this === 'object' ? this.valueOf() : this;
|
|
611
|
+
return __astTracker.propagateTaint(primitiveThis, args, result);
|
|
612
|
+
};
|
|
613
|
+
|
|
614
|
+
String.prototype.padStart = function(targetLength, padString) {
|
|
615
|
+
const result = __originals.String_padStart.call(this, targetLength, padString);
|
|
616
|
+
const primitiveThis = typeof this === 'object' ? this.valueOf() : this;
|
|
617
|
+
return __astTracker.propagateTaint(primitiveThis, [], result);
|
|
618
|
+
};
|
|
619
|
+
|
|
620
|
+
String.prototype.padEnd = function(targetLength, padString) {
|
|
621
|
+
const result = __originals.String_padEnd.call(this, targetLength, padString);
|
|
622
|
+
const primitiveThis = typeof this === 'object' ? this.valueOf() : this;
|
|
623
|
+
return __astTracker.propagateTaint(primitiveThis, [], result);
|
|
624
|
+
};
|
|
625
|
+
|
|
626
|
+
String.prototype.repeat = function(count) {
|
|
627
|
+
const result = __originals.String_repeat.call(this, count);
|
|
628
|
+
const primitiveThis = typeof this === 'object' ? this.valueOf() : this;
|
|
629
|
+
return __astTracker.propagateTaint(primitiveThis, [], result);
|
|
630
|
+
};
|
|
631
|
+
|
|
632
|
+
String.prototype.normalize = function(form) {
|
|
633
|
+
const result = __originals.String_normalize.call(this, form);
|
|
634
|
+
const primitiveThis = typeof this === 'object' ? this.valueOf() : this;
|
|
635
|
+
return __astTracker.propagateTaint(primitiveThis, [], result);
|
|
636
|
+
};
|
|
637
|
+
|
|
638
|
+
// Array method wrappers
|
|
639
|
+
Array.prototype.map = function(callback, thisArg) {
|
|
640
|
+
const result = __originals.Array_map.call(this, callback, thisArg);
|
|
641
|
+
return __astTracker.propagateTaint(this, [], result);
|
|
642
|
+
};
|
|
643
|
+
|
|
644
|
+
Array.prototype.filter = function(callback, thisArg) {
|
|
645
|
+
const result = __originals.Array_filter.call(this, callback, thisArg);
|
|
646
|
+
return __astTracker.propagateTaint(this, [], result);
|
|
647
|
+
};
|
|
648
|
+
|
|
649
|
+
Array.prototype.reduce = function(callback, initialValue) {
|
|
650
|
+
const result = arguments.length > 1
|
|
651
|
+
? __originals.Array_reduce.call(this, callback, initialValue)
|
|
652
|
+
: __originals.Array_reduce.call(this, callback);
|
|
653
|
+
return __astTracker.propagateTaint(this, [], result);
|
|
654
|
+
};
|
|
655
|
+
|
|
656
|
+
Array.prototype.reduceRight = function(callback, initialValue) {
|
|
657
|
+
const result = arguments.length > 1
|
|
658
|
+
? __originals.Array_reduceRight.call(this, callback, initialValue)
|
|
659
|
+
: __originals.Array_reduceRight.call(this, callback);
|
|
660
|
+
return __astTracker.propagateTaint(this, [], result);
|
|
661
|
+
};
|
|
662
|
+
|
|
663
|
+
Array.prototype.join = function(separator) {
|
|
664
|
+
const result = __originals.Array_join.call(this, separator);
|
|
665
|
+
return __astTracker.propagateTaint(this, [], result);
|
|
666
|
+
};
|
|
667
|
+
|
|
668
|
+
Array.prototype.slice = function(start, end) {
|
|
669
|
+
const result = __originals.Array_slice.call(this, start, end);
|
|
670
|
+
return __astTracker.propagateTaint(this, [], result);
|
|
671
|
+
};
|
|
672
|
+
|
|
673
|
+
Array.prototype.concat = function(...args) {
|
|
674
|
+
const result = __originals.Array_concat.apply(this, args);
|
|
675
|
+
return __astTracker.propagateTaint(this, args, result);
|
|
676
|
+
};
|
|
677
|
+
|
|
678
|
+
Array.prototype.flat = function(depth) {
|
|
679
|
+
const result = __originals.Array_flat.call(this, depth);
|
|
680
|
+
return __astTracker.propagateTaint(this, [], result);
|
|
681
|
+
};
|
|
682
|
+
|
|
683
|
+
Array.prototype.flatMap = function(callback, thisArg) {
|
|
684
|
+
const result = __originals.Array_flatMap.call(this, callback, thisArg);
|
|
685
|
+
return __astTracker.propagateTaint(this, [], result);
|
|
686
|
+
};
|
|
687
|
+
|
|
688
|
+
Array.prototype.find = function(callback, thisArg) {
|
|
689
|
+
const result = __originals.Array_find.call(this, callback, thisArg);
|
|
690
|
+
return __astTracker.propagateTaint(this, [], result);
|
|
691
|
+
};
|
|
692
|
+
|
|
693
|
+
Array.prototype.every = function(callback, thisArg) {
|
|
694
|
+
return __originals.Array_every.call(this, callback, thisArg);
|
|
695
|
+
};
|
|
696
|
+
|
|
697
|
+
Array.prototype.some = function(callback, thisArg) {
|
|
698
|
+
return __originals.Array_some.call(this, callback, thisArg);
|
|
699
|
+
};
|
|
700
|
+
|
|
701
|
+
// Number method wrappers - use this.valueOf() to get primitive for provenance check
|
|
702
|
+
Number.prototype.toFixed = function(digits) {
|
|
703
|
+
const result = __originals.Number_toFixed.call(this, digits);
|
|
704
|
+
const primitiveThis = typeof this === 'object' ? this.valueOf() : this;
|
|
705
|
+
return __astTracker.propagateTaint(primitiveThis, [], result);
|
|
706
|
+
};
|
|
707
|
+
|
|
708
|
+
Number.prototype.toPrecision = function(precision) {
|
|
709
|
+
const result = __originals.Number_toPrecision.call(this, precision);
|
|
710
|
+
const primitiveThis = typeof this === 'object' ? this.valueOf() : this;
|
|
711
|
+
return __astTracker.propagateTaint(primitiveThis, [], result);
|
|
712
|
+
};
|
|
713
|
+
|
|
714
|
+
Number.prototype.toExponential = function(fractionDigits) {
|
|
715
|
+
const result = __originals.Number_toExponential.call(this, fractionDigits);
|
|
716
|
+
const primitiveThis = typeof this === 'object' ? this.valueOf() : this;
|
|
717
|
+
return __astTracker.propagateTaint(primitiveThis, [], result);
|
|
718
|
+
};
|
|
719
|
+
|
|
720
|
+
// JSON wrappers
|
|
721
|
+
JSON.stringify = function(value, replacer, space) {
|
|
722
|
+
const result = __originals.JSON_stringify(value, replacer, space);
|
|
723
|
+
return __astTracker.propagateTaint(value, [], result);
|
|
724
|
+
};
|
|
725
|
+
|
|
726
|
+
JSON.parse = function(text, reviver) {
|
|
727
|
+
const result = __originals.JSON_parse(text, reviver);
|
|
728
|
+
// If input was tainted, taint the parsed object
|
|
729
|
+
if (!__inTrackingCall) {
|
|
730
|
+
__inTrackingCall = true;
|
|
731
|
+
try {
|
|
732
|
+
const inputMeta = __astTracker.checkProvenanceInternal(text);
|
|
733
|
+
if (inputMeta && result && typeof result === 'object') {
|
|
734
|
+
__astTracker.deepTaintObject(result, inputMeta);
|
|
735
|
+
}
|
|
736
|
+
} finally {
|
|
737
|
+
__inTrackingCall = false;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
return result;
|
|
741
|
+
};
|
|
742
|
+
|
|
743
|
+
// Global function wrappers
|
|
744
|
+
const __OrigString = __originals.String_ctor;
|
|
745
|
+
globalThis.String = function(value) {
|
|
746
|
+
if (new.target) {
|
|
747
|
+
return new __OrigString(value);
|
|
748
|
+
}
|
|
749
|
+
const result = __OrigString(value);
|
|
750
|
+
return __astTracker.propagateTaint(value, [], result);
|
|
751
|
+
};
|
|
752
|
+
Object.setPrototypeOf(globalThis.String, __OrigString);
|
|
753
|
+
globalThis.String.prototype = __OrigString.prototype;
|
|
754
|
+
globalThis.String.fromCharCode = __OrigString.fromCharCode;
|
|
755
|
+
globalThis.String.fromCodePoint = __OrigString.fromCodePoint;
|
|
756
|
+
globalThis.String.raw = __OrigString.raw;
|
|
757
|
+
|
|
758
|
+
const __OrigNumber = __originals.Number_ctor;
|
|
759
|
+
globalThis.Number = function(value) {
|
|
760
|
+
if (new.target) {
|
|
761
|
+
return new __OrigNumber(value);
|
|
762
|
+
}
|
|
763
|
+
const result = __OrigNumber(value);
|
|
764
|
+
return __astTracker.propagateTaint(value, [], result);
|
|
765
|
+
};
|
|
766
|
+
Object.setPrototypeOf(globalThis.Number, __OrigNumber);
|
|
767
|
+
globalThis.Number.prototype = __OrigNumber.prototype;
|
|
768
|
+
globalThis.Number.isNaN = __OrigNumber.isNaN;
|
|
769
|
+
globalThis.Number.isFinite = __OrigNumber.isFinite;
|
|
770
|
+
globalThis.Number.isInteger = __OrigNumber.isInteger;
|
|
771
|
+
globalThis.Number.isSafeInteger = __OrigNumber.isSafeInteger;
|
|
772
|
+
globalThis.Number.parseFloat = __OrigNumber.parseFloat;
|
|
773
|
+
globalThis.Number.parseInt = __OrigNumber.parseInt;
|
|
774
|
+
globalThis.Number.MAX_VALUE = __OrigNumber.MAX_VALUE;
|
|
775
|
+
globalThis.Number.MIN_VALUE = __OrigNumber.MIN_VALUE;
|
|
776
|
+
globalThis.Number.NaN = __OrigNumber.NaN;
|
|
777
|
+
globalThis.Number.NEGATIVE_INFINITY = __OrigNumber.NEGATIVE_INFINITY;
|
|
778
|
+
globalThis.Number.POSITIVE_INFINITY = __OrigNumber.POSITIVE_INFINITY;
|
|
779
|
+
globalThis.Number.MAX_SAFE_INTEGER = __OrigNumber.MAX_SAFE_INTEGER;
|
|
780
|
+
globalThis.Number.MIN_SAFE_INTEGER = __OrigNumber.MIN_SAFE_INTEGER;
|
|
781
|
+
globalThis.Number.EPSILON = __OrigNumber.EPSILON;
|
|
782
|
+
|
|
783
|
+
globalThis.parseInt = function(string, radix) {
|
|
784
|
+
const result = __originals.parseInt_fn(string, radix);
|
|
785
|
+
return __astTracker.propagateTaint(string, [], result);
|
|
786
|
+
};
|
|
787
|
+
|
|
788
|
+
globalThis.parseFloat = function(string) {
|
|
789
|
+
const result = __originals.parseFloat_fn(string);
|
|
790
|
+
return __astTracker.propagateTaint(string, [], result);
|
|
791
|
+
};
|
|
792
|
+
|
|
793
|
+
// ============================================================================
|
|
794
|
+
// GLOBAL TRACKING FUNCTIONS
|
|
795
|
+
// ============================================================================
|
|
796
|
+
|
|
518
797
|
globalThis.__track = (v, s, d) => __astTracker.track(v, s, d);
|
|
519
798
|
globalThis.__track_binary = (l, r, o) => __astTracker.trackBinary(l, r, o);
|
|
520
799
|
globalThis.__track_assign = (n, v) => __astTracker.trackAssign(n, v);
|
|
@@ -524,35 +803,30 @@ globalThis.__get_provenance = (v) => __astTracker.getMetadata(v);
|
|
|
524
803
|
globalThis.__get_all_metadata = () => __astTracker.getAllMetadata();
|
|
525
804
|
globalThis.__check_provenance = (v) => __astTracker.checkProvenance(v);
|
|
526
805
|
|
|
527
|
-
// Mark a string literal as tainted (for cross-execution tracking)
|
|
528
806
|
globalThis.__mark_tainted = (value) => {
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
// ALSO check if this value CONTAINS any hint values (substring match)
|
|
539
|
-
// This enables cross-execution tracking for template literals/concatenation
|
|
540
|
-
if (typeof value === 'string' && __astTracker.hintValues && __astTracker.hintValues.size > 0) {
|
|
541
|
-
for (const [hintValue, metadata] of __astTracker.hintValues.entries()) {
|
|
542
|
-
if (value.includes(hintValue)) {
|
|
543
|
-
const taintedKey = 'tainted:' + String(value);
|
|
544
|
-
// Ensure metadata has all required fields, use restricted readers for tool data
|
|
545
|
-
const fullMetadata = {
|
|
546
|
-
...metadata,
|
|
547
|
-
readers: metadata.readers || { type: 'restricted', readers: [] },
|
|
548
|
-
dependencies: metadata.dependencies || metadata.deps || []
|
|
549
|
-
};
|
|
550
|
-
__astTracker.metadata.set(taintedKey, fullMetadata);
|
|
807
|
+
if (__inTrackingCall) return value;
|
|
808
|
+
__inTrackingCall = true;
|
|
809
|
+
try {
|
|
810
|
+
const digest = __astTracker.computeDigest(value);
|
|
811
|
+
if (digest) {
|
|
812
|
+
const hintMeta = __astTracker.hints.get(digest);
|
|
813
|
+
if (hintMeta) {
|
|
814
|
+
__astTracker.registerTaintedPrimitive(value, hintMeta);
|
|
551
815
|
return value;
|
|
552
816
|
}
|
|
553
817
|
}
|
|
818
|
+
|
|
819
|
+
if (typeof value === 'string' && __astTracker.hintValues && __astTracker.hintValues.size > 0) {
|
|
820
|
+
for (const [hintValue, metadata] of __astTracker.hintValues.entries()) {
|
|
821
|
+
if (value.includes(hintValue)) {
|
|
822
|
+
__astTracker.registerTaintedPrimitive(value, metadata);
|
|
823
|
+
return value;
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
} finally {
|
|
828
|
+
__inTrackingCall = false;
|
|
554
829
|
}
|
|
555
|
-
|
|
556
830
|
return value;
|
|
557
831
|
};
|
|
558
832
|
`;
|