harden(sync): close staging-guard TOCTOU + fail hard on marker write (#1802 C5)

checkOwnedStagingDir() now returns the realpath-resolved canonicalPath on a
pass, and cleanupStagingDir() rmSync's that instead of the raw input — closing
the gap where the input is a symlink swapped between the ownership check and the
delete. makeStagingDir() tears down the partial dir and rethrows if the marker
write fails, so a marker-less dir (which the guard would refuse forever) can
never leak.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-06-03 07:21:58 -07:00
parent 661ba50169
commit a8591f88c8
3 changed files with 50 additions and 3 deletions
+8 -1
View File
@@ -50,6 +50,13 @@ export interface StagingVerdict {
ok: boolean;
/** Precise rejection reason, for actionable logging. Undefined when ok. */
reason?: string;
/**
* The realpath-resolved directory the verdict actually validated. Present only
* when ok. Callers that delete MUST `rmSync` this path, not the raw input —
* deleting the canonical path closes the TOCTOU gap where the input is a
* symlink swapped between this check and the delete (#1802 C5).
*/
canonicalPath?: string;
}
/**
@@ -100,7 +107,7 @@ export function checkOwnedStagingDir(dir: string, gstackHome: string): StagingVe
} catch {
return { ok: false, reason: `missing "${STAGING_MARKER}" marker — not minted by makeStagingDir` };
}
return { ok: true };
return { ok: true, canonicalPath: canon };
}
/** Boolean convenience wrapper around {@link checkOwnedStagingDir}. */