7fa5b07718
Routes the MCP index_repository handler and the graph UI's POST /api/index through a single decision function. The UI route previously checked only that root_path was a directory, so an operator's configured boundary held on one entry point and not the other; it now canonicalizes first and applies the same policy, answering 403 with the reason. The decision is two-tier, because a bare default-deny would refuse every first run and a bare opt-in leaves the default open: - Breadth is always enforced, with nothing configured. Filesystem, drive and share roots, top-level system trees, the home directory itself and credential directories are refused as indexing roots out of the box. - Containment in a declared root applies once CBM_ALLOWED_ROOT is set or a grant exists, and is evaluated first so a path outside a configured root is reported as exactly that. Three things the tests caught, each a real defect rather than a test fix: - On macOS /etc, /tmp and /var are firmlinked under /private, so canonicalizing "/etc" yields "/private/etc" and counted two deep — sailing past a minimum of two, missing the very path being guarded. Depth now discounts a leading "private" component. - An earlier draft refused any root containing the cache directory. That was over-claimed: the indexer only parses recognised source files and a graph database is binary SQLite it would never extract. Refusing a whole root is also the wrong remedy where the concern does hold — not walking the cache is. Removed, with the reasoning recorded at the site. - Rewording the refusal to "outside every allowed root" broke an assertion matching "outside the allowed root", and that test's early return skipped its CBM_ALLOWED_ROOT cleanup, leaking the variable into every later test in the suite. The original wording is kept and guidance appended instead. Worth remembering: these contracts match strings, not properties. Docs updated in the same change, since both env-var tables said "unset imposes no restriction" and that is no longer true: CONFIGURATION.md and README.md describe the two tiers, and CONFIGURATION.md lists the always- refused roots along with the two limits that matter — this constrains scope rather than sensitivity, and the credential list is a denylist that raises the cost of a mistake rather than closing the class. SECURITY.md's supported- versions table was still on 0.8.x and now reads 0.9.x. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>