affaan-m/ECC · critical · Error

Invalid ECC repo root: missing package.json at

Error message

Invalid ECC repo root: missing package.json at ${packageJsonPath}

What it means

assertMemoryDirectorySafe applies the same symlink check that the root gets, but to a subdirectory of the vault (asserting it stays within the trusted root). If the directory exists and lstat reports a symbolic link, the library refuses to traverse it. This blocks symlink-based escapes from inside the vault (e.g. an attacker planting 'project -> /etc' inside the memory directory).

Solutions

  1. Replace the symlinked directory with a real directory and copy/move its contents in place.
  2. Audit the vault tree for unexpected links (`find ~/.ecc-memory -type l`) and remove any you did not create.
  3. Point the whole scope root config at the real location if you intentionally keep the data elsewhere.
  4. Re-check directory contents after any restore/sync operation that may have reintroduced symlinks.

Example fix

// before (shell)
ln -s /shared/notes ~/.ecc-memory/project/notes

// after (shell)
rm ~/.ecc-memory/project/notes && mkdir ~/.ecc-memory/project/notes && cp -rL /shared/notes/. ~/.ecc-memory/project/notes/
Defensive patterns

Strategy: validation

Validate before calling

const fs = require('fs');
function assertNoSymlinkedSubdirs(vaultRoot) {
  for (const entry of fs.readdirSync(vaultRoot)) {
    const p = `${vaultRoot}/${entry}`;
    if (fs.lstatSync(p).isSymbolicLink()) {
      throw new Error(`Symlinked directory inside vault: ${p}. Replace with a real directory.`);
    }
  }
}

Type guard

const isRealDirectory = (p) => { try { const st = fs.lstatSync(p); return st.isDirectory() && !st.isSymbolicLink(); } catch { return false; } };

Try / catch

try {
  saveMemory(scope, doc);
} catch (err) {
  if (err.message.includes('symlink directory')) {
    console.error(`Security: replace the symlinked vault subdirectory with a real one: ${err.message}`);
  } else throw err;
}

Prevention

When it happens

Trigger: Calling saveMemory or directory-listing paths when a scope subdirectory (e.g. the per-scope memory directory under the root) is a symlink. Triggered by directories/saveMemory callers whenever the checked directory path exists as a link.

Common situations: A user symlinked a subfolder of the vault to shared storage; a compromised process planted a symlink inside the vault; restoring from an archive that preserved symlinks; a package manager or tool replaced a vault subdirectory with a link during an update.

Understand the failure class

Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/3c9980d0e5503fcd. Report an issue: GitHub.

Appendix: source

Thrown at scripts/auto-update.js:144

    return path.dirname(record.state.target.root);
  }

  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');
  const installApplyPath = path.join(normalized, 'scripts', 'install-apply.js');

  if (!fs.existsSync(packageJsonPath)) {
    throw new Error(`Invalid ECC repo root: missing package.json at ${packageJsonPath}`);
  }

  if (!fs.existsSync(installApplyPath)) {
    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;
}

View on GitHub (pinned to 8321021c54)