pub fn evaluate_path_safe(
rules: &RuleSet,
kind: PathKind,
root: &Path,
raw_path: &str,
default: Decision,
) -> DecisionExpand description
SECURITY (CRITICAL fix, guarantor audit, traced to this file’s former
evaluate_path doc comment claiming “no canonicalization … is
involved here … no unparseable case to fail closed on” — the flawed
assumption that let a traversal payload bypass protected_paths): the
safe entry point for evaluating a RAW, model-supplied path tool
argument (relative or absolute, exactly as it arrives in args["path"])
as a "read"/"write" pseudo-tool subject. Resolves raw_path against
root through crate::safe_path::resolve_for_matching — the SAME dual
lexical+symlink-resolved check crate::checkpoint’s P5-9 fix uses — and
folds evaluate_path against the RAW subject, the lexically-normalized
project-relative form, AND the symlink-resolved project-relative form
down to the single strictest Decision (ties broken toward stricter,
via Decision::stricter), so a rule can never be satisfied by
matching only one of these three views.
This is what closes the CRITICAL bug: write_file path="x/../.git/config" with protected_paths=[".git/**"] — the raw
subject "x/../.git/config" does not match, but the resolved subject
".git/config" does, so the fold still lands on Decision::Deny.
FAIL-CLOSED: if root/raw_path cannot be proven safe (a resolution
error, or raw_path lexically looks contained but symlink-resolves
OUTSIDE root — see crate::safe_path::PathForMatching::Unsafe), the
result is escalated to at least Decision::Deny, never silently
falling through to default. A path that legitimately resolves outside
root entirely (e.g. an absolute write elsewhere under
SandboxPolicy::DangerFullAccess) is NOT penalized for that alone — only
the raw-subject match applies to it, exactly as before this fix (no
over-block).