just-bash 2.9.2 → 2.9.3
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/bin/chunks/chunk-HSNUCOOQ.js +6 -0
- package/dist/bin/chunks/chunk-UYBH3FNE.js +2 -0
- package/dist/bin/{shell/chunks/curl-7IUASLUJ.js → chunks/curl-RWQO3SBN.js} +18 -18
- package/dist/bin/{shell/chunks/jq-BMOLDA72.js → chunks/jq-EIPK4SZA.js} +1 -1
- package/dist/bin/chunks/worker.js +746 -2
- package/dist/bin/{shell/chunks/xan-EAK3S7KJ.js → chunks/xan-QLNZCWIG.js} +35 -35
- package/dist/bin/chunks/{yq-WYJ3A4JF.js → yq-XD7UYY5C.js} +1 -1
- package/dist/bin/just-bash.js +2 -2
- package/dist/bin/shell/chunks/chunk-HSNUCOOQ.js +6 -0
- package/dist/bin/shell/chunks/chunk-UYBH3FNE.js +2 -0
- package/dist/bin/{chunks/curl-7IUASLUJ.js → shell/chunks/curl-RWQO3SBN.js} +18 -18
- package/dist/bin/{chunks/jq-BMOLDA72.js → shell/chunks/jq-EIPK4SZA.js} +1 -1
- package/dist/bin/{chunks/xan-EAK3S7KJ.js → shell/chunks/xan-QLNZCWIG.js} +35 -35
- package/dist/bin/shell/chunks/{yq-WYJ3A4JF.js → yq-XD7UYY5C.js} +1 -1
- package/dist/bin/shell/shell.js +2 -2
- package/dist/bundle/browser.js +464 -464
- package/dist/bundle/chunks/chunk-7MKBHGLS.js +1 -0
- package/dist/bundle/chunks/chunk-WQCJYUEW.js +5 -0
- package/dist/bundle/chunks/{curl-QDCXHQMX.js → curl-6L7YZUIH.js} +18 -18
- package/dist/bundle/chunks/{jq-GMMYKAEP.js → jq-6U2TPE6U.js} +1 -1
- package/dist/bundle/chunks/worker.js +746 -2
- package/dist/bundle/chunks/{xan-DYADHWWG.js → xan-EN6JZQ3T.js} +35 -35
- package/dist/bundle/chunks/{yq-5WHXPM6R.js → yq-JD6M7T5A.js} +1 -1
- package/dist/bundle/index.js +193 -193
- package/dist/commands/python3/worker.d.ts +6 -0
- package/dist/commands/query-engine/safe-object.d.ts +10 -0
- package/dist/commands/query-engine/value-operations.d.ts +1 -0
- package/dist/commands/sqlite3/worker.d.ts +10 -0
- package/dist/fs/read-write-fs/read-write-fs.d.ts +4 -1
- package/package.json +1 -1
- package/dist/bin/chunks/chunk-FJTZ5HFK.js +0 -6
- package/dist/bin/shell/chunks/chunk-FJTZ5HFK.js +0 -6
- package/dist/bundle/chunks/chunk-IHEPHGKB.js +0 -5
|
@@ -2,6 +2,724 @@
|
|
|
2
2
|
import { parentPort, workerData } from "node:worker_threads";
|
|
3
3
|
import { loadPyodide } from "pyodide";
|
|
4
4
|
|
|
5
|
+
// src/security/blocked-globals.ts
|
|
6
|
+
function getBlockedGlobals() {
|
|
7
|
+
const globals = [
|
|
8
|
+
// Direct code execution vectors
|
|
9
|
+
{
|
|
10
|
+
prop: "Function",
|
|
11
|
+
target: globalThis,
|
|
12
|
+
violationType: "function_constructor",
|
|
13
|
+
strategy: "throw",
|
|
14
|
+
reason: "Function constructor allows arbitrary code execution"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
prop: "eval",
|
|
18
|
+
target: globalThis,
|
|
19
|
+
violationType: "eval",
|
|
20
|
+
strategy: "throw",
|
|
21
|
+
reason: "eval() allows arbitrary code execution"
|
|
22
|
+
},
|
|
23
|
+
// Timer functions with string argument allow code execution
|
|
24
|
+
{
|
|
25
|
+
prop: "setTimeout",
|
|
26
|
+
target: globalThis,
|
|
27
|
+
violationType: "setTimeout",
|
|
28
|
+
strategy: "throw",
|
|
29
|
+
reason: "setTimeout with string argument allows code execution"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
prop: "setInterval",
|
|
33
|
+
target: globalThis,
|
|
34
|
+
violationType: "setInterval",
|
|
35
|
+
strategy: "throw",
|
|
36
|
+
reason: "setInterval with string argument allows code execution"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
prop: "setImmediate",
|
|
40
|
+
target: globalThis,
|
|
41
|
+
violationType: "setImmediate",
|
|
42
|
+
strategy: "throw",
|
|
43
|
+
reason: "setImmediate could be used to escape sandbox context"
|
|
44
|
+
},
|
|
45
|
+
// Note: We intentionally do NOT block `process` entirely because:
|
|
46
|
+
// 1. Node.js internals (Promise resolution, etc.) use process.nextTick
|
|
47
|
+
// 2. Blocking process entirely breaks normal async operation
|
|
48
|
+
// 3. The primary code execution vectors (Function, eval) are already blocked
|
|
49
|
+
// However, we DO block specific dangerous process properties.
|
|
50
|
+
{
|
|
51
|
+
prop: "env",
|
|
52
|
+
target: process,
|
|
53
|
+
violationType: "process_env",
|
|
54
|
+
strategy: "throw",
|
|
55
|
+
reason: "process.env could leak sensitive environment variables"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
prop: "binding",
|
|
59
|
+
target: process,
|
|
60
|
+
violationType: "process_binding",
|
|
61
|
+
strategy: "throw",
|
|
62
|
+
reason: "process.binding provides access to native Node.js modules"
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
prop: "_linkedBinding",
|
|
66
|
+
target: process,
|
|
67
|
+
violationType: "process_binding",
|
|
68
|
+
strategy: "throw",
|
|
69
|
+
reason: "process._linkedBinding provides access to native Node.js modules"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
prop: "dlopen",
|
|
73
|
+
target: process,
|
|
74
|
+
violationType: "process_dlopen",
|
|
75
|
+
strategy: "throw",
|
|
76
|
+
reason: "process.dlopen allows loading native addons"
|
|
77
|
+
},
|
|
78
|
+
// We also don't block `require` because:
|
|
79
|
+
// 1. It may not exist in all environments (ESM)
|
|
80
|
+
// 2. import() is the modern escape vector and can't be blocked this way
|
|
81
|
+
// Reference leak vectors
|
|
82
|
+
{
|
|
83
|
+
prop: "WeakRef",
|
|
84
|
+
target: globalThis,
|
|
85
|
+
violationType: "weak_ref",
|
|
86
|
+
strategy: "throw",
|
|
87
|
+
reason: "WeakRef could be used to leak references outside sandbox"
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
prop: "FinalizationRegistry",
|
|
91
|
+
target: globalThis,
|
|
92
|
+
violationType: "finalization_registry",
|
|
93
|
+
strategy: "throw",
|
|
94
|
+
reason: "FinalizationRegistry could be used to leak references outside sandbox"
|
|
95
|
+
},
|
|
96
|
+
// Introspection/interception vectors (freeze instead of throw)
|
|
97
|
+
{
|
|
98
|
+
prop: "Reflect",
|
|
99
|
+
target: globalThis,
|
|
100
|
+
violationType: "reflect",
|
|
101
|
+
strategy: "freeze",
|
|
102
|
+
reason: "Reflect provides introspection capabilities"
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
prop: "Proxy",
|
|
106
|
+
target: globalThis,
|
|
107
|
+
violationType: "proxy",
|
|
108
|
+
strategy: "throw",
|
|
109
|
+
reason: "Proxy allows intercepting and modifying object behavior"
|
|
110
|
+
},
|
|
111
|
+
// WebAssembly allows arbitrary code execution
|
|
112
|
+
{
|
|
113
|
+
prop: "WebAssembly",
|
|
114
|
+
target: globalThis,
|
|
115
|
+
violationType: "webassembly",
|
|
116
|
+
strategy: "throw",
|
|
117
|
+
reason: "WebAssembly allows executing arbitrary compiled code"
|
|
118
|
+
}
|
|
119
|
+
// Note: Error.prepareStackTrace is handled specially in defense-in-depth-box.ts
|
|
120
|
+
// because we only want to block SETTING it, not reading (V8 reads it internally)
|
|
121
|
+
];
|
|
122
|
+
try {
|
|
123
|
+
const AsyncFunction = Object.getPrototypeOf(async () => {
|
|
124
|
+
}).constructor;
|
|
125
|
+
if (AsyncFunction && AsyncFunction !== Function) {
|
|
126
|
+
globals.push({
|
|
127
|
+
prop: "constructor",
|
|
128
|
+
target: Object.getPrototypeOf(async () => {
|
|
129
|
+
}),
|
|
130
|
+
violationType: "async_function_constructor",
|
|
131
|
+
strategy: "throw",
|
|
132
|
+
reason: "AsyncFunction constructor allows arbitrary async code execution"
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
} catch {
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
const GeneratorFunction = Object.getPrototypeOf(
|
|
139
|
+
function* () {
|
|
140
|
+
}
|
|
141
|
+
).constructor;
|
|
142
|
+
if (GeneratorFunction && GeneratorFunction !== Function) {
|
|
143
|
+
globals.push({
|
|
144
|
+
prop: "constructor",
|
|
145
|
+
target: Object.getPrototypeOf(function* () {
|
|
146
|
+
}),
|
|
147
|
+
violationType: "generator_function_constructor",
|
|
148
|
+
strategy: "throw",
|
|
149
|
+
reason: "GeneratorFunction constructor allows arbitrary generator code execution"
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
} catch {
|
|
153
|
+
}
|
|
154
|
+
try {
|
|
155
|
+
const AsyncGeneratorFunction = Object.getPrototypeOf(
|
|
156
|
+
async function* () {
|
|
157
|
+
}
|
|
158
|
+
).constructor;
|
|
159
|
+
if (AsyncGeneratorFunction && AsyncGeneratorFunction !== Function && AsyncGeneratorFunction !== Object.getPrototypeOf(async () => {
|
|
160
|
+
}).constructor) {
|
|
161
|
+
globals.push({
|
|
162
|
+
prop: "constructor",
|
|
163
|
+
target: Object.getPrototypeOf(async function* () {
|
|
164
|
+
}),
|
|
165
|
+
violationType: "async_generator_function_constructor",
|
|
166
|
+
strategy: "throw",
|
|
167
|
+
reason: "AsyncGeneratorFunction constructor allows arbitrary async generator code execution"
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
} catch {
|
|
171
|
+
}
|
|
172
|
+
return globals.filter((g) => {
|
|
173
|
+
try {
|
|
174
|
+
return g.target[g.prop] !== void 0;
|
|
175
|
+
} catch {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// src/security/defense-in-depth-box.ts
|
|
182
|
+
var IS_BROWSER = typeof __BROWSER__ !== "undefined" && __BROWSER__;
|
|
183
|
+
var AsyncLocalStorageClass = null;
|
|
184
|
+
if (!IS_BROWSER) {
|
|
185
|
+
try {
|
|
186
|
+
const { createRequire } = await import("node:module");
|
|
187
|
+
const require2 = createRequire(import.meta.url);
|
|
188
|
+
const asyncHooks = require2("node:async_hooks");
|
|
189
|
+
AsyncLocalStorageClass = asyncHooks.AsyncLocalStorage;
|
|
190
|
+
} catch (e) {
|
|
191
|
+
console.debug(
|
|
192
|
+
"[DefenseInDepthBox] AsyncLocalStorage not available, defense-in-depth disabled:",
|
|
193
|
+
e instanceof Error ? e.message : e
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
var executionContext = !IS_BROWSER && AsyncLocalStorageClass ? new AsyncLocalStorageClass() : null;
|
|
198
|
+
|
|
199
|
+
// src/security/worker-defense-in-depth.ts
|
|
200
|
+
var DEFENSE_IN_DEPTH_NOTICE = "\n\nThis is a defense-in-depth measure and indicates a bug in just-bash. Please report this at security@vercel.com";
|
|
201
|
+
var WorkerSecurityViolationError = class extends Error {
|
|
202
|
+
constructor(message, violation) {
|
|
203
|
+
super(message + DEFENSE_IN_DEPTH_NOTICE);
|
|
204
|
+
this.violation = violation;
|
|
205
|
+
this.name = "WorkerSecurityViolationError";
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
var MAX_STORED_VIOLATIONS = 1e3;
|
|
209
|
+
function generateExecutionId() {
|
|
210
|
+
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
211
|
+
return crypto.randomUUID();
|
|
212
|
+
}
|
|
213
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
214
|
+
const r = Math.random() * 16 | 0;
|
|
215
|
+
const v = c === "x" ? r : r & 3 | 8;
|
|
216
|
+
return v.toString(16);
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
var WorkerDefenseInDepth = class {
|
|
220
|
+
config;
|
|
221
|
+
isActivated = false;
|
|
222
|
+
originalDescriptors = [];
|
|
223
|
+
violations = [];
|
|
224
|
+
executionId;
|
|
225
|
+
/**
|
|
226
|
+
* Original Proxy constructor, captured before patching.
|
|
227
|
+
* This is captured at instance creation time to ensure we get the unpatched version.
|
|
228
|
+
*/
|
|
229
|
+
originalProxy;
|
|
230
|
+
/**
|
|
231
|
+
* Recursion guard to prevent infinite loops when proxy traps trigger
|
|
232
|
+
* code that accesses the same proxied object (e.g., process.env).
|
|
233
|
+
*/
|
|
234
|
+
inTrap = false;
|
|
235
|
+
/**
|
|
236
|
+
* Create and activate the worker defense layer.
|
|
237
|
+
*
|
|
238
|
+
* @param config - Configuration for the defense layer
|
|
239
|
+
*/
|
|
240
|
+
constructor(config) {
|
|
241
|
+
this.originalProxy = Proxy;
|
|
242
|
+
this.config = config;
|
|
243
|
+
this.executionId = generateExecutionId();
|
|
244
|
+
if (config.enabled !== false) {
|
|
245
|
+
this.activate();
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Get statistics about the defense layer.
|
|
250
|
+
*/
|
|
251
|
+
getStats() {
|
|
252
|
+
return {
|
|
253
|
+
violationsBlocked: this.violations.length,
|
|
254
|
+
violations: [...this.violations],
|
|
255
|
+
isActive: this.isActivated
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Clear stored violations. Useful for testing.
|
|
260
|
+
*/
|
|
261
|
+
clearViolations() {
|
|
262
|
+
this.violations = [];
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Get the execution ID for this worker.
|
|
266
|
+
*/
|
|
267
|
+
getExecutionId() {
|
|
268
|
+
return this.executionId;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Deactivate the defense layer and restore original globals.
|
|
272
|
+
* Typically only needed for testing.
|
|
273
|
+
*/
|
|
274
|
+
deactivate() {
|
|
275
|
+
if (!this.isActivated) {
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
this.restorePatches();
|
|
279
|
+
this.isActivated = false;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Activate the defense layer by applying patches.
|
|
283
|
+
*/
|
|
284
|
+
activate() {
|
|
285
|
+
if (this.isActivated) {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
this.applyPatches();
|
|
289
|
+
this.isActivated = true;
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Get a human-readable path for a target object and property.
|
|
293
|
+
*/
|
|
294
|
+
getPathForTarget(target, prop) {
|
|
295
|
+
if (target === globalThis) {
|
|
296
|
+
return `globalThis.${prop}`;
|
|
297
|
+
}
|
|
298
|
+
if (typeof process !== "undefined" && target === process) {
|
|
299
|
+
return `process.${prop}`;
|
|
300
|
+
}
|
|
301
|
+
if (target === Error) {
|
|
302
|
+
return `Error.${prop}`;
|
|
303
|
+
}
|
|
304
|
+
if (target === Function.prototype) {
|
|
305
|
+
return `Function.prototype.${prop}`;
|
|
306
|
+
}
|
|
307
|
+
if (target === Object.prototype) {
|
|
308
|
+
return `Object.prototype.${prop}`;
|
|
309
|
+
}
|
|
310
|
+
return `<object>.${prop}`;
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Record a violation and invoke the callback.
|
|
314
|
+
* In worker context, blocking always happens (no audit mode context check).
|
|
315
|
+
*/
|
|
316
|
+
recordViolation(type, path, message) {
|
|
317
|
+
const violation = {
|
|
318
|
+
timestamp: Date.now(),
|
|
319
|
+
type,
|
|
320
|
+
message,
|
|
321
|
+
path,
|
|
322
|
+
stack: new Error().stack,
|
|
323
|
+
executionId: this.executionId
|
|
324
|
+
};
|
|
325
|
+
if (this.violations.length < MAX_STORED_VIOLATIONS) {
|
|
326
|
+
this.violations.push(violation);
|
|
327
|
+
}
|
|
328
|
+
if (this.config.onViolation) {
|
|
329
|
+
try {
|
|
330
|
+
this.config.onViolation(violation);
|
|
331
|
+
} catch (e) {
|
|
332
|
+
console.debug(
|
|
333
|
+
"[WorkerDefenseInDepth] onViolation callback threw:",
|
|
334
|
+
e instanceof Error ? e.message : e
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return violation;
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Create a blocking proxy for a function.
|
|
342
|
+
* In worker context, always blocks (no context check needed).
|
|
343
|
+
*/
|
|
344
|
+
// @banned-pattern-ignore: intentional use of Function type for security proxy
|
|
345
|
+
createBlockingProxy(original, path, violationType) {
|
|
346
|
+
const self = this;
|
|
347
|
+
const auditMode = this.config.auditMode;
|
|
348
|
+
return new this.originalProxy(original, {
|
|
349
|
+
apply(target, thisArg, args) {
|
|
350
|
+
const message = `${path} is blocked in worker context`;
|
|
351
|
+
const violation = self.recordViolation(violationType, path, message);
|
|
352
|
+
if (!auditMode) {
|
|
353
|
+
throw new WorkerSecurityViolationError(message, violation);
|
|
354
|
+
}
|
|
355
|
+
return Reflect.apply(target, thisArg, args);
|
|
356
|
+
},
|
|
357
|
+
construct(target, args, newTarget) {
|
|
358
|
+
const message = `${path} constructor is blocked in worker context`;
|
|
359
|
+
const violation = self.recordViolation(violationType, path, message);
|
|
360
|
+
if (!auditMode) {
|
|
361
|
+
throw new WorkerSecurityViolationError(message, violation);
|
|
362
|
+
}
|
|
363
|
+
return Reflect.construct(target, args, newTarget);
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Create a blocking proxy for an object (blocks all property access).
|
|
369
|
+
*/
|
|
370
|
+
createBlockingObjectProxy(original, path, violationType) {
|
|
371
|
+
const self = this;
|
|
372
|
+
const auditMode = this.config.auditMode;
|
|
373
|
+
return new this.originalProxy(original, {
|
|
374
|
+
get(target, prop, receiver) {
|
|
375
|
+
if (self.inTrap) {
|
|
376
|
+
return Reflect.get(target, prop, receiver);
|
|
377
|
+
}
|
|
378
|
+
self.inTrap = true;
|
|
379
|
+
try {
|
|
380
|
+
const fullPath = `${path}.${String(prop)}`;
|
|
381
|
+
const message = `${fullPath} is blocked in worker context`;
|
|
382
|
+
const violation = self.recordViolation(
|
|
383
|
+
violationType,
|
|
384
|
+
fullPath,
|
|
385
|
+
message
|
|
386
|
+
);
|
|
387
|
+
if (!auditMode) {
|
|
388
|
+
throw new WorkerSecurityViolationError(message, violation);
|
|
389
|
+
}
|
|
390
|
+
return Reflect.get(target, prop, receiver);
|
|
391
|
+
} finally {
|
|
392
|
+
self.inTrap = false;
|
|
393
|
+
}
|
|
394
|
+
},
|
|
395
|
+
set(target, prop, value, receiver) {
|
|
396
|
+
if (self.inTrap) {
|
|
397
|
+
return Reflect.set(target, prop, value, receiver);
|
|
398
|
+
}
|
|
399
|
+
self.inTrap = true;
|
|
400
|
+
try {
|
|
401
|
+
const fullPath = `${path}.${String(prop)}`;
|
|
402
|
+
const message = `${fullPath} modification is blocked in worker context`;
|
|
403
|
+
const violation = self.recordViolation(
|
|
404
|
+
violationType,
|
|
405
|
+
fullPath,
|
|
406
|
+
message
|
|
407
|
+
);
|
|
408
|
+
if (!auditMode) {
|
|
409
|
+
throw new WorkerSecurityViolationError(message, violation);
|
|
410
|
+
}
|
|
411
|
+
return Reflect.set(target, prop, value, receiver);
|
|
412
|
+
} finally {
|
|
413
|
+
self.inTrap = false;
|
|
414
|
+
}
|
|
415
|
+
},
|
|
416
|
+
ownKeys(target) {
|
|
417
|
+
if (self.inTrap) {
|
|
418
|
+
return Reflect.ownKeys(target);
|
|
419
|
+
}
|
|
420
|
+
self.inTrap = true;
|
|
421
|
+
try {
|
|
422
|
+
const message = `${path} enumeration is blocked in worker context`;
|
|
423
|
+
const violation = self.recordViolation(violationType, path, message);
|
|
424
|
+
if (!auditMode) {
|
|
425
|
+
throw new WorkerSecurityViolationError(message, violation);
|
|
426
|
+
}
|
|
427
|
+
return Reflect.ownKeys(target);
|
|
428
|
+
} finally {
|
|
429
|
+
self.inTrap = false;
|
|
430
|
+
}
|
|
431
|
+
},
|
|
432
|
+
getOwnPropertyDescriptor(target, prop) {
|
|
433
|
+
if (self.inTrap) {
|
|
434
|
+
return Reflect.getOwnPropertyDescriptor(target, prop);
|
|
435
|
+
}
|
|
436
|
+
self.inTrap = true;
|
|
437
|
+
try {
|
|
438
|
+
const fullPath = `${path}.${String(prop)}`;
|
|
439
|
+
const message = `${fullPath} descriptor access is blocked in worker context`;
|
|
440
|
+
const violation = self.recordViolation(
|
|
441
|
+
violationType,
|
|
442
|
+
fullPath,
|
|
443
|
+
message
|
|
444
|
+
);
|
|
445
|
+
if (!auditMode) {
|
|
446
|
+
throw new WorkerSecurityViolationError(message, violation);
|
|
447
|
+
}
|
|
448
|
+
return Reflect.getOwnPropertyDescriptor(target, prop);
|
|
449
|
+
} finally {
|
|
450
|
+
self.inTrap = false;
|
|
451
|
+
}
|
|
452
|
+
},
|
|
453
|
+
has(target, prop) {
|
|
454
|
+
if (self.inTrap) {
|
|
455
|
+
return Reflect.has(target, prop);
|
|
456
|
+
}
|
|
457
|
+
self.inTrap = true;
|
|
458
|
+
try {
|
|
459
|
+
const fullPath = `${path}.${String(prop)}`;
|
|
460
|
+
const message = `${fullPath} existence check is blocked in worker context`;
|
|
461
|
+
const violation = self.recordViolation(
|
|
462
|
+
violationType,
|
|
463
|
+
fullPath,
|
|
464
|
+
message
|
|
465
|
+
);
|
|
466
|
+
if (!auditMode) {
|
|
467
|
+
throw new WorkerSecurityViolationError(message, violation);
|
|
468
|
+
}
|
|
469
|
+
return Reflect.has(target, prop);
|
|
470
|
+
} finally {
|
|
471
|
+
self.inTrap = false;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Apply security patches to dangerous globals.
|
|
478
|
+
*/
|
|
479
|
+
applyPatches() {
|
|
480
|
+
const blockedGlobals = getBlockedGlobals();
|
|
481
|
+
const excludeTypes = new Set(this.config.excludeViolationTypes ?? []);
|
|
482
|
+
for (const blocked of blockedGlobals) {
|
|
483
|
+
if (excludeTypes.has(blocked.violationType)) {
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
this.applyPatch(blocked);
|
|
487
|
+
}
|
|
488
|
+
if (!excludeTypes.has("function_constructor")) {
|
|
489
|
+
this.protectConstructorChain(excludeTypes);
|
|
490
|
+
}
|
|
491
|
+
if (!excludeTypes.has("error_prepare_stack_trace")) {
|
|
492
|
+
this.protectErrorPrepareStackTrace();
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Protect against .constructor.constructor escape vector.
|
|
497
|
+
* @param excludeTypes - Set of violation types to skip
|
|
498
|
+
*/
|
|
499
|
+
protectConstructorChain(excludeTypes) {
|
|
500
|
+
let AsyncFunction = null;
|
|
501
|
+
let GeneratorFunction = null;
|
|
502
|
+
let AsyncGeneratorFunction = null;
|
|
503
|
+
try {
|
|
504
|
+
AsyncFunction = Object.getPrototypeOf(async () => {
|
|
505
|
+
}).constructor;
|
|
506
|
+
} catch {
|
|
507
|
+
}
|
|
508
|
+
try {
|
|
509
|
+
GeneratorFunction = Object.getPrototypeOf(function* () {
|
|
510
|
+
}).constructor;
|
|
511
|
+
} catch {
|
|
512
|
+
}
|
|
513
|
+
try {
|
|
514
|
+
AsyncGeneratorFunction = Object.getPrototypeOf(
|
|
515
|
+
async function* () {
|
|
516
|
+
}
|
|
517
|
+
).constructor;
|
|
518
|
+
} catch {
|
|
519
|
+
}
|
|
520
|
+
this.patchPrototypeConstructor(
|
|
521
|
+
Function.prototype,
|
|
522
|
+
"Function.prototype.constructor",
|
|
523
|
+
"function_constructor"
|
|
524
|
+
);
|
|
525
|
+
if (!excludeTypes.has("async_function_constructor") && AsyncFunction && AsyncFunction !== Function) {
|
|
526
|
+
this.patchPrototypeConstructor(
|
|
527
|
+
AsyncFunction.prototype,
|
|
528
|
+
"AsyncFunction.prototype.constructor",
|
|
529
|
+
"async_function_constructor"
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
if (!excludeTypes.has("generator_function_constructor") && GeneratorFunction && GeneratorFunction !== Function) {
|
|
533
|
+
this.patchPrototypeConstructor(
|
|
534
|
+
GeneratorFunction.prototype,
|
|
535
|
+
"GeneratorFunction.prototype.constructor",
|
|
536
|
+
"generator_function_constructor"
|
|
537
|
+
);
|
|
538
|
+
}
|
|
539
|
+
if (!excludeTypes.has("async_generator_function_constructor") && AsyncGeneratorFunction && AsyncGeneratorFunction !== Function && AsyncGeneratorFunction !== AsyncFunction) {
|
|
540
|
+
this.patchPrototypeConstructor(
|
|
541
|
+
AsyncGeneratorFunction.prototype,
|
|
542
|
+
"AsyncGeneratorFunction.prototype.constructor",
|
|
543
|
+
"async_generator_function_constructor"
|
|
544
|
+
);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Protect Error.prepareStackTrace from being set.
|
|
549
|
+
*/
|
|
550
|
+
protectErrorPrepareStackTrace() {
|
|
551
|
+
const self = this;
|
|
552
|
+
const auditMode = this.config.auditMode;
|
|
553
|
+
try {
|
|
554
|
+
const originalDescriptor = Object.getOwnPropertyDescriptor(
|
|
555
|
+
Error,
|
|
556
|
+
"prepareStackTrace"
|
|
557
|
+
);
|
|
558
|
+
this.originalDescriptors.push({
|
|
559
|
+
target: Error,
|
|
560
|
+
prop: "prepareStackTrace",
|
|
561
|
+
descriptor: originalDescriptor
|
|
562
|
+
});
|
|
563
|
+
let currentValue = originalDescriptor?.value;
|
|
564
|
+
Object.defineProperty(Error, "prepareStackTrace", {
|
|
565
|
+
get() {
|
|
566
|
+
return currentValue;
|
|
567
|
+
},
|
|
568
|
+
set(value) {
|
|
569
|
+
const message = "Error.prepareStackTrace modification is blocked in worker context";
|
|
570
|
+
const violation = self.recordViolation(
|
|
571
|
+
"error_prepare_stack_trace",
|
|
572
|
+
"Error.prepareStackTrace",
|
|
573
|
+
message
|
|
574
|
+
);
|
|
575
|
+
if (!auditMode) {
|
|
576
|
+
throw new WorkerSecurityViolationError(message, violation);
|
|
577
|
+
}
|
|
578
|
+
currentValue = value;
|
|
579
|
+
},
|
|
580
|
+
configurable: true
|
|
581
|
+
});
|
|
582
|
+
} catch {
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Patch a prototype's constructor property.
|
|
587
|
+
*
|
|
588
|
+
* Returns a proxy that allows reading properties (like .name) but blocks
|
|
589
|
+
* calling the constructor as a function (which would allow code execution).
|
|
590
|
+
*/
|
|
591
|
+
patchPrototypeConstructor(prototype, path, violationType) {
|
|
592
|
+
const self = this;
|
|
593
|
+
const auditMode = this.config.auditMode;
|
|
594
|
+
try {
|
|
595
|
+
const originalDescriptor = Object.getOwnPropertyDescriptor(
|
|
596
|
+
prototype,
|
|
597
|
+
"constructor"
|
|
598
|
+
);
|
|
599
|
+
this.originalDescriptors.push({
|
|
600
|
+
target: prototype,
|
|
601
|
+
prop: "constructor",
|
|
602
|
+
descriptor: originalDescriptor
|
|
603
|
+
});
|
|
604
|
+
const originalValue = originalDescriptor?.value;
|
|
605
|
+
const constructorProxy = originalValue && typeof originalValue === "function" ? new this.originalProxy(originalValue, {
|
|
606
|
+
apply(_target, _thisArg, _args) {
|
|
607
|
+
const message = `${path} invocation is blocked in worker context`;
|
|
608
|
+
const violation = self.recordViolation(
|
|
609
|
+
violationType,
|
|
610
|
+
path,
|
|
611
|
+
message
|
|
612
|
+
);
|
|
613
|
+
if (!auditMode) {
|
|
614
|
+
throw new WorkerSecurityViolationError(message, violation);
|
|
615
|
+
}
|
|
616
|
+
return void 0;
|
|
617
|
+
},
|
|
618
|
+
construct(_target, _args, _newTarget) {
|
|
619
|
+
const message = `${path} construction is blocked in worker context`;
|
|
620
|
+
const violation = self.recordViolation(
|
|
621
|
+
violationType,
|
|
622
|
+
path,
|
|
623
|
+
message
|
|
624
|
+
);
|
|
625
|
+
if (!auditMode) {
|
|
626
|
+
throw new WorkerSecurityViolationError(message, violation);
|
|
627
|
+
}
|
|
628
|
+
return {};
|
|
629
|
+
},
|
|
630
|
+
// Allow all property access (like .name, .prototype, etc.)
|
|
631
|
+
get(target, prop, receiver) {
|
|
632
|
+
return Reflect.get(target, prop, receiver);
|
|
633
|
+
},
|
|
634
|
+
getPrototypeOf(target) {
|
|
635
|
+
return Reflect.getPrototypeOf(target);
|
|
636
|
+
},
|
|
637
|
+
has(target, prop) {
|
|
638
|
+
return Reflect.has(target, prop);
|
|
639
|
+
},
|
|
640
|
+
ownKeys(target) {
|
|
641
|
+
return Reflect.ownKeys(target);
|
|
642
|
+
},
|
|
643
|
+
getOwnPropertyDescriptor(target, prop) {
|
|
644
|
+
return Reflect.getOwnPropertyDescriptor(target, prop);
|
|
645
|
+
}
|
|
646
|
+
}) : originalValue;
|
|
647
|
+
Object.defineProperty(prototype, "constructor", {
|
|
648
|
+
get() {
|
|
649
|
+
return constructorProxy;
|
|
650
|
+
},
|
|
651
|
+
set(value) {
|
|
652
|
+
const message = `${path} modification is blocked in worker context`;
|
|
653
|
+
const violation = self.recordViolation(violationType, path, message);
|
|
654
|
+
if (!auditMode) {
|
|
655
|
+
throw new WorkerSecurityViolationError(message, violation);
|
|
656
|
+
}
|
|
657
|
+
Object.defineProperty(this, "constructor", {
|
|
658
|
+
value,
|
|
659
|
+
writable: true,
|
|
660
|
+
configurable: true
|
|
661
|
+
});
|
|
662
|
+
},
|
|
663
|
+
configurable: true
|
|
664
|
+
});
|
|
665
|
+
} catch {
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* Apply a single patch to a blocked global.
|
|
670
|
+
*/
|
|
671
|
+
applyPatch(blocked) {
|
|
672
|
+
const { target, prop, violationType, strategy } = blocked;
|
|
673
|
+
try {
|
|
674
|
+
const original = target[prop];
|
|
675
|
+
if (original === void 0) {
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
const descriptor = Object.getOwnPropertyDescriptor(target, prop);
|
|
679
|
+
this.originalDescriptors.push({ target, prop, descriptor });
|
|
680
|
+
if (strategy === "freeze") {
|
|
681
|
+
if (typeof original === "object" && original !== null) {
|
|
682
|
+
Object.freeze(original);
|
|
683
|
+
}
|
|
684
|
+
} else {
|
|
685
|
+
const path = this.getPathForTarget(target, prop);
|
|
686
|
+
const proxy = typeof original === "function" ? this.createBlockingProxy(
|
|
687
|
+
original,
|
|
688
|
+
path,
|
|
689
|
+
violationType
|
|
690
|
+
) : this.createBlockingObjectProxy(
|
|
691
|
+
original,
|
|
692
|
+
path,
|
|
693
|
+
violationType
|
|
694
|
+
);
|
|
695
|
+
Object.defineProperty(target, prop, {
|
|
696
|
+
value: proxy,
|
|
697
|
+
writable: true,
|
|
698
|
+
configurable: true
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
} catch {
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Restore all original values.
|
|
706
|
+
*/
|
|
707
|
+
restorePatches() {
|
|
708
|
+
for (let i = this.originalDescriptors.length - 1; i >= 0; i--) {
|
|
709
|
+
const { target, prop, descriptor } = this.originalDescriptors[i];
|
|
710
|
+
try {
|
|
711
|
+
if (descriptor) {
|
|
712
|
+
Object.defineProperty(target, prop, descriptor);
|
|
713
|
+
} else {
|
|
714
|
+
delete target[prop];
|
|
715
|
+
}
|
|
716
|
+
} catch {
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
this.originalDescriptors = [];
|
|
720
|
+
}
|
|
721
|
+
};
|
|
722
|
+
|
|
5
723
|
// src/commands/python3/protocol.ts
|
|
6
724
|
var OpCode = {
|
|
7
725
|
NOOP: 0,
|
|
@@ -831,6 +1549,7 @@ class _JbHttpResponse:
|
|
|
831
1549
|
def __init__(self, data):
|
|
832
1550
|
self.status_code = data.get('status', 0)
|
|
833
1551
|
self.reason = data.get('statusText', '')
|
|
1552
|
+
# @banned-pattern-ignore: Python code, not JavaScript
|
|
834
1553
|
self.headers = data.get('headers', {})
|
|
835
1554
|
self.text = data.get('body', '')
|
|
836
1555
|
self.url = data.get('url', '')
|
|
@@ -1339,18 +2058,43 @@ except SystemExit as e:
|
|
|
1339
2058
|
return { success: true };
|
|
1340
2059
|
}
|
|
1341
2060
|
}
|
|
2061
|
+
var defense = null;
|
|
2062
|
+
async function initializeWithDefense() {
|
|
2063
|
+
await getPyodide();
|
|
2064
|
+
defense = new WorkerDefenseInDepth({
|
|
2065
|
+
excludeViolationTypes: ["proxy", "setImmediate"],
|
|
2066
|
+
onViolation: (v) => {
|
|
2067
|
+
parentPort?.postMessage({ type: "security-violation", violation: v });
|
|
2068
|
+
}
|
|
2069
|
+
});
|
|
2070
|
+
}
|
|
1342
2071
|
if (parentPort) {
|
|
1343
2072
|
if (workerData) {
|
|
1344
|
-
runPython(workerData).then((result) => {
|
|
2073
|
+
initializeWithDefense().then(() => runPython(workerData)).then((result) => {
|
|
2074
|
+
result.defenseStats = defense?.getStats();
|
|
1345
2075
|
parentPort?.postMessage(result);
|
|
2076
|
+
}).catch((e) => {
|
|
2077
|
+
parentPort?.postMessage({
|
|
2078
|
+
success: false,
|
|
2079
|
+
error: e.message,
|
|
2080
|
+
defenseStats: defense?.getStats()
|
|
2081
|
+
});
|
|
1346
2082
|
});
|
|
1347
2083
|
}
|
|
1348
2084
|
parentPort.on("message", async (input) => {
|
|
1349
2085
|
try {
|
|
2086
|
+
if (!defense) {
|
|
2087
|
+
await initializeWithDefense();
|
|
2088
|
+
}
|
|
1350
2089
|
const result = await runPython(input);
|
|
2090
|
+
result.defenseStats = defense?.getStats();
|
|
1351
2091
|
parentPort?.postMessage(result);
|
|
1352
2092
|
} catch (e) {
|
|
1353
|
-
parentPort?.postMessage({
|
|
2093
|
+
parentPort?.postMessage({
|
|
2094
|
+
success: false,
|
|
2095
|
+
error: e.message,
|
|
2096
|
+
defenseStats: defense?.getStats()
|
|
2097
|
+
});
|
|
1354
2098
|
}
|
|
1355
2099
|
});
|
|
1356
2100
|
}
|