fix(#2701): reject NUL-corrupted plan/state artifacts at the validator entry points (#2829)

* test(#2701): failing-first regression for NUL-corrupted plan/state validators

* fix(#2701): reject NUL-corrupted plan/state artifacts at the validator entry points

* fix(#2701): seed STATE.md in test (writeState); add NUL-path guards to validate/verify for parity (review)

* docs(changeset): #2701 validators reject NUL-corrupted artifacts

* docs(changeset): backfill #2701 PR number to 2829
This commit is contained in:
Tom Boucher
2026-07-29 12:29:45 -04:00
committed by GitHub
parent 69dbf28ca7
commit 6229f0e55c
6 changed files with 277 additions and 1 deletions

View File

@@ -49,6 +49,7 @@ import {
import { tokenizeHeadings, collectSection, replaceSection } from './markdown-sectionizer.cjs';
import type { HeadingToken } from './markdown-sectionizer.cjs';
import { parseMarkdownTable, updateTableCell, deleteTableRow, insertTableRow, splitTableRow, isDelimiterRow } from './markdown-table.cjs';
import { textEncodingError } from './validate.cjs';
// ─── Types ────────────────────────────────────────────────────────────────────
@@ -2639,6 +2640,14 @@ function cmdStateValidate(cwd: string, raw: boolean): void {
}
const content = fs.readFileSync(statePath, 'utf-8');
// #2701: fail loud on NUL/binary corruption before drift checks. A corrupt
// STATE.md otherwise validates as clean and is silently skipped by recursive
// searchers downstream, reading as "absent" rather than "corrupt."
const encErr = textEncodingError(content, 'STATE.md');
if (encErr) {
output({ valid: false, warnings: [encErr], drift: {} }, raw, undefined);
return;
}
const warnings: string[] = [];
const drift: Record<string, unknown> = {};