fix(security): contain install-state file ops to trusted root — RCE fix (GHSA-hfpv-w6mp-5g95)

Critical: project-local install-state (e.g. a cloned repo's .cursor/ecc-install-state.json)
is attacker-controllable, and repair/uninstall/auto-update replayed its operations with
destinationPath validated only for non-emptiness — confirmed arbitrary file write/delete
and chained RCE (write ~/.bashrc, .git/hooks, or run a planted install-apply.js).

- New scripts/lib/path-safety.js: assertWithinTrustedRoot() canonicalizes (incl. symlink
  escape via nearest-existing-ancestor realpath) and fails closed unless the destination is
  within the adapter-derived trusted root.
- install-lifecycle.js: gate executeRepairOperation + executeUninstallOperation + the
  install-state removal against record.targetRoot (the adapter-resolved root, NOT the
  attacker-supplied state.target.root).
- auto-update.js: validateRepoRoot now requires package.json name to be an official ECC
  package, so a planted nested repo can't drive auto-update into executing attacker code.
- 7 containment regression tests. Existing install-lifecycle/repair/uninstall/auto-update
  suites still green (legit destinations are within the root).
This commit is contained in:
Affaan Mustafa
2026-06-18 19:54:22 -04:00
parent 5e4f5533d7
commit 5994d3fac1
4 changed files with 201 additions and 4 deletions
+18
View File
@@ -123,6 +123,12 @@ function determineInstallCwd(record, repoRoot) {
return repoRoot;
}
// Recognized ECC package names. A repo root is only trusted to run its
// install-apply.js if its package.json identifies it as ECC — otherwise a
// cloned project that ships a nested `evil/{package.json,scripts/install-apply.js}`
// could drive auto-update into executing attacker code (GHSA-hfpv-w6mp-5g95).
const ECC_PACKAGE_NAMES = new Set(['ecc-universal', 'everything-claude-code']);
function validateRepoRoot(repoRoot) {
const normalized = path.resolve(repoRoot);
const packageJsonPath = path.join(normalized, 'package.json');
@@ -136,6 +142,18 @@ function validateRepoRoot(repoRoot) {
throw new Error(`Invalid ECC repo root: missing install script at ${installApplyPath}`);
}
let pkgName = null;
try {
pkgName = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')).name;
} catch {
throw new Error(`Invalid ECC repo root: unreadable package.json at ${packageJsonPath}`);
}
if (!ECC_PACKAGE_NAMES.has(pkgName)) {
throw new Error(
`Refusing to run install from untrusted repo root ${normalized}: package.json name '${pkgName}' is not an official ECC package.`
);
}
return normalized;
}