fix(watch): run the orchestrator with the watched project as cwd
PLAN § v8.1.3 punkt 5. watch-cron.mjs started the orchestrator with
cwd = pluginRoot, so every watched project was outside cwd, i.e. not the
caller's own working tree, and its .llm-security-ignore / policy.json
were dropped: the user saw findings they had already suppressed. The
orchestrator now runs with cwd = the target dir (its parent for a file);
a relative target.path still resolves against pluginRoot as before.
The scope test's entropy blob is now fixed instead of random: one run
with a random blob reported zero findings for both projects, probably a
blob starting with `/` (skipped as a path, 1 in 64). Red on 002c0ba with
the fixed blob, verified.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
002c0bac99
commit
62e3cade60
2 changed files with 25 additions and 4 deletions
|
|
@ -8,7 +8,7 @@
|
||||||
// Exit: 0 = all ALLOW, 1 = any WARNING, 2 = any BLOCK
|
// Exit: 0 = all ALLOW, 1 = any WARNING, 2 = any BLOCK
|
||||||
|
|
||||||
import { resolve, join, dirname, basename } from 'node:path';
|
import { resolve, join, dirname, basename } from 'node:path';
|
||||||
import { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync } from 'node:fs';
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync, statSync } from 'node:fs';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
import { spawnSync } from 'node:child_process';
|
import { spawnSync } from 'node:child_process';
|
||||||
import { tmpdir } from 'node:os';
|
import { tmpdir } from 'node:os';
|
||||||
|
|
@ -66,18 +66,32 @@ function loadConfig(configPath) {
|
||||||
|
|
||||||
// --- Scan Execution ---
|
// --- Scan Execution ---
|
||||||
|
|
||||||
|
// The orchestrator runs with the watched project as cwd, so the project is
|
||||||
|
// its own working tree and its .llm-security-ignore / policy.json are honored
|
||||||
|
// (scanners/lib/own-working-tree.mjs). Until v8.1.3 it ran with cwd =
|
||||||
|
// pluginRoot, which made every watched project foreign. A relative
|
||||||
|
// target.path still resolves against pluginRoot, as it did then.
|
||||||
|
function scanCwd(targetPath) {
|
||||||
|
try {
|
||||||
|
return statSync(targetPath).isDirectory() ? targetPath : dirname(targetPath);
|
||||||
|
} catch {
|
||||||
|
return dirname(targetPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function runScan(target, options, pluginRoot) {
|
function runScan(target, options, pluginRoot) {
|
||||||
const label = target.label || basename(target.path);
|
const label = target.label || basename(target.path);
|
||||||
const tmpFile = join(tmpdir(), `llm-security-watch-${Date.now()}-${label}.json`);
|
const tmpFile = join(tmpdir(), `llm-security-watch-${Date.now()}-${label}.json`);
|
||||||
|
const targetPath = resolve(pluginRoot, target.path);
|
||||||
|
|
||||||
const args = [ORCHESTRATOR, target.path, '--output-file', tmpFile];
|
const args = [ORCHESTRATOR, targetPath, '--output-file', tmpFile];
|
||||||
if (options.baseline !== false) args.push('--baseline');
|
if (options.baseline !== false) args.push('--baseline');
|
||||||
if (options.saveBaseline !== false) args.push('--save-baseline');
|
if (options.saveBaseline !== false) args.push('--save-baseline');
|
||||||
|
|
||||||
const result = spawnSync(process.execPath, args, {
|
const result = spawnSync(process.execPath, args, {
|
||||||
encoding: 'utf8',
|
encoding: 'utf8',
|
||||||
timeout: SCAN_TIMEOUT,
|
timeout: SCAN_TIMEOUT,
|
||||||
cwd: pluginRoot
|
cwd: scanCwd(targetPath)
|
||||||
});
|
});
|
||||||
|
|
||||||
const entry = {
|
const entry = {
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,14 @@ const ORCHESTRATOR = join(PLUGIN_ROOT, 'scanners', 'scan-orchestrator.mjs');
|
||||||
const LATEST = join(PLUGIN_ROOT, 'reports', 'watch', 'latest.json');
|
const LATEST = join(PLUGIN_ROOT, 'reports', 'watch', 'latest.json');
|
||||||
|
|
||||||
// Not a real credential — a known-positive blob for the entropy scanner only.
|
// Not a real credential — a known-positive blob for the entropy scanner only.
|
||||||
const HIGH_ENTROPY_BLOB = crypto.randomBytes(72).toString('base64');
|
// Fixed, not random: a random blob starts with `/` one time in 64, and the
|
||||||
|
// entropy scanner skips a string that starts with `/` as a path. One run of
|
||||||
|
// this test with a random blob reported zero findings for both projects; that
|
||||||
|
// cause is the probable one, not a proven one (the blob was not logged).
|
||||||
|
const HIGH_ENTROPY_BLOB = Buffer.concat([
|
||||||
|
crypto.createHash('sha512').update('watch-cron-scope-1').digest(),
|
||||||
|
crypto.createHash('sha512').update('watch-cron-scope-2').digest(),
|
||||||
|
]).subarray(0, 72).toString('base64');
|
||||||
|
|
||||||
function writeProject(dir, { withIgnore }) {
|
function writeProject(dir, { withIgnore }) {
|
||||||
mkdirSync(dir, { recursive: true });
|
mkdirSync(dir, { recursive: true });
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue