Skip to content

fix(F18): normalize relative paths in is_external_path - #90

Merged
yogthos merged 1 commit into
mainfrom
fix/f18-relative-path-external
May 21, 2026
Merged

fix(F18): normalize relative paths in is_external_path#90
yogthos merged 1 commit into
mainfrom
fix/f18-relative-path-external

Conversation

@yogthos

@yogthos yogthos commented May 21, 2026

Copy link
Copy Markdown
Collaborator

Track F-MEDIUM #18. Previously is_external_path returned false on any non-absolute path, letting ../../escape bypass Accept-mode external-directory rules. Now resolves via the same canonicalize logic used by check_path, comparing against both literal and canonical working_dir. 1 new test, 679 pass.

Track F-MEDIUM #18 from ROADMAP.md.

## Problem

`is_external_path` (`checker.rs:334-341`) returned `false` for any
non-absolute path. In `Accept` mode that's a hole: a relative
path with `..` traversal could escape the working directory
without triggering the external-path branch:

  cwd = /home/user/project
  agent calls read "../../etc/passwd"
  → is_external_path("../../etc/passwd") = false (not absolute)
  → Accept mode auto-allows
  → file leaked to LLM

## Fix

`is_external_path` now resolves relative paths via the same
`resolve_absolute` used in `check_path`, which canonicalizes
when possible (F7) and falls back to lexical parent-canonicalize
otherwise. After resolution, the starts_with comparison runs
against BOTH the literal `working_dir` and its canonical form
— covers macOS's `/tmp → /private/tmp` and similar symlinked
roots that would otherwise cause a canonical resolved path to
not share a prefix with the literal cwd.

If resolve_absolute returns a still-relative path (working_dir
is bogus or both canonicalize attempts failed), treat as not-
external — rules fall through to the default action, same as
the pre-F18 lenient behavior.

## Tests

Existing test `relative_path_is_not_external` still passes —
in-tree relative paths are unaffected.

One new test in `tests/checker_tests.rs`:

- `relative_path_escaping_cwd_is_external`: builds a deep tempdir
  layout `base/a/b/c` (cwd) and `base/escaped/file.rs`. Asserts:
  - In-tree `local.rs` auto-allows in Accept (regression guard).
  - `../../../escaped/file.rs` (which canonicalizes to
    `base/escaped/file.rs`) does NOT auto-allow; surfaces as
    Ask because it's now correctly classified external.

679 pass (was 678). All build profiles clean.
@yogthos
yogthos merged commit 4edacb2 into main May 21, 2026
1 check passed
@yogthos
yogthos deleted the fix/f18-relative-path-external branch May 21, 2026 04:59
yogthos added a commit that referenced this pull request May 21, 2026
All actionable Track F items now landed:

- F-CRITICAL (PRs #76, #77): ACP perm asks routed to Deny, find/glob/list_dir hide dotfiles by default
- F-HIGH (PRs #78#84): compress aligns cut-point, read streams large files, ACP parallel tool ids, bash pgid cleanup, symlink canonicalize, session schema version, quote-aware bash splitter
- F-MEDIUM (PRs #85, #86, #87, #89, #90, #91): interleaved bash output, compress net-savings, Retry-After parsing, plugin docs, relative-path normalize, BOM strip, bounded interject channel
- F-SKIP (F9, F11, F15): verified false positives or N/A; rationale documented inline

Status legend updated; ordering recommendation replaced with the
shipped-status section.

Co-authored-by: Yogthos <yogthos@gmail.com>
allen-munsch pushed a commit to allen-munsch/dirge that referenced this pull request Jun 3, 2026
Track F-MEDIUM dirge-code#18 from ROADMAP.md.

## Problem

`is_external_path` (`checker.rs:334-341`) returned `false` for any
non-absolute path. In `Accept` mode that's a hole: a relative
path with `..` traversal could escape the working directory
without triggering the external-path branch:

  cwd = /home/user/project
  agent calls read "../../etc/passwd"
  → is_external_path("../../etc/passwd") = false (not absolute)
  → Accept mode auto-allows
  → file leaked to LLM

## Fix

`is_external_path` now resolves relative paths via the same
`resolve_absolute` used in `check_path`, which canonicalizes
when possible (F7) and falls back to lexical parent-canonicalize
otherwise. After resolution, the starts_with comparison runs
against BOTH the literal `working_dir` and its canonical form
— covers macOS's `/tmp → /private/tmp` and similar symlinked
roots that would otherwise cause a canonical resolved path to
not share a prefix with the literal cwd.

If resolve_absolute returns a still-relative path (working_dir
is bogus or both canonicalize attempts failed), treat as not-
external — rules fall through to the default action, same as
the pre-F18 lenient behavior.

## Tests

Existing test `relative_path_is_not_external` still passes —
in-tree relative paths are unaffected.

One new test in `tests/checker_tests.rs`:

- `relative_path_escaping_cwd_is_external`: builds a deep tempdir
  layout `base/a/b/c` (cwd) and `base/escaped/file.rs`. Asserts:
  - In-tree `local.rs` auto-allows in Accept (regression guard).
  - `../../../escaped/file.rs` (which canonicalizes to
    `base/escaped/file.rs`) does NOT auto-allow; surfaces as
    Ask because it's now correctly classified external.

679 pass (was 678). All build profiles clean.

Co-authored-by: Yogthos <yogthos@gmail.com>
allen-munsch pushed a commit to allen-munsch/dirge that referenced this pull request Jun 3, 2026
…irge-code#92)

All actionable Track F items now landed:

- F-CRITICAL (PRs dirge-code#76, dirge-code#77): ACP perm asks routed to Deny, find/glob/list_dir hide dotfiles by default
- F-HIGH (PRs dirge-code#78dirge-code#84): compress aligns cut-point, read streams large files, ACP parallel tool ids, bash pgid cleanup, symlink canonicalize, session schema version, quote-aware bash splitter
- F-MEDIUM (PRs dirge-code#85, dirge-code#86, dirge-code#87, dirge-code#89, dirge-code#90, dirge-code#91): interleaved bash output, compress net-savings, Retry-After parsing, plugin docs, relative-path normalize, BOM strip, bounded interject channel
- F-SKIP (F9, F11, F15): verified false positives or N/A; rationale documented inline

Status legend updated; ordering recommendation replaced with the
shipped-status section.

Co-authored-by: Yogthos <yogthos@gmail.com>
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