{"record":{"id":"8d1c726438aaa2ac","repo":"ruvnet/RuView","slug":"refusing-cli-access-repository-does-not-match-the-8d1c72","errorCode":null,"errorMessage":"Refusing CLI access: repository does not match the configured trusted root","messagePattern":"Refusing CLI access: repository does not match the configured trusted root","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"harness/ruview/src/repo-trust.js","lineNumber":14,"sourceCode":"// SPDX-License-Identifier: MIT\nimport { existsSync, realpathSync, readFileSync, statSync } from 'node:fs';\nimport { isAbsolute, join, relative } from 'node:path';\nconst REQUIRED_MARKERS = ['.git', 'README.md', 'v2'];\nconst RUVIEW_MARKERS = ['firmware', 'wifi_densepose'];\nfunction isWithin(parent, child) {\n  const rel = relative(parent, child);\n  return rel === '' || (!rel.startsWith('..') && !isAbsolute(rel));\n}\nexport function assertTrustedRuViewRepo(repoRoot, { trustedRoot = repoRoot } = {}) {\n  if (!repoRoot || !trustedRoot) throw new TypeError('repoRoot and trustedRoot are required');\n  const root = realpathSync(repoRoot);\n  const trustAnchor = realpathSync(trustedRoot);\n  if (!isWithin(trustAnchor, root) || root !== trustAnchor) throw new Error('Refusing CLI access: repository does not match the configured trusted root');\n  if (!statSync(root).isDirectory()) throw new Error('Refusing CLI access: trusted root is not a directory');\n  const missing = REQUIRED_MARKERS.filter((marker) => !existsSync(join(root, marker)));\n  if (missing.length || !RUVIEW_MARKERS.some((marker) => existsSync(join(root, marker)))) {\n    throw new Error(`Refusing CLI access: RuView repository markers are missing${missing.length ? ` (${missing.join(', ')})` : ''}`);\n  }\n  const readme = readFileSync(join(root, 'README.md'), 'utf8').slice(0, 131_072);\n  if (!/\\b(?:RuView|wifi[- ]densepose)\\b/i.test(readme)) throw new Error('Refusing CLI access: README does not identify a RuView checkout');\n  return root;\n}\n","sourceCodeStart":1,"sourceCodeEnd":24,"githubUrl":"https://github.com/ruvnet/RuView/blob/4685618388a5e49fad5b3005806f3bdd6a7c25c3/harness/ruview/src/repo-trust.js#L1-L24","documentation":"assertTrustedRuViewRepo (harness/ruview/src/repo-trust.js) is the harness's fail-closed trust gate. It realpath()s both repoRoot and trustedRoot (trustedRoot defaults to repoRoot) and requires the resolved repoRoot to be inside the trust anchor AND identical to it. Any subdirectory, parent directory, or symlink-resolution mismatch is refused before any host subprocess is spawned.","triggerScenarios":"runCodex({ repoRoot: '/repos/RuView/harness/ruview', trustedRoot: '/repos/RuView' }); a checkout reached through a symlink whose realpath differs from the configured trusted root; trustedRoot set to a home/workspace directory while repoRoot is the repo inside it.","commonSituations":"CI checkouts under symlinked workspace paths (/workspace -> /mnt/volumeN); monorepo tooling passing a subpackage as repoRoot; separate --repo and --trusted-root CLI flags drifting apart.","solutions":["Pass the same absolute repository root for repoRoot and trustedRoot, or omit trustedRoot so it defaults to repoRoot.","Resolve both through realpath before calling: const root = realpathSync(repoRoot); ...({ repoRoot: root, trustedRoot: root }).","In the shell, confirm `realpath <repoRoot>` and `realpath <trustedRoot>` print identical paths, then use those literal paths."],"exampleFix":"// before\nrunCodex({ prompt, repoRoot: '/link/ruview', trustedRoot: '/repos/RuView' }) // '/link/ruview' is a symlink\n// after\nimport { realpathSync } from 'node:fs';\nconst root = realpathSync('/link/ruview');\nrunCodex({ prompt, repoRoot: root, trustedRoot: root });","handlingStrategy":"validation","validationCode":"import { realpathSync } from 'node:fs';\nconst root = realpathSync(repoRoot);\nconst anchor = realpathSync(trustedRoot ?? repoRoot);\nif (root !== anchor) {\n  throw new Error(`repoRoot ${root} does not equal trusted root ${anchor}`);\n}\nrunCodex({ prompt, repoRoot: root, trustedRoot: anchor });","typeGuard":"import { realpathSync } from 'node:fs';\nfunction isSameResolvedPath(a, b) {\n  return realpathSync(a) === realpathSync(b);\n}","tryCatchPattern":"try {\n  await runCodex({ prompt, repoRoot, trustedRoot });\n} catch (e) {\n  if (e instanceof Error && e.message.includes('does not match the configured trusted root')) {\n    // fail closed: never widen the trust anchor automatically; surface to the operator\n    throw new Error(`trust gate refused ${repoRoot}; pass repoRoot === trustedRoot after realpath`);\n  }\n  throw e;\n}","preventionTips":["Default trustedRoot to repoRoot (omit it) unless you have a reason to pin it.","Resolve symlinks with realpathSync on both before comparing.","Never auto-widen the trust anchor on failure — it is a security control; fix the paths instead."],"tags":["security","fail-closed","paths","symlinks","trust-gate","ruview-harness"],"backgroundTag":null,"analyzedSha":"4685618388a5e49fad5b3005806f3bdd6a7c25c3","analyzedAt":"2026-08-16T06:09:40.886Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}