affaan-m/ECC · error · Error

Refusing to access memory through symlink directory

Error message

Refusing to access memory through symlink directory: ${directory}

What it means

assertMemoryDirectorySafe throws when the memory root itself (or a directory encountered while walking to it) is a symlink, blocking symlink-based escapes from the vault. The faulting input is the symlinked directory path in the message.

Solutions

  1. Remove the symlink and use a real directory for memory storage.
  2. Point ECC_MEMORY_* env vars at the physical path instead of a symlinked one.
  3. If symlinks are intentional in your setup, relocate the vault root above the link.
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at scripts/lib/memory-vault.js:119 when the library encounters an invalid state.

Common situations: See trigger scenarios.


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

Appendix: source

Thrown at scripts/lib/memory-vault.js:119

  const root = roots[scope];
  if (typeof root !== 'string' || root.length === 0) {
    throw new Error(`No memory root is configured for scope "${scope}".`);
  }
  const boundary = roots[VAULT_ROOT_BOUNDARIES]?.[scope];
  if (typeof boundary !== 'string' || boundary.length === 0) {
    throw new Error(`No trusted boundary policy is configured for memory scope "${scope}".`);
  }
  assertWithinTrustedRoot(root, boundary, 'access memory through a symlink');
  if (fs.existsSync(root) && fs.lstatSync(root).isSymbolicLink()) {
    throw new Error(`Refusing to access memory through symlink root: ${root}`);
  }
  return root;
}

function assertMemoryDirectorySafe(directory, root) {
  assertWithinTrustedRoot(directory, root, 'access memory directory');
  if (fs.existsSync(directory) && fs.lstatSync(directory).isSymbolicLink()) {
    throw new Error(`Refusing to access memory through symlink directory: ${directory}`);
  }
  return directory;
}

function sameFileIdentity(left, right) {
  // The inode is the primary identity signal and must always match.
  if (left.ino !== right.ino) {
    return false;
  }
  // libuv 1.49.0 through 1.50.x resolve path-based stat() and lstat() on Windows
  // through GetFileInformationByName, which leaves the volume serial unset, while
  // fstat() on an open handle reports it. Comparing the two then never matches and
  // every vault read and write is rejected. libuv 82cdfb75f fixed this in 1.51.0,
  // so only Node 22.12-22.16 and 24.0-24.1 are affected, but the guard should not
  // depend on the runtime's patch level. Compare dev only when both sides report
  // one; POSIX always does, so the original strict behaviour is preserved there.
  if (!left.dev || !right.dev) {
    return true;

View on GitHub (pinned to 8321021c54)