{"record":{"id":"ae4a6de6f29e0389","repo":"affaan-m/ECC","slug":"unable-to-infer-ecc-repo-root-from-install-state-o","errorCode":null,"errorMessage":"Unable to infer ECC repo root from install-state operations","messagePattern":"Unable to infer ECC repo root from install-state operations","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"critical","filePath":"scripts/auto-update.js","lineNumber":81,"sourceCode":"    if (typeof operation.sourceRelativePath !== 'string' || !operation.sourceRelativePath.trim()) {\n      continue;\n    }\n\n    const relativeParts = operation.sourceRelativePath.split(/[\\\\/]+/).filter(Boolean);\n\n    if (relativeParts.length === 0) {\n      continue;\n    }\n\n    let repoRoot = path.resolve(operation.sourcePath);\n    for (let index = 0; index < relativeParts.length; index += 1) {\n      repoRoot = path.dirname(repoRoot);\n    }\n\n    return repoRoot;\n  }\n\n  throw new Error('Unable to infer ECC repo root from install-state operations');\n}\n\nfunction buildInstallApplyArgs(record) {\n  const state = record.state;\n  const target = state.target.target || record.adapter.target;\n  const request = state.request || {};\n  const args = [];\n  const hookConsent = getRecordedHookConsent(state);\n\n  if (target) {\n    args.push('--target', target);\n  }\n\n  if (request.profile) {\n    args.push('--profile', request.profile);\n  }\n\n  if (Array.isArray(request.modules) && request.modules.length > 0) {","sourceCodeStart":63,"sourceCodeEnd":99,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/scripts/auto-update.js#L63-L99","documentation":"Even with a root and boundary configured, assertMemoryRootSafe does a final runtime check: if the root path exists and lstat reports it is a symbolic link, it refuses to use it. This prevents an attacker from swapping the memory root for a symlink pointing outside the trusted boundary (e.g. to ~/.ssh or system files). The check uses lstat (not stat) so a link is detected before resolution.","triggerScenarios":"The configured scope root path resolves to a symlink — e.g. the vault directory was replaced by a link to another location, an attacker (or a syncing tool like Dropbox/git) created a link, or the user pre-created the root as 'ln -s /elsewhere vault' expecting it to work.","commonSituations":"Users symlink their memory vault into a dotfiles repo or cloud-sync folder; a restore tool recreated the directory as a symlink; a malicious or buggy script replaced the root to exfiltrate memory writes; moving the vault with `ln -s` instead of editing the config path.","solutions":["Remove the symlink (rm the link) and create a real directory at the root path, moving the data into it.","Update the roots config to point directly at the real (non-symlink) target directory instead of linking.","Investigate how the symlink appeared if you did not create it — treat it as a potential tampering signal.","If you legitimately need the data elsewhere, move the files and set the config path to the new location."],"exampleFix":"// before (shell)\nln -s ~/Dropbox/vault ~/.ecc-memory/project\n\n// after (shell)\nmv ~/Dropbox/vault ~/.ecc-memory/project   # real directory, config points here directly","handlingStrategy":"validation","validationCode":"const fs = require('fs');\nfunction assertRealDirectory(path) {\n  if (fs.existsSync(path) && fs.lstatSync(path).isSymbolicLink()) {\n    throw new Error(`Refusing to use symlinked memory root: ${path}. Replace with a real directory.`);\n  }\n}","typeGuard":"const isRealDir = (p) => fs.existsSync(p) && fs.lstatSync(p).isDirectory() && !fs.lstatSync(p).isSymbolicLink();","tryCatchPattern":"try {\n  const root = resolveMemoryRoot(roots, scope);\n} catch (err) {\n  if (err.message.includes('symlink root')) {\n    console.error(`Security: ${err.message}. Move the real data and point the config at it directly.`);\n    process.exitCode = 1;\n  } else throw err;\n}","preventionTips":["Never create memory roots with `ln -s`; move data and update the config path instead.","Run a periodic check (`find <vault> -type l`) to detect unexpected symlinks.","Exclude the vault from cloud-sync tools that create links during restore.","Treat an unexplained symlink as tampering and investigate before deleting it."],"tags":["security","symlink","memory-vault","path-traversal"],"backgroundTag":"path-traversal-blocked","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}