mirror of
https://github.com/affaan-m/ECC.git
synced 2026-08-29 11:19:39 +02:00
fix(config-protection): match protected filenames case-insensitively (#2543)
On a case-insensitive filesystem (macOS APFS/HFS+, Windows NTFS) a write to `.ESLINTRC.JS` lands on the exact same inode as `.eslintrc.js`, but the guard looked the basename up in PROTECTED_FILES with a case-sensitive `Set.has`. Every entry in that Set is lowercase, so any case-variant path missed the branch entirely and returned exit 0 — a single Write silently overwrote a live config while the hook reported success. Reproduced on macOS APFS: `.eslintrc.js` and `.ESLINTRC.JS` share one inode, yet the hook returned exit 2 for the former and exit 0 for the latter, and the uppercase write replaced the real config's contents. This is a one-step bypass of the whole guard and needs no shell access, unlike the known delete-then-recreate route. Fix: also test `basename.toLowerCase()`. All 32 PROTECTED_FILES entries are already lowercase, so the fallback is exact. On a genuinely case-sensitive filesystem this costs at most a false positive on a distinct file whose name differs from a protected one by case alone. Behaviour deliberately unchanged: first-time creation is still allowed (the bootstrap affordance), non-config paths still pass through, and the existing lstat/ENOENT fail-closed semantics are untouched. Test: adds a case-variant case that asserts exit 2. It guards itself with an inode comparison and skips on case-sensitive filesystems rather than asserting something untrue there. Verified in both directions — it FAILS against the unpatched hook (`Got 0; 0 !== 2`) and passes with the fix. Suite: 9/9.
This commit is contained in:
@@ -94,7 +94,14 @@ function run(inputOrRaw, options = {}) {
|
||||
if (!filePath) return { exitCode: 0 };
|
||||
|
||||
const basename = path.basename(filePath);
|
||||
if (PROTECTED_FILES.has(basename)) {
|
||||
// Match case-insensitively. Every PROTECTED_FILES entry is lowercase, and on
|
||||
// case-insensitive filesystems (macOS APFS/HFS+, Windows NTFS) a write to
|
||||
// `.ESLINTRC.JS` lands on the very same inode as `.eslintrc.js`. A
|
||||
// case-sensitive Set lookup therefore let a single case-variant Write
|
||||
// silently overwrite the real config while the guard returned exit 0.
|
||||
// On genuinely case-sensitive filesystems this only costs a false positive
|
||||
// on a distinct file that differs from a protected name by case alone.
|
||||
if (PROTECTED_FILES.has(basename) || PROTECTED_FILES.has(basename.toLowerCase())) {
|
||||
// Allow first-time creation — there's no existing config to weaken.
|
||||
// The hook's purpose is blocking modifications; writing a brand-new
|
||||
// config file in a project that has none is a legitimate bootstrap
|
||||
|
||||
Reference in New Issue
Block a user