mirror of
https://github.com/garrytan/gstack.git
synced 2026-07-01 14:05:43 +02:00
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:
@@ -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}. */
|
||||
|
||||
Reference in New Issue
Block a user