test(v8.1.3): red tests for plan points 1, 2, 3, 5 and addenda a-c
Each test fails on fd7de23:
- own-working-tree: venv site-packages, vendor/, config-dir skills/
(punkt 1); ~/.claude/plugins next to $CLAUDE_CONFIG_DIR and a leading
~ in the variable (punkt 2); NODE_MODULES case variant and a
case-mismatched parent segment (punkt 3, closes v8.1.2 punkt 4).
- watch-cron-scope: a watched project's own ignore file is honored
(punkt 5).
- av-surface (b2): no runnable base64-to-shell line with a short
command blob (addendum a; 3 hits today).
- doc-consistency: scanner-reference Knowledge Files matches knowledge/
(addendum b); ci-cd-guide makes no offline claim (addendum c).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
fd7de230e4
commit
b62c3e60f5
4 changed files with 290 additions and 0 deletions
100
tests/scanners/watch-cron-scope.test.mjs
Normal file
100
tests/scanners/watch-cron-scope.test.mjs
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
// watch-cron-scope.test.mjs — `watch-cron.mjs` must scan each watched project
|
||||
// as that project's own working tree (v8.1.3, PLAN § v8.1.3 punkt 5).
|
||||
//
|
||||
// Since v8.1.0 a project's .llm-security-ignore and .llm-security/policy.json
|
||||
// are honored only when the target is the caller's own working tree (at or
|
||||
// below cwd, same git root). watch-cron.mjs started the orchestrator with
|
||||
// `cwd: PLUGIN_ROOT`, so every watched project was outside cwd and its own
|
||||
// suppressions were dropped: the user was shown findings they had already
|
||||
// suppressed. v8.1.3 runs the orchestrator with the project dir as cwd.
|
||||
//
|
||||
// (a) a watched project WITH a `**` ignore file reports the same counts as
|
||||
// the orchestrator run from inside that project (its config honored);
|
||||
// (b) a sibling WITHOUT the ignore file reports findings (known-positive:
|
||||
// keeps (a) from passing on a fixture that produces nothing).
|
||||
// Fixtures under $HOME: outside os.tmpdir() (always foreign) and this repo.
|
||||
// watch-cron writes PLUGIN_ROOT/reports/watch/latest.json; the test restores
|
||||
// whatever was there before.
|
||||
|
||||
import { describe, it, before, after } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { resolve, dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { mkdirSync, writeFileSync, readFileSync, rmSync, existsSync } from 'node:fs';
|
||||
import crypto from 'node:crypto';
|
||||
import { mkOwnTreeDir } from '../helpers/own-tree.mjs';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const PLUGIN_ROOT = resolve(__dirname, '../..');
|
||||
const WATCH_CRON = join(PLUGIN_ROOT, 'scanners', 'watch-cron.mjs');
|
||||
const ORCHESTRATOR = join(PLUGIN_ROOT, 'scanners', 'scan-orchestrator.mjs');
|
||||
const LATEST = join(PLUGIN_ROOT, 'reports', 'watch', 'latest.json');
|
||||
|
||||
// Not a real credential — a known-positive blob for the entropy scanner only.
|
||||
const HIGH_ENTROPY_BLOB = crypto.randomBytes(72).toString('base64');
|
||||
|
||||
function writeProject(dir, { withIgnore }) {
|
||||
mkdirSync(dir, { recursive: true });
|
||||
const r = spawnSync('git', ['init', '-q', dir], { encoding: 'utf8' });
|
||||
assert.equal(r.status, 0, `git init failed: ${r.stderr}`);
|
||||
writeFileSync(join(dir, 'config.js'), `const payload = "${HIGH_ENTROPY_BLOB}";\nmodule.exports = { payload };\n`);
|
||||
if (withIgnore) writeFileSync(join(dir, '.llm-security-ignore'), '**\n');
|
||||
}
|
||||
|
||||
const total = (counts) => Object.values(counts || {}).reduce((a, b) => a + b, 0);
|
||||
|
||||
describe('watch-cron: a watched project is scanned as its own working tree', () => {
|
||||
let root;
|
||||
let savedLatest = null;
|
||||
let watched;
|
||||
let direct;
|
||||
|
||||
before(() => {
|
||||
root = mkOwnTreeDir('watch-cron-');
|
||||
writeProject(join(root, 'suppressed'), { withIgnore: true });
|
||||
writeProject(join(root, 'plain'), { withIgnore: false });
|
||||
const config = join(root, 'watch-config.json');
|
||||
writeFileSync(config, JSON.stringify({
|
||||
targets: [
|
||||
{ path: join(root, 'suppressed'), label: 'suppressed' },
|
||||
{ path: join(root, 'plain'), label: 'plain' },
|
||||
],
|
||||
options: { baseline: false, saveBaseline: false },
|
||||
}));
|
||||
if (existsSync(LATEST)) savedLatest = readFileSync(LATEST);
|
||||
|
||||
const run = spawnSync(process.execPath, [WATCH_CRON, '--config', config], {
|
||||
cwd: root, encoding: 'utf8', timeout: 600_000,
|
||||
});
|
||||
assert.ok(run.status !== null, `watch-cron did not finish: ${run.error?.message}`);
|
||||
watched = JSON.parse(readFileSync(LATEST, 'utf8'));
|
||||
|
||||
// Reference: the orchestrator run from inside the project (own tree).
|
||||
const ref = spawnSync(process.execPath, [ORCHESTRATOR, '.'], {
|
||||
cwd: join(root, 'suppressed'), encoding: 'utf8', timeout: 300_000, maxBuffer: 64 * 1024 * 1024,
|
||||
});
|
||||
direct = JSON.parse(ref.stdout).aggregate;
|
||||
});
|
||||
|
||||
after(() => {
|
||||
if (savedLatest !== null) writeFileSync(LATEST, savedLatest);
|
||||
else rmSync(LATEST, { force: true });
|
||||
rmSync(root, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('(b) the project without an ignore file reports findings (known-positive)', () => {
|
||||
const plain = watched.targets.find(t => t.label === 'plain');
|
||||
assert.equal(plain.error, null, `plain target errored: ${plain.error}`);
|
||||
assert.ok(total(plain.counts) > 0, `expected findings, got ${JSON.stringify(plain.counts)}`);
|
||||
});
|
||||
|
||||
it('(a) the project\'s own ignore file is honored (same counts as a run from inside it)', () => {
|
||||
const suppressed = watched.targets.find(t => t.label === 'suppressed');
|
||||
assert.equal(suppressed.error, null, `suppressed target errored: ${suppressed.error}`);
|
||||
assert.deepEqual(suppressed.counts, direct.counts);
|
||||
const plain = watched.targets.find(t => t.label === 'plain');
|
||||
assert.ok(total(suppressed.counts) < total(plain.counts),
|
||||
`ignore file had no effect: ${JSON.stringify(suppressed.counts)} vs ${JSON.stringify(plain.counts)}`);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue