{"record":{"id":"55fc6c77e6f2f261","repo":"ruvnet/RuView","slug":"refusing-cli-access-repository-marker-escapes-the","errorCode":null,"errorMessage":"Refusing CLI access: repository marker escapes the trusted root","messagePattern":"Refusing CLI access: repository marker escapes the trusted root","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"harness/homecore/src/repo-trust.js","lineNumber":30,"sourceCode":"\nconst REQUIRED_MARKERS = Object.freeze([\n  '.git',\n  'README.md',\n  'v2/Cargo.toml',\n  'v2/crates/homecore/Cargo.toml',\n  'v2/crates/homecore-server/Cargo.toml',\n  'docs/adr/ADR-126-ruview-native-ha-port-master.md',\n]);\n\nfunction isWithin(parent, child) {\n  const rel = relative(parent, child);\n  return rel === '' || (!rel.startsWith('..') && !isAbsolute(rel));\n}\n\nfunction readContainedPrefix(root, path, maxBytes) {\n  const real = realpathSync(path);\n  if (!isWithin(root, real)) {\n    throw new Error('Refusing CLI access: repository marker escapes the trusted root');\n  }\n  const stat = statSync(real);\n  if (!stat.isFile()) {\n    throw new Error('Refusing CLI access: README marker is not a regular file');\n  }\n  const buffer = Buffer.alloc(Math.min(stat.size, maxBytes));\n  const descriptor = openSync(real, 'r');\n  try {\n    const bytes = readSync(descriptor, buffer, 0, buffer.length, 0);\n    return buffer.subarray(0, bytes).toString('utf8');\n  } finally {\n    closeSync(descriptor);\n  }\n}\n\nexport function looksLikeHomecoreRepo(path) {\n  if (!path || !existsSync(path)) return false;\n  return REQUIRED_MARKERS.every((marker) => existsSync(join(path, marker)));","sourceCodeStart":12,"sourceCodeEnd":48,"githubUrl":"https://github.com/ruvnet/RuView/blob/4685618388a5e49fad5b3005806f3bdd6a7c25c3/harness/homecore/src/repo-trust.js#L12-L48","documentation":"Homecore's repo-trust module realpaths the README.md marker and verifies the resolved path stays inside the trusted root before reading it. If README.md is a symlink whose target resolves outside the root, this security error aborts CLI access, preventing the trust check from reading arbitrary files outside the checkout.","triggerScenarios":"README.md in the checkout root is a symlink to a file outside the repository (shared docs folder, home directory, another checkout). readContainedPrefix realpaths it, isWithin(root, real) fails, and assertTrustedHomecoreRepo throws during trust verification.","commonSituations":"Repos linking README.md to an absolute external path, dotfile-managed checkouts, monorepos sharing a README by symlink, test fixtures built with symlinks.","solutions":["Replace the README.md symlink with a real file inside the repository (copy the content in)","If linking is required, link to a target still inside the checkout and re-run","Confirm where it resolves: readlink -f README.md"],"exampleFix":"# before\nREADME.md -> /home/me/shared/README.md   (symlink escaping the repo)\n\n# after\n$ rm README.md && cp /home/me/shared/README.md ./README.md   (regular file inside the repo)","handlingStrategy":"validation","validationCode":"import { realpathSync } from 'node:fs';\nimport { isAbsolute, join, relative } from 'node:path';\nfunction readmeStaysInRoot(root) {\n  try {\n    const realRoot = realpathSync(root);\n    const real = realpathSync(join(root, 'README.md'));\n    const rel = relative(realRoot, real);\n    return rel === '' || (!rel.startsWith('..') && !isAbsolute(rel));\n  } catch {\n    return false;\n  }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep README.md a regular file committed inside the repository — never a symlink outside it","Audit checkouts for marker symlinks before running trust-gated CLIs: find . -maxdepth 1 -type l","Treat this error as a security stop: fix the filesystem, do not catch and continue"],"tags":["security","filesystem","symlink","cli","homecore"],"backgroundTag":null,"analyzedSha":"4685618388a5e49fad5b3005806f3bdd6a7c25c3","analyzedAt":"2026-08-16T06:09:40.886Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}