Skip to content

fix(harness): strip file:// scheme before sensitive-path probing - #292

Open
raymondginger2018-sudo wants to merge 1 commit into
HKUDS:mainfrom
raymondginger2018-sudo:pr/file-url-sensitive-path
Open

raymondginger2018-sudo wants to merge 1 commit into
HKUDS:mainfrom
raymondginger2018-sudo:pr/file-url-sensitive-path

Conversation

@raymondginger2018-sudo

Copy link
Copy Markdown
Contributor

Problem

is_sensitive_path() probes candidate paths by suffix matching after _normalize_path(). A file:// URL — e.g. file:///home/u/.ssh/id_rsa — never suffix-matches the denylist entry /home/u/.ssh/id_rsa, so is_sensitive_path() returns False and a protected-path denylist entry can be bypassed under permissive permission modes (FULL_AUTO/AUTOPILOT) by simply wrapping the path in a file: scheme. The same bypass works for percent-encoded (file:///home/u/.ssh/%69d_rsa), UNC (file://host/share/...), localhost, and Windows drive (file:///C:/Users/...) forms, and for nested tool-call arguments under non-path keys.

Fix

  • _normalize_path() now strips a leading file: scheme (case-insensitive, including //host/share UNC and //localhost forms) and percent-decodes the result before the denylist suffix probe (new helper _strip_file_scheme, urllib.parse.urlparse + unquote).
  • _path_values() additionally yields string values that start with file: even when the key does not match _PATH_KEY, closing nested-argument bypasses.

No allowlist semantics, no other scheme handling, and no probe ordering is changed.

Tests

7 new module-level tests in tests/test_harness_permissions.py:

  • file:// URL under a non-path key is denied
  • percent-encoding is decoded before the probe
  • localhost and UNC forms are denied
  • Windows drive form is denied
  • file:// in nested arguments is denied
  • remote http(s):// URLs are not treated as paths (no false positives)
  • plain text mentioning a file:// URL is not treated as a path

RED/GREEN paired verification on this branch (Python 3.12–3.14): pre-fix 5 failed / 2 passed (RED) → post-fix 7/7 passed (GREEN). The two false-positive guards were already passing and stay passing.

Security Considerations

  • Threat model: untrusted tool-call arguments processed under permissive permission modes; the sensitive-path denylist is the last line of defense before file reads/writes. A denylist bypass means e.g. ~/.ssh/id_rsa can be read/written by a crafted file:// argument.
  • Scope: this tightens the denylist probe only. Normalization is conservative — only a leading file: scheme is stripped; http(s):// URLs and prose mentions are regression-tested to remain non-paths.
  • Residual risk (unchanged, out of scope here): .. traversal, symlink escapes (covered by separate tests, incl. test_denylist_follows_symlinks), and Windows 8.3 short-name / NTFS stream aliasing.

Local CI preflight on this revision: all legs green except pre-existing Windows-environmental reds (xdist worker collapse on py3.12; pre-existing platform red family on py3.13/3.14 — proven identical on pristine base 4ff321ec via failed-set differential, net new reds attributable to this diff = 0).

@Zongwei9888 requesting a security review — this touches the sensitive-path denylist boundary.

file: URLs passed as tool path/argument values missed the denylist
suffix probe: the raw string "file:///home/u/.ssh/id_rsa" never
suffix-matches "/home/u/.ssh/id_rsa", so is_sensitive_path returned
False and _extract_file_targets still extracted the value as a
filesystem path -> denylist bypass under FULL_AUTO.

Normalize the boundary value once (_strip_file_scheme): case-insensitive
file: scheme strip (plain, //host/share UNC and //localhost forms) plus
percent-decoding, before the existing suffix probe. Scope stays minimal:
only the file: form is rewritten; http(s)/ws(s) and other URL schemes,
plain filesystem paths and relative names keep byte-identical behavior.

Tests: 7 new cases (non-path keys, percent-encoding, localhost/UNC,
Windows drive form, nested arguments) plus two negative controls
(remote URLs and prose mentioning file: are not paths).
@raymondginger2018-sudo

Copy link
Copy Markdown
Contributor Author

@Zongwei9888 Hi! This PR fixes a denylist bypass in the sensitive-path probe (file:// scheme + percent-encoding forms never suffix-match the denylist, so protected paths like ~/.ssh/id_rsa were reachable under FULL_AUTO). RED/GREEN paired verification included (7 new tests, 3 Python versions); details and Security Considerations are in the PR body. Would appreciate a security review when convenient — happy to adjust scope if you would rather have the _path_values widening split into a separate PR.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant