Skip to content

Branch-name validation in Refs (path traversal fix) #105

Closed

Description

@tonibergholm

Question

Fix audit finding #1 (critical): CommitInput.branch is untrusted socket input passed unchanged into Refs::write_branch; branch_path() joins it directly under refs/heads, so ../… or an absolute path escapes the ref directory and write_atomic() creates parent dirs and renames a file there — arbitrary filesystem writes as the daemon user.

Scope:

  • Validate branch names centrally in every Refs read/write/resolve path (evidence: crates/agentic-core/src/refs.rs:59, crates/agentic-core/src/refs.rs:88, crates/agenticd/src/commit.rs:67).
  • Permit only normal relative components; reject .., absolute paths, empty components, and .tmp.
  • Regression tests: traversal, absolute path, empty component, .tmp collision.

Resolution records: PR link, the validation rule as implemented, test names.

Activity

  1. added
    ready-for-agentScoped and unambiguous enough for an agent to pick up
    wayfinder:taskWayfinder ticket: work that unblocks a decision or lands a fix
    on Jul 9, 2026
  2. self-assigned this
    on Jul 9, 2026
  3. tonibergholm commented on Jul 9, 2026

    @tonibergholm
    MemberAuthor

    Resolution

    Shipped as PR #115 (pending Toni's review/merge per the map's PR gate).

    What landed: central validation at the branch_path() chokepoint in agentic-core::refs — every Refs read/write/resolve path is covered by one rule, plus explicit validation in write_head_symbolic. Permitted: /-separated normal relative components (nested names like feature/foo stay supported). Rejected with typed Error::InvalidBranchName: empty names/components, . / .., absolute paths, backslashes, control characters, .tmp-suffixed components, and the reserved name HEAD. Wire classification: non-retryable Validation / invalid_branch_name (typed downcast arm, forced by the exhaustive match). Reads reject loudly rather than returning None, so traversal probes are distinguishable from missing branches.

    Tests: hostile-name matrix at the Refs layer including no-file-at-escape-target assertions for relative and absolute escapes; read/resolve rejection with a planted outside file; daemon-level commit::execute regression with a traversal branch name (typed rejection before staging, nothing written, no HEAD published); wire-classification test. Workspace tests, clippy -D warnings, fmt all green.

    Facts later tickets depend on: validate_branch_name is public in agentic_core::refs — the rollback target parameter flows through resolve → read_branch, so rollback targets are covered by the same gate with no daemon-side changes.

  4. added a commit that references this issue on Jul 9, 2026
    d81e15d
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

ready-for-agentScoped and unambiguous enough for an agent to pick upwayfinder:taskWayfinder ticket: work that unblocks a decision or lands a fix

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions