// nested-clone-scope.test.mjs — a foreign clone that sits UNDER the caller's // cwd must not configure its own scan (v8.1.1). // // v8.1.0 (S3b/S3c) honored .llm-security-ignore and .llm-security/policy.json // for any target at or below cwd. A clone under cwd (cwd = $HOME, or a vendor // clone inside a project) could therefore still ship `**` in its ignore file // and a policy that raises the entropy thresholds and loads its own SIG rules. // v8.1.1: own tree = at or below cwd AND the same git root as cwd. // // Fixture: the known HIGH entropy blob + custom SIG rule from // policy-scope.test.mjs, plus a `**` ignore file (would suppress everything). // (a) cwd = a repo, target = a nested clone (own `.git`) WITH the config: // findings identical to the same clone WITHOUT the config files. // (b) cwd = a repo, target = a plain subdir of that SAME repo with the // policy: still honored (known-positive — keeps (a) from being vacuous). // (c) cwd = a $HOME-like dir with no git root, target = a clone under it: // foreign, the known finding survives. // All under $HOME: outside os.tmpdir() (always foreign) and this repo's tree. 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 { spawn, spawnSync } from 'node:child_process'; import { mkdirSync, writeFileSync, rmSync } from 'node:fs'; import crypto from 'node:crypto'; import { mkOwnTreeDir } from '../helpers/own-tree.mjs'; const __dirname = dirname(fileURLToPath(import.meta.url)); const ORCHESTRATOR = resolve(__dirname, '../../scanners/scan-orchestrator.mjs'); // Not a real credential — a known-positive blob for the entropy scanner only. const HIGH_ENTROPY_BLOB = crypto.randomBytes(72).toString('base64'); const CUSTOM_MARKER = 'NESTEDSCOPEMARKER_4417'; const CUSTOM_RULE_ID = 'CUSTOM-NESTED-001'; const UNREACHABLE = { entropy: 99, minLen: 1_000_000 }; function writeFixture(dir, { withPolicy, withIgnore }) { mkdirSync(dir, { recursive: true }); writeFileSync(join(dir, 'config.js'), `const payload = "${HIGH_ENTROPY_BLOB}";\nmodule.exports = { payload };\n`); writeFileSync(join(dir, 'notes.txt'), `prefix ${CUSTOM_MARKER} suffix\n`); writeFileSync(join(dir, 'custom-sigs.json'), JSON.stringify({ rules: [{ id: CUSTOM_RULE_ID, family: 'webshell', severity: 'high', pattern: 'NESTEDSCOPEMARKER_[0-9]+', description: 'Target-supplied custom rule (must only load for the own working tree)', }], })); if (withPolicy) { mkdirSync(join(dir, '.llm-security'), { recursive: true }); writeFileSync(join(dir, '.llm-security', 'policy.json'), JSON.stringify({ entropy: { thresholds: { critical: UNREACHABLE, high: UNREACHABLE, medium: UNREACHABLE } }, sig: { custom_rules_path: 'custom-sigs.json' }, })); } if (withIgnore) writeFileSync(join(dir, '.llm-security-ignore'), '**\n'); } function git(dir, args) { const r = spawnSync('git', ['-C', dir, ...args], { encoding: 'utf8', env: { ...process.env, GIT_AUTHOR_NAME: 't', GIT_AUTHOR_EMAIL: 't@example.invalid', GIT_COMMITTER_NAME: 't', GIT_COMMITTER_EMAIL: 't@example.invalid', GIT_AUTHOR_DATE: '2026-01-01T00:00:00Z', GIT_COMMITTER_DATE: '2026-01-01T00:00:00Z', }, }); assert.equal(r.status, 0, `git ${args.join(' ')} failed: ${r.stderr}`); } /** A "clone": its own git root with every fixture file committed. */ function makeClone(dir, opts) { writeFixture(dir, opts); git(dir, ['init', '-q']); git(dir, ['add', '-A']); git(dir, ['commit', '-q', '-m', 'fixture']); } function runOrchestrator(target, cwd) { return new Promise((resolveP) => { const stdout = []; const stderr = []; const child = spawn('node', [ORCHESTRATOR, target], { cwd, timeout: 180_000, stdio: ['ignore', 'pipe', 'pipe'] }); child.stdout.on('data', (c) => stdout.push(c)); child.stderr.on('data', (c) => stderr.push(c)); child.on('close', (code) => { resolveP({ code: code ?? 1, env: JSON.parse(Buffer.concat(stdout).toString('utf8')), stderr: Buffer.concat(stderr).toString('utf8'), }); }); }); } const entropyFindings = (env) => env?.scanners?.entropy?.findings || []; const customSigFindings = (env) => (env?.scanners?.sig?.findings || []) .filter((f) => String(f.evidence || '').includes(CUSTOM_RULE_ID)); /** Order-independent, id-independent fingerprint of every finding. */ function findingKeys(env) { const keys = []; for (const [name, result] of Object.entries(env?.scanners || {})) { for (const f of result.findings || []) { keys.push(`${name}|${f.severity}|${f.title}|${f.file}|${f.line ?? ''}`); } } return keys.sort(); } describe('a clone under the caller\'s cwd is a foreign target (v8.1.1)', () => { let root; let work; let homeish; before(() => { root = mkOwnTreeDir('nested-clone-scope-'); work = join(root, 'work'); mkdirSync(work, { recursive: true }); git(work, ['init', '-q']); homeish = join(root, 'homeish'); mkdirSync(homeish, { recursive: true }); }); after(() => { rmSync(root, { recursive: true, force: true }); }); describe('(a) cwd = repo, target = nested clone with policy.json + `**` ignore', () => { let withRun; let withoutRun; before(async () => { // Same parent, so the only difference the scanners can see is the config. makeClone(join(work, 'vendor', 'clone'), { withPolicy: true, withIgnore: true }); makeClone(join(work, 'vendor', 'clone-noconfig'), { withPolicy: false, withIgnore: false }); withRun = await runOrchestrator(join(work, 'vendor', 'clone'), work); withoutRun = await runOrchestrator(join(work, 'vendor', 'clone-noconfig'), work); }); it('the known HIGH entropy finding survives', () => { assert.equal(entropyFindings(withRun.env).length, 1); }); it('the clone-supplied custom SIG rule is not loaded', () => { assert.equal(customSigFindings(withRun.env).length, 0); }); it('nothing is suppressed', () => { assert.ok(!withRun.env.suppressed, `suppressed must be falsy, got ${withRun.env.suppressed}`); }); it('same verdict and same findings as the same clone without the config files', () => { assert.equal(withRun.env.aggregate.verdict, withoutRun.env.aggregate.verdict); assert.deepEqual(findingKeys(withRun.env), findingKeys(withoutRun.env)); }); it('stderr says both config files were not honored', () => { assert.match(withRun.stderr, /\.llm-security-ignore.*ignored/i); assert.match(withRun.stderr, /policy\.json.*ignored/i); }); }); describe('(b) cwd = repo, target = plain subdir of the SAME repo (known-positive)', () => { let run; before(async () => { writeFixture(join(work, 'inner'), { withPolicy: true, withIgnore: false }); run = await runOrchestrator(join(work, 'inner'), work); }); it('the raised thresholds silence the entropy finding', () => { assert.equal(entropyFindings(run.env).length, 0); }); it('the custom SIG rule loads and fires', () => { assert.equal(customSigFindings(run.env).length, 1); }); }); describe('(c) cwd = dir with no git root, target = clone under it', () => { let run; before(async () => { makeClone(join(homeish, 'clone'), { withPolicy: true, withIgnore: true }); run = await runOrchestrator(join(homeish, 'clone'), homeish); }); it('the known HIGH entropy finding survives and nothing is suppressed', () => { assert.equal(entropyFindings(run.env).length, 1); assert.ok(!run.env.suppressed); }); it('the clone-supplied custom SIG rule is not loaded', () => { assert.equal(customSigFindings(run.env).length, 0); }); }); });