# Uncontained package path join

Package or ref names from lockfiles, submodules, or schemas joined into filesystem paths without containment

_Area: Software supply chain and CI/CD. Seen in 5 disclosed reports; latest 2026-10-22. Updated 2026-10-24._

## How it works
A name taken from an untrusted source (a lockfile key, a submodule path, a schema `$ref`) is joined into a filesystem location — install dir, clone target, base path — without checking the result stays inside that location. The code assumes the name is a plain identifier. Traversal sequences or `.git` aliases break that assumption, and a write, clone, or read lands outside the intended directory. The recurring failure is ordering and coverage: a containment or validation check exists, but a mutating operation runs before it, or a sibling code path never got the check.

## Where it shows up
- `path.join(base, name)` where `name` comes from a lockfile, manifest, or ref string [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-c59q-g84q-2gj5.md)
- A permissive path helper used before a strict validator runs later [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-83vg-56qc-22m7.md)
- Scheme allowlists that exempt `file://` or local paths from a remote-ref gate [E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-8359-h9fx-j6v9.md)
- Clone/write/mkdir calls that precede the validation that would reject the path [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-83vg-56qc-22m7.md)
- A containment helper applied to one linker or branch but not its siblings [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-c59q-g84q-2gj5.md)[E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-8359-h9fx-j6v9.md)

## How to review
Find every place an untrusted name reaches a filesystem operation. The invariant: after joining, the resolved path must be verified to be inside the intended base (`is_relative_to(base)` or equivalent) *before* any write, clone, or read. Check ordering — no mutation may precede validation [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-83vg-56qc-22m7.md). Check coverage — grep every call site of the same primitive, not just the one that was patched [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-c59q-g84q-2gj5.md). For scheme gates, enumerate every branch handling an exempted scheme and confirm each has containment [E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-8359-h9fx-j6v9.md).

## Variants seen
- Late validation: strict `.git`-alias check at index-add time, clone already wrote a hook [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-83vg-56qc-22m7.md)
- Lockfile key traversal: `dp.parse().name` joined into virtual store, escaping it [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-c59q-g84q-2gj5.md)
- Ref resolution: `file://` and `../` `$ref` read arbitrary local files, bypassing the remote-ref gate [E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-8359-h9fx-j6v9.md)

## Why fixes fail
Containment helpers were added to one path (hoisted linker, HTTP-local-ref branch) but not sibling paths using the identical pattern, leaving them exposed [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-c59q-g84q-2gj5.md)[E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-8359-h9fx-j6v9.md). A strict validator existed but ran after the mutating operation [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-83vg-56qc-22m7.md).

## Impact and severity
Arbitrary file write leading to code execution via planted hooks [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-83vg-56qc-22m7.md); package files written outside the store during install [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-c59q-g84q-2gj5.md); arbitrary local file read leaking secrets into generated code [E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-8359-h9fx-j6v9.md). Severity is high where the write path enables execution; worse when the check is present but misordered or partial, since reviewers assume coverage.

## Evidence
- E1: https://bugbountylibrary.com/r/ghsa%3AGHSA-83vg-56qc-22m7.md
- E2: https://bugbountylibrary.com/r/ghsa%3AGHSA-c59q-g84q-2gj5.md
- E3: https://bugbountylibrary.com/r/ghsa%3AGHSA-8359-h9fx-j6v9.md
