Files
Triple-C/app
shadow-testandClaude Opus 5 f2a84c18f9 Judge a host path by where it leads, not by how it is spelled
`validate_host_path` was a string test. Nothing in the module called
`canonicalize`, `read_link` or `O_NOFOLLOW`, so a path whose components are
all visible could still land somewhere hidden: with `~/Downloads/pub` a
symlink to `~/.ssh`, a `host_path` of `~/Downloads/pub/authorized_keys` has no
hidden component, no `..` and no system root — and writes into `~/.ssh`. The
container end is not hypothetical: `/proc/self/mountinfo` inside a Triple-C
container spells the host's project paths out verbatim, so code in there knows
both where to plant the link and what host path to ask for. The same bypass
read host files back the other way.

So the policy now runs twice: once on the string, and once on what the OS says
the string resolves to. A write resolves the parent and keeps the caller's
leaf, because the leaf is never followed — the partial file is created with
`O_EXCL` and the download finishes with a rename, which replaces a link rather
than writing through it. A read resolves the whole path, because the whole
path is opened. On Linux the descriptor is then checked against the path that
was validated (`/proc/self/fd`), which is what closes the window between
resolving and opening; elsewhere that window stays open and the comment says so.

Also here:

  * The upload's overwrite guard is a guard again. `noOverwriteDirNonDir`
    refuses only dir-over-non-dir and the reverse — file-over-file extraction
    proceeds, which is exactly the `.credentials.json` case (verified against a
    live daemon). The probe and the write are now one `set -C` exclusive
    create, with the path travelling as `$0` rather than as script. The
    `FILE_EXISTS: <path> already exists` contract with the frontend is
    unchanged, and now pinned by a test — as is the claim the old comment made.
  * Windows normalisation stopped being a string swap: `\\?\`, `\\?\UNC\` and
    administrative shares all reach the same places and are compared as such,
    and the rules are pure functions over a string, so the Windows entries are
    exercised on any platform. The old test passed on Linux only because
    `Path::is_absolute` was false for a Windows path.
  * Container write roots are resolved inside the container too, and the
    comment no longer claims more than the check does.
  * A failed download can no longer delete a pre-existing file that happened to
    collide with the partial's name.
  * One-shot exec output is buffered as bytes and decoded once, so a filename
    split across two Docker frames survives; stdout and stderr are tellable
    apart, so `find`'s diagnostics stay out of the listing parser; and a
    directory too big to buffer is described as one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GBq2rGum6GX7xXgsas1fDc
2026-08-23 13:13:35 -07:00
..