@securecode-ai/mcp 0.5.5 → 0.5.7
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/README.md +52 -12
- package/dist/api/types.d.ts +7 -0
- package/dist/approval/auditLog.d.ts +1 -1
- package/dist/approval/auditLog.js +11 -3
- package/dist/approval/auditLog.js.map +1 -1
- package/dist/approval/broker.d.ts +7 -2
- package/dist/approval/broker.js +239 -110
- package/dist/approval/broker.js.map +1 -1
- package/dist/approval/policy.d.ts +10 -0
- package/dist/approval/policy.js +104 -0
- package/dist/approval/policy.js.map +1 -0
- package/dist/approval/types.d.ts +11 -2
- package/dist/approval/types.js +10 -1
- package/dist/approval/types.js.map +1 -1
- package/dist/attack/agentScanExecutor.d.ts +35 -1
- package/dist/attack/agentScanExecutor.js +405 -76
- package/dist/attack/agentScanExecutor.js.map +1 -1
- package/dist/attack/agentScanLoop.d.ts +20 -0
- package/dist/attack/agentScanLoop.js +1264 -60
- package/dist/attack/agentScanLoop.js.map +1 -1
- package/dist/attack/agentScanProtocol.d.ts +382 -6
- package/dist/attack/agentScanProtocol.js +110 -4
- package/dist/attack/agentScanProtocol.js.map +1 -1
- package/dist/attack/agentTrace.d.ts +73 -0
- package/dist/attack/agentTrace.js +228 -0
- package/dist/attack/agentTrace.js.map +1 -0
- package/dist/attack/architectureScoutExecutor.d.ts +18 -0
- package/dist/attack/architectureScoutExecutor.js +211 -0
- package/dist/attack/architectureScoutExecutor.js.map +1 -0
- package/dist/attack/architectureScoutLoop.d.ts +36 -0
- package/dist/attack/architectureScoutLoop.js +403 -0
- package/dist/attack/architectureScoutLoop.js.map +1 -0
- package/dist/attack/architectureScoutProtocol.d.ts +209 -0
- package/dist/attack/architectureScoutProtocol.js +47 -0
- package/dist/attack/architectureScoutProtocol.js.map +1 -0
- package/dist/attack/candidateStore.d.ts +95 -0
- package/dist/attack/candidateStore.js +231 -0
- package/dist/attack/candidateStore.js.map +1 -0
- package/dist/attack/evidenceLedger.d.ts +67 -0
- package/dist/attack/evidenceLedger.js +192 -0
- package/dist/attack/evidenceLedger.js.map +1 -0
- package/dist/attack/finishGate.d.ts +62 -0
- package/dist/attack/finishGate.js +209 -0
- package/dist/attack/finishGate.js.map +1 -0
- package/dist/attack/fixCodeMerge.d.ts +27 -0
- package/dist/attack/fixCodeMerge.js +42 -0
- package/dist/attack/fixCodeMerge.js.map +1 -0
- package/dist/attack/fixVerifyLoop.d.ts +55 -0
- package/dist/attack/fixVerifyLoop.js +187 -0
- package/dist/attack/fixVerifyLoop.js.map +1 -0
- package/dist/attack/investigationProfiles.d.ts +36 -0
- package/dist/attack/investigationProfiles.js +144 -0
- package/dist/attack/investigationProfiles.js.map +1 -0
- package/dist/attack/investigationState.d.ts +227 -0
- package/dist/attack/investigationState.js +666 -0
- package/dist/attack/investigationState.js.map +1 -0
- package/dist/attack/mutationOperators.d.ts +22 -0
- package/dist/attack/mutationOperators.js +170 -0
- package/dist/attack/mutationOperators.js.map +1 -0
- package/dist/attack/mutationTest.d.ts +33 -0
- package/dist/attack/mutationTest.js +110 -0
- package/dist/attack/mutationTest.js.map +1 -0
- package/dist/attack/proofGate.d.ts +20 -0
- package/dist/attack/proofGate.js +104 -0
- package/dist/attack/proofGate.js.map +1 -0
- package/dist/attack/proofTypes.d.ts +57 -0
- package/dist/attack/proofTypes.js +32 -0
- package/dist/attack/proofTypes.js.map +1 -0
- package/dist/attack/protocolValidator.d.ts +50 -0
- package/dist/attack/protocolValidator.js +427 -0
- package/dist/attack/protocolValidator.js.map +1 -0
- package/dist/attack/qualityMetrics.d.ts +133 -0
- package/dist/attack/qualityMetrics.js +226 -0
- package/dist/attack/qualityMetrics.js.map +1 -0
- package/dist/attack/scanScheduler.d.ts +52 -0
- package/dist/attack/scanScheduler.js +265 -0
- package/dist/attack/scanScheduler.js.map +1 -0
- package/dist/attack/scanState.d.ts +58 -0
- package/dist/attack/scanState.js +102 -0
- package/dist/attack/scanState.js.map +1 -0
- package/dist/attack/searchIntent.d.ts +16 -0
- package/dist/attack/searchIntent.js +57 -0
- package/dist/attack/searchIntent.js.map +1 -0
- package/dist/attack/verifyLoop.d.ts +43 -0
- package/dist/attack/verifyLoop.js +314 -14
- package/dist/attack/verifyLoop.js.map +1 -1
- package/dist/attack/workItem.d.ts +57 -0
- package/dist/attack/workItem.js +225 -0
- package/dist/attack/workItem.js.map +1 -0
- package/dist/audit/findingReviewQueue.d.ts +109 -0
- package/dist/audit/findingReviewQueue.js +335 -0
- package/dist/audit/findingReviewQueue.js.map +1 -0
- package/dist/audit/scanAuditLog.d.ts +160 -0
- package/dist/audit/scanAuditLog.js +404 -0
- package/dist/audit/scanAuditLog.js.map +1 -0
- package/dist/dependency/dependencyChecker.js +35 -9
- package/dist/dependency/dependencyChecker.js.map +1 -1
- package/dist/dependency/exploitPriority.d.ts +33 -0
- package/dist/dependency/exploitPriority.js +78 -0
- package/dist/dependency/exploitPriority.js.map +1 -0
- package/dist/dependency/finding.d.ts +16 -0
- package/dist/dependency/osvClient.js +2 -0
- package/dist/dependency/osvClient.js.map +1 -1
- package/dist/dependency/types.d.ts +12 -1
- package/dist/mcp/server.js +13 -2
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tools.d.ts +1 -0
- package/dist/mcp/tools.js +127 -6
- package/dist/mcp/tools.js.map +1 -1
- package/dist/project-map/agentMemory.d.ts +83 -1
- package/dist/project-map/agentMemory.js +197 -7
- package/dist/project-map/agentMemory.js.map +1 -1
- package/dist/project-map/architectureContext.d.ts +202 -0
- package/dist/project-map/architectureContext.js +421 -0
- package/dist/project-map/architectureContext.js.map +1 -0
- package/dist/project-map/blastRadius.d.ts +54 -0
- package/dist/project-map/blastRadius.js +196 -0
- package/dist/project-map/blastRadius.js.map +1 -0
- package/dist/project-map/callGraphExtractor.d.ts +13 -0
- package/dist/project-map/callGraphExtractor.js +222 -0
- package/dist/project-map/callGraphExtractor.js.map +1 -0
- package/dist/project-map/capabilityRegistry.d.ts +46 -0
- package/dist/project-map/capabilityRegistry.js +91 -0
- package/dist/project-map/capabilityRegistry.js.map +1 -0
- package/dist/project-map/findTests.d.ts +29 -0
- package/dist/project-map/findTests.js +236 -0
- package/dist/project-map/findTests.js.map +1 -0
- package/dist/project-map/handlerInventory.d.ts +112 -0
- package/dist/project-map/handlerInventory.js +184 -0
- package/dist/project-map/handlerInventory.js.map +1 -0
- package/dist/project-map/implementationResolver.d.ts +43 -0
- package/dist/project-map/implementationResolver.js +217 -0
- package/dist/project-map/implementationResolver.js.map +1 -0
- package/dist/project-map/mapContext.d.ts +6 -1
- package/dist/project-map/mapContext.js +1 -0
- package/dist/project-map/mapContext.js.map +1 -1
- package/dist/project-map/scanCache.d.ts +57 -3
- package/dist/project-map/scanCache.js +61 -2
- package/dist/project-map/scanCache.js.map +1 -1
- package/dist/project-map/symbolIndex.d.ts +39 -0
- package/dist/project-map/symbolIndex.js +385 -0
- package/dist/project-map/symbolIndex.js.map +1 -0
- package/dist/tooling/agentEvalScoring.d.ts +77 -0
- package/dist/tooling/agentEvalScoring.js +140 -0
- package/dist/tooling/agentEvalScoring.js.map +1 -0
- package/dist/tooling/agentRegression.d.ts +53 -0
- package/dist/tooling/agentRegression.js +99 -0
- package/dist/tooling/agentRegression.js.map +1 -0
- package/dist/tools/agentScan.d.ts +69 -0
- package/dist/tools/agentScan.js +702 -43
- package/dist/tools/agentScan.js.map +1 -1
- package/dist/tools/findingReviewTools.d.ts +22 -0
- package/dist/tools/findingReviewTools.js +121 -0
- package/dist/tools/findingReviewTools.js.map +1 -0
- package/dist/tools/fix.js +1 -1
- package/dist/tools/fix.js.map +1 -1
- package/dist/tools/map.js +191 -1
- package/dist/tools/map.js.map +1 -1
- package/dist/tools/runTests.d.ts +2 -0
- package/dist/tools/runTests.js +29 -0
- package/dist/tools/runTests.js.map +1 -0
- package/dist/utils/effectMock.d.ts +1 -0
- package/dist/utils/effectMock.js +162 -0
- package/dist/utils/effectMock.js.map +1 -0
- package/dist/utils/gitContext.d.ts +26 -0
- package/dist/utils/gitContext.js +317 -0
- package/dist/utils/gitContext.js.map +1 -0
- package/dist/utils/localTestRunner.d.ts +57 -2
- package/dist/utils/localTestRunner.js +122 -115
- package/dist/utils/localTestRunner.js.map +1 -1
- package/dist/utils/securityConfig.d.ts +17 -0
- package/dist/utils/securityConfig.js +192 -0
- package/dist/utils/securityConfig.js.map +1 -0
- package/dist/utils/testCommandPolicy.d.ts +36 -0
- package/dist/utils/testCommandPolicy.js +252 -0
- package/dist/utils/testCommandPolicy.js.map +1 -0
- package/dist/utils/testRunner.d.ts +56 -0
- package/dist/utils/testRunner.js +246 -0
- package/dist/utils/testRunner.js.map +1 -0
- package/dist/utils/testSafety.d.ts +40 -2
- package/dist/utils/testSafety.js +205 -26
- package/dist/utils/testSafety.js.map +1 -1
- package/dist/utils/verificationSandbox.d.ts +113 -0
- package/dist/utils/verificationSandbox.js +656 -0
- package/dist/utils/verificationSandbox.js.map +1 -0
- package/package.json +1 -1
package/dist/utils/testSafety.js
CHANGED
|
@@ -1,38 +1,217 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Static test safety analyzer — defense-in-depth, NOT a security boundary.
|
|
4
|
+
*
|
|
5
|
+
* The real boundary is the verification sandbox (`verificationSandbox.ts`):
|
|
6
|
+
* Docker with `--network=none --read-only`, or Deno with `--allow-none`.
|
|
7
|
+
* This module exists to reject obviously dangerous scripts *before* we
|
|
8
|
+
* pay for a sandbox spawn, and as a second layer in case a sandbox backend
|
|
9
|
+
* is somehow misconfigured.
|
|
10
|
+
*
|
|
11
|
+
* Policy: deny-by-default. Only specific known-safe imports and globals
|
|
12
|
+
* are permitted. Anything else is blocked. The previous implementation
|
|
13
|
+
* was allow-by-default with a 4-module blocklist — it let `http`, `https`,
|
|
14
|
+
* `fs`, `dns`, `os`, `tls`, `vm`, `dgram`, `fetch`, `eval`, `new Function`,
|
|
15
|
+
* dynamic `import()`, and `process.env` access all pass. That gap is the
|
|
16
|
+
* reason this module was rewritten.
|
|
17
|
+
*
|
|
18
|
+
* What this analyzer checks (in priority order):
|
|
19
|
+
* 1. Static import / require specifiers — against an allowlist.
|
|
20
|
+
* 2. Dynamic require/import — blocked outright (cannot be statically
|
|
21
|
+
* analyzed).
|
|
22
|
+
* 3. Global function calls that execute strings as code — `eval`,
|
|
23
|
+
* `new Function`, `setTimeout(string)`, `setInterval(string)`,
|
|
24
|
+
* `setImmediate(string)`.
|
|
25
|
+
* 4. Network-related globals and Node built-ins (covered by #1 for
|
|
26
|
+
* imports; here for member-style access like `globalThis.fetch`).
|
|
27
|
+
* 5. Process/env access — `process.env`, `process.argv`, `process.cwd`,
|
|
28
|
+
* `process.execPath`, `process.cwd()`.
|
|
29
|
+
* 6. Reflection tricks that resolve to dangerous globals —
|
|
30
|
+
* `globalThis['eval']`, `globalThis['Function']`, `this['eval']`,
|
|
31
|
+
* bracket access with string literals that match dangerous names.
|
|
32
|
+
*
|
|
33
|
+
* Obfuscation handling: we catch literal bracket access like
|
|
34
|
+
* `globalThis["eval"]` and `process["env"]`. We do NOT attempt to defeat
|
|
35
|
+
* arbitrary runtime-computed string construction (e.g., `String.fromCharCode`
|
|
36
|
+
* to build a name) — that is the sandbox's job. This analyzer only catches
|
|
37
|
+
* what a defender can see statically.
|
|
38
|
+
*/
|
|
2
39
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
40
|
exports.checkTestSafety = checkTestSafety;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
41
|
+
// ── Import allowlist ────────────────────────────────────────────────────────
|
|
42
|
+
//
|
|
43
|
+
// Allowed import specifiers (after the leading `node:` / `bun:` prefix is
|
|
44
|
+
// stripped for built-ins). Built-ins are kept on a tiny allowlist because
|
|
45
|
+
// even `node:` built-ins like `fs` are dangerous. The sandbox is the real
|
|
46
|
+
// boundary, but we still block here so we don't waste a container start
|
|
47
|
+
// on something obviously bad.
|
|
48
|
+
const ALLOWED_NODE_BUILTINS = new Set([
|
|
49
|
+
'assert', 'console', 'crypto', 'path', 'util', 'module',
|
|
50
|
+
'string_decoder', 'url',
|
|
51
|
+
]);
|
|
52
|
+
const ALLOWED_PREFIXES = [
|
|
53
|
+
'./', '../', // relative imports — needed to load the vulnerable module
|
|
54
|
+
'effect', // the user's framework
|
|
55
|
+
'bun:', // bun:* built-ins (test framework, etc.)
|
|
56
|
+
'@synara/', // user's workspace packages
|
|
8
57
|
];
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
child_process
|
|
12
|
-
net
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
58
|
+
// Modules blocked even if a prefix matches (defense-in-depth).
|
|
59
|
+
const BLOCKED_MODULE_NAMES = new Set([
|
|
60
|
+
'child_process', 'node:child_process',
|
|
61
|
+
'net', 'node:net',
|
|
62
|
+
'http', 'node:http',
|
|
63
|
+
'https', 'node:https',
|
|
64
|
+
'http2', 'node:http2',
|
|
65
|
+
'dgram', 'node:dgram',
|
|
66
|
+
'dns', 'node:dns',
|
|
67
|
+
'dns/promises', 'node:dns/promises',
|
|
68
|
+
'tls', 'node:tls',
|
|
69
|
+
'fs', 'node:fs',
|
|
70
|
+
'fs/promises', 'node:fs/promises',
|
|
71
|
+
'os', 'node:os',
|
|
72
|
+
'vm', 'node:vm',
|
|
73
|
+
'worker_threads', 'node:worker_threads',
|
|
74
|
+
'cluster', 'node:cluster',
|
|
75
|
+
'repl', 'node:repl',
|
|
76
|
+
'process', 'node:process',
|
|
77
|
+
'inspector', 'node:inspector',
|
|
78
|
+
'perf_hooks', 'node:perf_hooks', // timing side-channels
|
|
79
|
+
]);
|
|
80
|
+
// ── Dangerous globals & member access ───────────────────────────────────────
|
|
81
|
+
const DANGEROUS_GLOBAL_CALLS = [
|
|
82
|
+
{ pattern: /\beval\s*\(/, reason: 'eval() executes arbitrary strings as code' },
|
|
83
|
+
{ pattern: /\bnew\s+Function\s*\(/, reason: 'new Function() executes arbitrary strings as code' },
|
|
84
|
+
{ pattern: /\bnew\s+AsyncFunction\s*\(/, reason: 'new AsyncFunction() executes arbitrary strings as code' },
|
|
85
|
+
{ pattern: /\bnew\s+GeneratorFunction\s*\(/, reason: 'new GeneratorFunction() executes arbitrary strings as code' },
|
|
86
|
+
];
|
|
87
|
+
// setTimeout/setInterval/setImmediate with a STRING argument is eval-by-proxy.
|
|
88
|
+
const TIMER_STRING_ARG = /\b(setTimeout|setInterval|setImmediate)\s*\(\s*['"`]/;
|
|
89
|
+
const DANGEROUS_PROCESS_ACCESS = [
|
|
90
|
+
{ pattern: /\bprocess\.env\b/, reason: 'process.env access can leak host secrets — pass values explicitly' },
|
|
91
|
+
{ pattern: /\bprocess\.argv\b/, reason: 'process.argv access can leak host arguments' },
|
|
92
|
+
{ pattern: /\bprocess\.execPath\b/, reason: 'process.execPath exposes the host runtime path' },
|
|
93
|
+
{ pattern: /\bprocess\.cwd\s*\(/, reason: 'process.cwd() exposes the host filesystem layout' },
|
|
94
|
+
{ pattern: /\bprocess\.getuid\s*\(/, reason: 'process.getuid() exposes host user identity' },
|
|
95
|
+
{ pattern: /\bprocess\.getgid\s*\(/, reason: 'process.getgid() exposes host group identity' },
|
|
96
|
+
];
|
|
97
|
+
// Network & fs globals reachable without import (Node 18+ has global fetch).
|
|
98
|
+
const DANGEROUS_GLOBAL_REFERENCES = [
|
|
99
|
+
{ pattern: /\bfetch\s*\(/, reason: 'fetch() can exfiltrate data over the network' },
|
|
100
|
+
{ pattern: /\bnavigator\b/, reason: 'navigator access is not needed in a unit test' },
|
|
101
|
+
];
|
|
102
|
+
// Dynamic import (ESM) and dynamic require (CJS) — both are unsafe because
|
|
103
|
+
// the specifier can be computed at runtime.
|
|
104
|
+
const DYNAMIC_IMPORT = /\bimport\s*\(\s*[^"'\s`]/;
|
|
105
|
+
const DYNAMIC_REQUIRE = /\brequire\s*\(\s*(?![`'"])[^)]*\)/;
|
|
106
|
+
const STATIC_REQUIRE = /\brequire\s*\(\s*['"`]([^`'"]+)['"`]\s*\)/g;
|
|
107
|
+
const STATIC_IMPORT_FROM = /\bimport\s+[^;]*?\s+from\s*['"`]([^`'"]+)['"`]/g;
|
|
108
|
+
const STATIC_IMPORT_BARE = /\bimport\s+['"`]([^`'"]+)['"`]/g;
|
|
109
|
+
const STATIC_EXPORT_FROM = /\bexport\s+[^;]*?\s+from\s*['"`]([^`'"]+)['"`]/g;
|
|
110
|
+
// Bracket-access reflection: globalThis["eval"], process["env"], etc.
|
|
111
|
+
// Matches `<identifier|keyword>["<dangerous-name>"]` and `['...']`.
|
|
112
|
+
function buildBracketAccessRegex(name) {
|
|
113
|
+
// Capture a bracket access where the property is a string literal matching
|
|
114
|
+
// the dangerous name. The receiver is any identifier-like token (we
|
|
115
|
+
// accept anything that's not a quote, to keep this simple).
|
|
116
|
+
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
117
|
+
return new RegExp(`\\b[^'"\\\`\\s]+\\s*\\[\\s*['"\`\\s]*${escaped}['"\`\\s]*\\]`, 'i');
|
|
118
|
+
}
|
|
119
|
+
const DANGEROUS_BRACKET_NAMES = [
|
|
120
|
+
'eval', 'Function', 'AsyncFunction', 'GeneratorFunction',
|
|
121
|
+
'fetch', 'process', 'require', 'import',
|
|
122
|
+
'child_process', 'net', 'http', 'https', 'fs', 'os', 'vm',
|
|
123
|
+
'exec', 'execSync', 'spawn', 'spawnSync', 'fork',
|
|
124
|
+
// Dangerous properties of `process` accessed via bracket (process["env"]).
|
|
125
|
+
// Blocking the property name on any receiver is intentional — bracket
|
|
126
|
+
// access to "env" is a known reflection trick and tests shouldn't need it.
|
|
127
|
+
'env', 'argv', 'execPath', 'mainModule',
|
|
128
|
+
];
|
|
129
|
+
// ── Main entry ──────────────────────────────────────────────────────────────
|
|
16
130
|
function checkTestSafety(script, _workspaceRoot) {
|
|
17
|
-
|
|
18
|
-
|
|
131
|
+
// 1. Dangerous call patterns (eval, Function, etc.)
|
|
132
|
+
for (const { pattern, reason } of DANGEROUS_GLOBAL_CALLS) {
|
|
19
133
|
if (pattern.test(script)) {
|
|
20
|
-
return { allowed: false, reason
|
|
134
|
+
return { allowed: false, reason };
|
|
21
135
|
}
|
|
22
136
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
137
|
+
// 2. Timer-with-string (eval-by-proxy)
|
|
138
|
+
if (TIMER_STRING_ARG.test(script)) {
|
|
139
|
+
return { allowed: false, reason: 'setTimeout/setInterval/setImmediate with a string argument is eval-by-proxy' };
|
|
140
|
+
}
|
|
141
|
+
// 3. process.* access that leaks host state
|
|
142
|
+
for (const { pattern, reason } of DANGEROUS_PROCESS_ACCESS) {
|
|
143
|
+
if (pattern.test(script)) {
|
|
144
|
+
return { allowed: false, reason };
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// 4. Global network refs
|
|
148
|
+
for (const { pattern, reason } of DANGEROUS_GLOBAL_REFERENCES) {
|
|
149
|
+
if (pattern.test(script)) {
|
|
150
|
+
return { allowed: false, reason };
|
|
29
151
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
152
|
+
}
|
|
153
|
+
// 5. Dynamic import / dynamic require — non-static specifiers
|
|
154
|
+
if (DYNAMIC_IMPORT.test(script)) {
|
|
155
|
+
return { allowed: false, reason: 'dynamic import() with a computed specifier cannot be statically analyzed' };
|
|
156
|
+
}
|
|
157
|
+
if (DYNAMIC_REQUIRE.test(script)) {
|
|
158
|
+
return { allowed: false, reason: 'dynamic require with variable is not statically checkable' };
|
|
159
|
+
}
|
|
160
|
+
// 6. Bracket-access reflection (globalThis["eval"], process["env"], etc.)
|
|
161
|
+
for (const name of DANGEROUS_BRACKET_NAMES) {
|
|
162
|
+
const re = buildBracketAccessRegex(name);
|
|
163
|
+
if (re.test(script)) {
|
|
164
|
+
return { allowed: false, reason: `bracket access to "${name}" is blocked — use the safe form directly if needed` };
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// 7. Static import / require specifiers — allowlist
|
|
168
|
+
const checkSpecifier = (mod) => {
|
|
169
|
+
if (BLOCKED_MODULE_NAMES.has(mod)) {
|
|
170
|
+
return { allowed: false, reason: `module "${mod}" is blocked — use the sandbox boundary for fs/net/process access` };
|
|
171
|
+
}
|
|
172
|
+
// node: prefix — strip and check the built-in allowlist.
|
|
173
|
+
if (mod.startsWith('node:')) {
|
|
174
|
+
const name = mod.slice('node:'.length);
|
|
175
|
+
if (!ALLOWED_NODE_BUILTINS.has(name)) {
|
|
176
|
+
return { allowed: false, reason: `node built-in "${mod}" is not on the allowlist (allowed: ${[...ALLOWED_NODE_BUILTINS].join(', ')})` };
|
|
177
|
+
}
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
// Bare built-in (no node: prefix) — block unless it's on the allowlist.
|
|
181
|
+
// We block bare 'fs', 'net', etc. even though Node resolves them to the
|
|
182
|
+
// same thing as node:fs.
|
|
183
|
+
if (ALLOWED_NODE_BUILTINS.has(mod))
|
|
184
|
+
return null;
|
|
185
|
+
if (mod === 'node')
|
|
186
|
+
return null; // not a real import
|
|
187
|
+
if (ALLOWED_PREFIXES.some(p => mod.startsWith(p)))
|
|
188
|
+
return null;
|
|
189
|
+
// Bare module names that aren't on an allowed prefix and aren't a
|
|
190
|
+
// built-in. These are third-party packages. We allow them — the
|
|
191
|
+
// sandbox will deny network access if they try to fetch anything.
|
|
192
|
+
// But we keep the known-dangerous ones in BLOCKED_MODULE_NAMES above.
|
|
193
|
+
return null;
|
|
194
|
+
};
|
|
195
|
+
const importPatterns = [
|
|
196
|
+
{ re: STATIC_IMPORT_FROM, label: 'import' },
|
|
197
|
+
{ re: STATIC_IMPORT_BARE, label: 'import' },
|
|
198
|
+
{ re: STATIC_EXPORT_FROM, label: 'export' },
|
|
199
|
+
{ re: STATIC_REQUIRE, label: 'require' },
|
|
200
|
+
];
|
|
201
|
+
for (const { re } of importPatterns) {
|
|
202
|
+
re.lastIndex = 0;
|
|
203
|
+
let m;
|
|
204
|
+
while ((m = re.exec(script)) !== null) {
|
|
205
|
+
const mod = m[1];
|
|
206
|
+
const verdict = checkSpecifier(mod);
|
|
207
|
+
if (verdict)
|
|
208
|
+
return verdict;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
// 8. process.exit() — masks test results and prevents the runner from
|
|
212
|
+
// capturing PASS/FAIL markers. (Was blocked before; keep the behavior.)
|
|
213
|
+
if (/\bprocess\.exit\s*\(/.test(script)) {
|
|
214
|
+
return { allowed: false, reason: 'process.exit() masks test results — use console.error + throw instead' };
|
|
36
215
|
}
|
|
37
216
|
return { allowed: true, reason: '' };
|
|
38
217
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testSafety.js","sourceRoot":"","sources":["../../src/utils/testSafety.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"testSafety.js","sourceRoot":"","sources":["../../src/utils/testSafety.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verification sandbox — the real security boundary for LLM-generated tests.
|
|
3
|
+
*
|
|
4
|
+
* Phase 2 of the agent scan pipeline takes an LLM-generated test script and
|
|
5
|
+
* executes it on the user's machine. The previous implementation ran the
|
|
6
|
+
* script via `execFileSync` with the full `process.env` spread and only a
|
|
7
|
+
* decorative blocklist (`checkTestSafety`) in front. Prompt-injected code
|
|
8
|
+
* in the scanned source could ride into the verify-generate prompt and
|
|
9
|
+
* produce a test script that exfiltrated secrets, hit the network, or
|
|
10
|
+
* spawned processes — `checkTestSafety` missed `fetch`, `http`, `https`,
|
|
11
|
+
* `fs`, `dns`, `eval`, `new Function`, dynamic imports, and `process.env`
|
|
12
|
+
* access.
|
|
13
|
+
*
|
|
14
|
+
* This module replaces that with a real isolation boundary. Pluggable
|
|
15
|
+
* backends provide kernel-enforced isolation:
|
|
16
|
+
*
|
|
17
|
+
* - DockerSandbox: `docker run --network=none --cpus=1
|
|
18
|
+
* --memory=256m --pids-limit=64 --user 1000:1000 -v <workspace>:/workspace:ro`
|
|
19
|
+
* — network disabled at the kernel level, workspace read-only, temp
|
|
20
|
+
* dir as tmpfs, no host env, non-root user. Works for any Node/Python
|
|
21
|
+
* test script unchanged.
|
|
22
|
+
* - DenoSandbox: `deno run --allow-read=<workspace> --allow-write=<tmp>
|
|
23
|
+
* --allow-env=NODE_ENV --no-prompt --no-remote` — real permission
|
|
24
|
+
* model, no network, no arbitrary fs, no arbitrary env. Only works
|
|
25
|
+
* for test scripts that don't use `require()` (Deno is ESM-only by
|
|
26
|
+
* default); the verify-generate prompt must be told to emit
|
|
27
|
+
* Deno-compatible code when Deno is the backend.
|
|
28
|
+
*
|
|
29
|
+
* If neither backend is available, `detectSandbox()` returns null and the
|
|
30
|
+
* caller MUST return INCONCLUSIVE. Never fall back to unrestricted
|
|
31
|
+
* `execFileSync` — that is the bug this module exists to fix.
|
|
32
|
+
*
|
|
33
|
+
* `checkTestSafety()` remains as defense-in-depth (it can reject scripts
|
|
34
|
+
* before we pay for a container start), but it is NOT the security
|
|
35
|
+
* boundary. The sandbox is.
|
|
36
|
+
*/
|
|
37
|
+
export interface SandboxExecuteOptions {
|
|
38
|
+
/** Execution mode: 'script' (default) runs a script via runner; 'command' runs a validated executable+args. */
|
|
39
|
+
mode?: 'script' | 'command';
|
|
40
|
+
/** Test script source code (required for mode='script'). */
|
|
41
|
+
script?: string;
|
|
42
|
+
/** Runner: 'node' | 'tsx' | 'bun' | 'python' | 'python3' | 'deno' | ... (required for mode='script'). */
|
|
43
|
+
runner?: string;
|
|
44
|
+
/** Validated executable (required for mode='command'). Passed as argv[0] — never via shell. */
|
|
45
|
+
executable?: string;
|
|
46
|
+
/** Validated args array (required for mode='command'). Each arg passed as a separate argv element. */
|
|
47
|
+
args?: string[];
|
|
48
|
+
/** Workspace root — mounted read-only into the sandbox. */
|
|
49
|
+
workspaceRoot: string;
|
|
50
|
+
/** Optional setup script run before the test (same sandbox). */
|
|
51
|
+
setupScript?: string | null;
|
|
52
|
+
/** Hard wall-clock timeout in ms. The sandbox kills the process at this boundary. */
|
|
53
|
+
timeoutMs: number;
|
|
54
|
+
/** AbortSignal — if aborted, the sandbox must kill the process and return. */
|
|
55
|
+
signal?: AbortSignal;
|
|
56
|
+
}
|
|
57
|
+
export interface SandboxExecuteResult {
|
|
58
|
+
verdict: 'pass' | 'fail' | 'error' | 'timeout' | 'blocked' | 'sandbox-unavailable' | 'cancelled';
|
|
59
|
+
output: string;
|
|
60
|
+
exitCode: number;
|
|
61
|
+
/** Identifier of the backend that ran the test (for logging). */
|
|
62
|
+
backend: string;
|
|
63
|
+
}
|
|
64
|
+
export interface SandboxBackend {
|
|
65
|
+
readonly name: string;
|
|
66
|
+
readonly available: boolean;
|
|
67
|
+
execute(opts: SandboxExecuteOptions): Promise<SandboxExecuteResult>;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Command-mode verdict: no PASS:/FAIL: markers. Exit code 0 = pass, nonzero = fail.
|
|
71
|
+
* This is the standard semantics for `npm test`, `pytest`, etc.
|
|
72
|
+
*/
|
|
73
|
+
export declare function parseVerdictCommandMode(exitCode: number, timedOut: boolean): SandboxExecuteResult['verdict'];
|
|
74
|
+
/**
|
|
75
|
+
* Pick the Docker image for a command-mode executable. Returns null if no
|
|
76
|
+
* suitable image is configured — the caller returns sandbox-unavailable.
|
|
77
|
+
*/
|
|
78
|
+
export declare function pickImageForCommand(executable: string): {
|
|
79
|
+
image: string | null;
|
|
80
|
+
reason?: string;
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* Pick the Docker image for a script-mode runner. Returns null if no suitable
|
|
84
|
+
* image is configured — the caller returns sandbox-unavailable so the user
|
|
85
|
+
* gets a clear "install X / set ENV to enable" message rather than a crash.
|
|
86
|
+
*/
|
|
87
|
+
export declare function pickImageForRunner(runner: string): {
|
|
88
|
+
image: string | null;
|
|
89
|
+
reason?: string;
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Build the in-container command string for a runner + script path.
|
|
93
|
+
* The path is generated by us (unique suffix), so shell quoting is not
|
|
94
|
+
* required for safety; we still quote defensively in case the workspace
|
|
95
|
+
* path contains spaces.
|
|
96
|
+
*/
|
|
97
|
+
export declare function runnerInvocation(runner: string, scriptPath: string): string;
|
|
98
|
+
/**
|
|
99
|
+
* Detect the best available sandbox backend. Docker is preferred (runs any
|
|
100
|
+
* Node/Python test unchanged); Deno is a fallback for TS/JS tests only.
|
|
101
|
+
* Returns null if neither is available — the caller MUST return INCONCLUSIVE.
|
|
102
|
+
*/
|
|
103
|
+
export declare function detectSandbox(): SandboxBackend | null;
|
|
104
|
+
/** Reset the cached backend selection. Used by tests. */
|
|
105
|
+
export declare function _resetSandboxCacheForTests(): void;
|
|
106
|
+
/** Force a specific backend (for tests). Pass null to simulate "no sandbox". */
|
|
107
|
+
export declare function _setSandboxForTests(backend: SandboxBackend | null): void;
|
|
108
|
+
export declare class __UnsafeHostSandboxForTests implements SandboxBackend {
|
|
109
|
+
readonly name = "unsafe-host-tests-only";
|
|
110
|
+
constructor();
|
|
111
|
+
get available(): boolean;
|
|
112
|
+
execute(opts: SandboxExecuteOptions): Promise<SandboxExecuteResult>;
|
|
113
|
+
}
|