fix: reject malformed generated roots
ci/woodpecker/pr/ci Pipeline was successful

This commit is contained in:
2026-07-31 19:25:44 -05:00
parent f710a8d7fb
commit 98046a76a2
4 changed files with 28 additions and 10 deletions
+11 -7
View File
@@ -164,11 +164,11 @@ export async function runPreflight({ root = process.cwd(), uid = process.getuid?
let generated = [];
try {
const nextStats = await lstat(nextDir);
if (nextStats.isSymbolicLink()) {
if (!nextStats.isDirectory() || nextStats.isSymbolicLink()) {
return {
code: GENERATED_STATE_EXIT,
message:
'MOSAIC_PREFLIGHT_GENERATED_STATE: apps/web/.next contains a symbolic link and is not trustworthy; run pnpm clean:generated, then rerun the gate',
'MOSAIC_PREFLIGHT_GENERATED_STATE: apps/web/.next must be a real directory, not a symbolic link, and is not trustworthy; run pnpm clean:generated, then rerun the gate',
};
}
generated = [nextDir, ...(await entries(nextDir))];
@@ -183,11 +183,15 @@ export async function runPreflight({ root = process.cwd(), uid = process.getuid?
if (uid !== undefined && stats.uid !== uid) foreign.push(path.relative(root, target));
}
// Threat model: this detects accidental, independent, or stale generated-state
// mutation. It does NOT defend against an actor with same-UID write access to
// the generated tree: that actor can regenerate both the manifest and marker
// consistently (CWE-345). No local construction can without a trust anchor
// outside that actor's authority.
// Detects accidental, independent, stale, and foreign-residue mutation of
// generated state: the class this check was born from was a five-month-stale
// .next whose validator referenced deleted pages and produced 19 phantom TS2307
// errors indistinguishable from real type errors.
//
// Does NOT defend against an actor with same-UID write access to the generated
// tree, which can regenerate both the manifest and marker consistently
// (CWE-345). No local construction can, absent a trust anchor outside that
// actor's authority. RM-59 tracks executor/spine-side attestation.
let certification = null;
let certifiedManifest = null;
try {