A single active relaxation of the path jail. Each one widens or disables what
tools can reach beyond the project root, so it is surfaced loudly (GH security
audit, finding 3): the MCP/HTTP server inherits its process env from the
IDE/launchd, so a globally-set LEAN_CTX_ALLOW_PATH / LEAN_CTX_EXTRA_ROOTS
/ LEAN_CTX_ALLOW_IDE_DIRS (or path_jail = false) silently loosens the
boundary with no in-band signal otherwise.
Collect every currently-active path-jail relaxation. An empty result means
the jail is fully in force. This is the single source of truth shared by the
startup warning (warn_if_relaxed) and lean-ctx doctor.
Heuristic canonicalize — honours the #356 TCC guard. Used by the
jail-disabled bypass and by external callers (session/startup/server roots)
that must not pop a privacy prompt on their own initiative.
If candidate sits inside a known language cache, return (label, root)
where root is the path truncated at the end of the marker directory (no
trailing slash). resolve_path uses this to auto-register a session read-only
root so the retry resolves without a config edit or a subprocess (#899).
allow_paths / extra_roots come from config.toml, where no shell ever
runs — users writing "$HOME/code" or "~/code" got a literal,
never-matching prefix and concluded the whole option was broken (GH #392).
Unset variables are left verbatim (and warned about) so the entry fails
loudly in lean-ctx doctor instead of silently matching something else.
HOME falls back to the platform home directory when the environment
variable is absent, as is common in native Windows shells.
True when candidate resolves to a location inside a configured read-only
root. The candidate’s nearest existing ancestor is canonicalized (so a
not-yet-existing file inherits the read-only status of the directory it
would be created in — closing the “create a new file in a read-only repo”
hole) and matched against the (symlink-resolved) read-only roots.
jail_path_with_roots plus the read-only auto-admission of #899: a path
the jail refuses inside a language cache or an agent instruction folder
registers that root read-only for the session and resolves again, so the
call is served instead of failing with “retry”. A symlinked skill whose
target is still refused gets its target opened read-only
(register_symlink_target_read_only). Writes stay denied either way.
One implementation for the MCP server and the one-shot CLI.
A read-only root is a sibling subtree the agent may read but never
write — e.g. a reference repo mounted next to the project. Empty by
default, so is_read_only_path/enforce_writable are zero-cost no-ops
for everyone who hasn’t opted in (#475).
When candidate reaches its target through a symlink inside an agent
instruction root (a skill directory linked in from a checkout or from
~/.agents/skills), register the target’s directory read-only too.
Emit a loud tracing::warn! for every active path-jail relaxation. Called
once at MCP/HTTP server startup so a trusted-but-loosening env/config leaves
an in-band audit signal instead of silently defeating the jail (finding 3).