Skip to content

Expand ~ in script paths before the existence check - #148

Merged
ldayton merged 3 commits into
mainfrom
lily/issue-144-tilde-expansion
Jun 8, 2026
Merged

Expand ~ in script paths before the existence check#148
ldayton merged 3 commits into
mainfrom
lily/issue-144-tilde-expansion

Conversation

@ldayton

@ldayton ldayton commented Jun 8, 2026

Copy link
Copy Markdown
Owner

Fixes #144.

Problem

The Python handler resolved a script path by joining the raw token onto the working directory. A ~-prefixed path like python3 ~/scripts/probe.py became <CWD>/~/scripts/probe.py with a literal tilde — pathlib doesn't treat ~ as absolute, so it got anchored to cwd. The file was never found, and a safe, statically-analyzable script fell back to ask. This defeats Dippy's own preference for python3 file.py over python3 -c "...": the file form is the one meant to get the fast path.

It also contradicted the documented security model, which already states path normalization expands ~/bar/home/user/bar before matching — the Python handler just wasn't doing it.

Fix

New core.paths.resolve_arg_path(token, cwd) — a small, no-raise resolver that expands ~/~user, then anchors relative paths to cwd. The Python handler routes through it instead of open-coding cwd / Path(token).

Two deliberate design choices, both matching the security model:

  • os.path.expanduser, not Path.expanduser(). Path.expanduser() raises RuntimeError on an unknown ~user (verified on 3.7–3.14), which would turn a graceful ask into a crashed hook. The os.path function leaves unresolvable tokens literal, so they degrade to ask.
  • Variables ($HOME) are left literal. The model keeps $HOME as $HOME and lets the shell expand it after approval, so the hook never guesses a variable's value from its own environment. The resolver only normalizes ~ and relative paths.

Placing this in core/ makes it reusable for future handlers that grow script analysis (script.py, uv.py, ...) rather than re-introducing the same gap.

Scope

Only the Python handler does script-file existence checks today; shell.py delegates to inner-command checking and never stats a path, so bash ~/x.sh doesn't hit this bug currently.

Tests

  • Unit (tests/core/test_paths.py): the resolver contract — ~ expands to home, relative anchors to cwd, absolute is preserved, $VAR is left literal (not read from the hook env), unknown ~user stays literal without raising.
  • Integration (tests/cli/test_python.py): tilde and relative script paths resolve and approve; unknown ~user degrades to ask without crashing.

Full suite: 10,906 passing.

ldayton added 3 commits June 8, 2026 11:36
Fixes #144. The Python handler resolved a script path by joining the raw
token onto cwd, so a ~-prefixed path became <CWD>/~/... with a literal
tilde. The file was never found and a safe script fell back to ask.

Add core.paths.resolve_arg_path, a no-raise resolver that expands ~,
~user, and $VAR/${VAR} before anchoring relatives to cwd, and route the
Python handler through it. Unresolvable tokens (unknown user, undefined
var) are left literal and degrade to ask rather than raising.
The security model (Reference/Security-Model.md) expands ~ during path
normalization but keeps variables literal: $HOME stays $HOME and is
expanded by the shell after approval, so the hook never guesses a
variable's value from its own environment.

Remove os.path.expandvars from resolve_arg_path so it only expands ~ and
~user, matching that invariant and still fully fixing #144 (which only
concerns ~). Pin the behavior with a test asserting $HOME is not
expanded.
The previous $HOME integration test asserted that a safe script via
$HOME degrades to ask. That pinned a UX wart (an unfortunate prompt) as
if it were the desired outcome.

Replace it with unit tests on resolve_arg_path that assert the actual
contract the security model requires: ~ expands to home, relative paths
anchor to cwd, $VAR is left literal (not read from the hook env), and an
unknown ~user stays literal without raising. The Python integration tests
now assert only desirable outcomes (tilde and relative resolve+approve).
@ldayton ldayton changed the title Expand ~ and $VAR in script paths before existence check Expand ~ in script paths before the existence check Jun 8, 2026
@ldayton
ldayton merged commit 3297e0e into main Jun 8, 2026
1 check passed
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.

File-existence check fails to expand ~ in script paths, causing false-positive ask on python3 path/to/script.py

1 participant