fix(llm-security): v8 Phase 1 — Berry lockfile, nested-v1 recursion, per-occurrence strip attribution
Three TDD-first fixes surviving the B8 roadmap bucket (v8.0.0-plan.local.md Phase 1, items 1-3; item 4 JAR hardening scoped out at review): - supply-chain-recheck.mjs parseYarnLock: ported the hook's per-entry parser (pre-install-supply-chain.mjs) so Berry's `version: x` format (unquoted) is recognized alongside Classic's `version "x"` — Berry lockfiles previously yielded zero deps, silently missing pinned compromised packages. - supply-chain-recheck.mjs parsePackageLock: lockfileVersion-1 fallback now recurses nested `dependencies`, mirroring the hook's walk() — a transitive, non-hoisted compromised copy below the top level was previously invisible. - content-extractor.mjs stripInjection: attribution moved from a global `Set<label>` to `Set<label::lineIndex>`. The old check silenced the unstripped flag for ANY occurrence of a label once ANY occurrence had been line-redacted, so a second, cross-line-only encoded occurrence of the same label survived into sanitized output without being flagged. Full suite 2019/2019 (one known-flaky timing test confirmed green in isolation).
This commit is contained in:
parent
b929ddc2bb
commit
ff4d8e8a31
5 changed files with 149 additions and 33 deletions
|
|
@ -137,7 +137,11 @@ function stripInjection(text, file) {
|
|||
// Runs first so that line indices still line up with the original text.
|
||||
const lines = text.split('\n');
|
||||
const normalizedLines = isDifferent ? lines.map(l => normalizeForScan(l)) : [];
|
||||
const attributed = new Set();
|
||||
// Keyed by `${label}::${lineIndex}`, not just label — a label redacted on
|
||||
// one line must not silence the unstripped check for a DIFFERENT
|
||||
// occurrence of the same label elsewhere (e.g. a second, cross-line-only
|
||||
// encoding Pass 1 couldn't catch). Attribution is judged per occurrence.
|
||||
const attributedLines = new Set();
|
||||
|
||||
if (isDifferent) {
|
||||
for (const { pattern, label } of allPatterns) {
|
||||
|
|
@ -147,7 +151,7 @@ function stripInjection(text, file) {
|
|||
if (lines[i].includes(STRIP_MARKER)) continue;
|
||||
if (toGlobal(pattern).test(normalizedLines[i])) {
|
||||
lines[i] = `[${STRIP_MARKER}: ${label}]`;
|
||||
attributed.add(label);
|
||||
attributedLines.add(`${label}::${i}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -164,8 +168,10 @@ function stripInjection(text, file) {
|
|||
const finding = { file, line, label, severity };
|
||||
const before = sanitized;
|
||||
sanitized = sanitized.replace(match[0], `[${STRIP_MARKER}: ${label}]`);
|
||||
// Neither a literal replace nor a line redaction removed this one.
|
||||
if (sanitized === before && !attributed.has(label)) finding.unstripped = true;
|
||||
// Neither a literal replace nor a line redaction removed THIS occurrence.
|
||||
if (sanitized === before && !attributedLines.has(`${label}::${line - 1}`)) {
|
||||
finding.unstripped = true;
|
||||
}
|
||||
findings.push(finding);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue