refactor(llm-security): parameterize buildSandboxedWorker with workerPath
This commit is contained in:
parent
aa269ed6d8
commit
112cb5af45
2 changed files with 70 additions and 4 deletions
|
|
@ -18,7 +18,10 @@ import { fileURLToPath } from 'node:url';
|
||||||
import { dirname, resolve as resolvePath } from 'node:path';
|
import { dirname, resolve as resolvePath } from 'node:path';
|
||||||
|
|
||||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
const WORKER_PATH = resolvePath(__dirname, 'vsix-fetch-worker.mjs');
|
const DEFAULT_VSIX_WORKER_PATH = resolvePath(__dirname, 'vsix-fetch-worker.mjs');
|
||||||
|
const DEFAULT_JETBRAINS_WORKER_PATH = resolvePath(__dirname, 'jetbrains-fetch-worker.mjs');
|
||||||
|
// Backward-compat alias — older internal refs may still import `WORKER_PATH`.
|
||||||
|
const WORKER_PATH = DEFAULT_VSIX_WORKER_PATH;
|
||||||
const WORKER_TIMEOUT_MS = 35_000; // fetch is 30s, give worker 5s of slack
|
const WORKER_TIMEOUT_MS = 35_000; // fetch is 30s, give worker 5s of slack
|
||||||
const MAX_OUTPUT_BYTES = 1024 * 1024; // 1MB JSON cap (output is tiny in practice)
|
const MAX_OUTPUT_BYTES = 1024 * 1024; // 1MB JSON cap (output is tiny in practice)
|
||||||
|
|
||||||
|
|
@ -76,10 +79,13 @@ export function buildBwrapArgs(allowedWritePath, innerArgs) {
|
||||||
*
|
*
|
||||||
* @param {string} tmpDir writable temp dir for the worker
|
* @param {string} tmpDir writable temp dir for the worker
|
||||||
* @param {string[]} workerArgs argv for the worker (after `node <worker>`)
|
* @param {string[]} workerArgs argv for the worker (after `node <worker>`)
|
||||||
|
* @param {string} [workerPath=DEFAULT_VSIX_WORKER_PATH] absolute path to the
|
||||||
|
* worker module. Defaults to the VSIX worker for backward compat; Step 12
|
||||||
|
* passes `DEFAULT_JETBRAINS_WORKER_PATH` for JetBrains plugin fetches.
|
||||||
* @returns {{cmd:string, args:string[], sandbox: 'sandbox-exec'|'bwrap'|null}}
|
* @returns {{cmd:string, args:string[], sandbox: 'sandbox-exec'|'bwrap'|null}}
|
||||||
*/
|
*/
|
||||||
export function buildSandboxedWorker(tmpDir, workerArgs) {
|
export function buildSandboxedWorker(tmpDir, workerArgs, workerPath = DEFAULT_VSIX_WORKER_PATH) {
|
||||||
const innerArgs = ['node', WORKER_PATH, ...workerArgs];
|
const innerArgs = ['node', workerPath, ...workerArgs];
|
||||||
|
|
||||||
const profile = buildSandboxProfile(tmpDir);
|
const profile = buildSandboxProfile(tmpDir);
|
||||||
if (profile) {
|
if (profile) {
|
||||||
|
|
@ -105,8 +111,29 @@ export function buildSandboxedWorker(tmpDir, workerArgs) {
|
||||||
* @returns {Promise<{ok:boolean, sandbox:'sandbox-exec'|'bwrap'|null, payload:object}>}
|
* @returns {Promise<{ok:boolean, sandbox:'sandbox-exec'|'bwrap'|null, payload:object}>}
|
||||||
*/
|
*/
|
||||||
export function runVsixWorker(url, tmpDir, opts = {}) {
|
export function runVsixWorker(url, tmpDir, opts = {}) {
|
||||||
|
return runPluginWorker(
|
||||||
|
DEFAULT_VSIX_WORKER_PATH,
|
||||||
|
['--url', url, '--tmpdir', tmpDir],
|
||||||
|
tmpDir,
|
||||||
|
opts,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generalized sandboxed-worker runner. Spawns any worker module, captures a
|
||||||
|
* single JSON line of stdout, enforces the same timeout / output cap as
|
||||||
|
* `runVsixWorker`. Step 12 uses this for the JetBrains worker — it must NOT
|
||||||
|
* duplicate spawn/parse logic.
|
||||||
|
*
|
||||||
|
* @param {string} workerPath absolute path to the worker .mjs file
|
||||||
|
* @param {string[]} workerArgs argv for the worker
|
||||||
|
* @param {string} tmpDir writable temp dir for the worker
|
||||||
|
* @param {{allowFallback?: boolean}} [opts]
|
||||||
|
* @returns {Promise<{ok:boolean, sandbox:'sandbox-exec'|'bwrap'|null, payload:object}>}
|
||||||
|
*/
|
||||||
|
export function runPluginWorker(workerPath, workerArgs, tmpDir, opts = {}) {
|
||||||
const { allowFallback = true } = opts;
|
const { allowFallback = true } = opts;
|
||||||
const { cmd, args, sandbox } = buildSandboxedWorker(tmpDir, ['--url', url, '--tmpdir', tmpDir]);
|
const { cmd, args, sandbox } = buildSandboxedWorker(tmpDir, workerArgs, workerPath);
|
||||||
|
|
||||||
if (!sandbox && !allowFallback) {
|
if (!sandbox && !allowFallback) {
|
||||||
return Promise.reject(new Error('no OS sandbox available and fallback disabled'));
|
return Promise.reject(new Error('no OS sandbox available and fallback disabled'));
|
||||||
|
|
@ -162,8 +189,16 @@ export function runVsixWorker(url, tmpDir, opts = {}) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
DEFAULT_VSIX_WORKER_PATH,
|
||||||
|
DEFAULT_JETBRAINS_WORKER_PATH,
|
||||||
|
};
|
||||||
|
|
||||||
export const __testing = {
|
export const __testing = {
|
||||||
WORKER_PATH,
|
WORKER_PATH,
|
||||||
|
DEFAULT_VSIX_WORKER_PATH,
|
||||||
|
DEFAULT_JETBRAINS_WORKER_PATH,
|
||||||
WORKER_TIMEOUT_MS,
|
WORKER_TIMEOUT_MS,
|
||||||
MAX_OUTPUT_BYTES,
|
MAX_OUTPUT_BYTES,
|
||||||
|
resolveWorkerPath: (name) => resolvePath(__dirname, name),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,9 @@ import {
|
||||||
buildBwrapArgs,
|
buildBwrapArgs,
|
||||||
buildSandboxedWorker,
|
buildSandboxedWorker,
|
||||||
runVsixWorker,
|
runVsixWorker,
|
||||||
|
runPluginWorker,
|
||||||
|
DEFAULT_VSIX_WORKER_PATH,
|
||||||
|
DEFAULT_JETBRAINS_WORKER_PATH,
|
||||||
__testing,
|
__testing,
|
||||||
} from '../../scanners/lib/vsix-sandbox.mjs';
|
} from '../../scanners/lib/vsix-sandbox.mjs';
|
||||||
|
|
||||||
|
|
@ -69,6 +72,34 @@ describe('vsix-sandbox — buildSandboxedWorker', () => {
|
||||||
assert.match(joined, /--url https:\/\/example\//);
|
assert.match(joined, /--url https:\/\/example\//);
|
||||||
assert.match(joined, /--tmpdir \/tmp/);
|
assert.match(joined, /--tmpdir \/tmp/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('honors explicit workerPath parameter (Step 10 generalization)', () => {
|
||||||
|
const custom = '/path/to/other-worker.mjs';
|
||||||
|
const { args } = buildSandboxedWorker('/tmp', ['--foo'], custom);
|
||||||
|
const joined = args.join(' ');
|
||||||
|
// Must contain the custom path, and NOT the default VSIX worker.
|
||||||
|
assert.ok(joined.includes(custom), `expected custom worker in args: ${joined}`);
|
||||||
|
assert.ok(!joined.includes('vsix-fetch-worker.mjs'),
|
||||||
|
`custom worker should have replaced default: ${joined}`);
|
||||||
|
assert.match(joined, /--foo/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('defaults to DEFAULT_VSIX_WORKER_PATH when workerPath omitted', () => {
|
||||||
|
const { args } = buildSandboxedWorker('/tmp', ['--url', 'https://x/', '--tmpdir', '/tmp']);
|
||||||
|
assert.ok(args.some((a) => a === DEFAULT_VSIX_WORKER_PATH),
|
||||||
|
`expected DEFAULT_VSIX_WORKER_PATH in args: ${args.join(' ')}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('exports DEFAULT_JETBRAINS_WORKER_PATH (Step 10 constant for Step 12)', () => {
|
||||||
|
assert.ok(typeof DEFAULT_JETBRAINS_WORKER_PATH === 'string');
|
||||||
|
assert.match(DEFAULT_JETBRAINS_WORKER_PATH, /jetbrains-fetch-worker\.mjs$/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('vsix-sandbox — runPluginWorker (generalized runner)', () => {
|
||||||
|
it('is exported as a function', () => {
|
||||||
|
assert.equal(typeof runPluginWorker, 'function');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('vsix-sandbox — runVsixWorker (live worker, no network)', () => {
|
describe('vsix-sandbox — runVsixWorker (live worker, no network)', () => {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue