# Unvalidated package specifier injection

User or repo strings concatenated into package specifiers, then resolved, installed or imported

_Area: Software supply chain and CI/CD. Seen in 3 disclosed reports; one confirmed bounty: $2,500; latest 2026-09-09. Updated 2026-10-30._

## How it works
A version or name string is treated as a plain value but is actually a package specifier grammar. When code builds `pkg@<input>` or reads a repo-declared dependency name and hands it to a resolver, installer or `import()`, the input can carry alias syntax (`file:`, `npm:`, git URLs, paths) or name a different artifact than intended. The broken assumption: that a version/name is inert data, when the package manager interprets it as a source selector. Whoever controls the string controls what code gets fetched and run.

## Where it shows up
- Version params validated only as `z.string()` or a loose regex, then interpolated into `name@version` for `npm`/`pnpm`/`yarn` add [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-wcjj-9m6g-2fr2.md)
- Post-install dynamic `import()` of the just-installed package, turning install into execution [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-wcjj-9m6g-2fr2.md)
- Repo-writable config (e.g. `configDependencies`, workspace manifests) that selects an install engine, plugin or binary [E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-gj8w-mvpf-x27x.md)
- Tooling that converts a private Git dependency into a public registry package and validates against metadata inside that public package [E1](https://bugbountylibrary.com/r/blog%3Agiraffesecurity-dev-posts-dependabot-confusion.md)
- Any place a dependency's own `package.json`/repository field is used to decide trust [E1](https://bugbountylibrary.com/r/blog%3Agiraffesecurity-dev-posts-dependabot-confusion.md)

## How to review
Find every sink that builds a specifier from a variable and trace the variable to its source. The invariant: a version must be constrained to a strict semver (or exact allowlisted name) before concatenation, and alias prefixes (`file:`, `npm:`, `git+`, paths, URLs) must be rejected, not merely discouraged. For repo-config-driven behavior, the property is that repo-writable settings cannot name an executable engine or binary; only a fixed, integrity-checked set is permitted [E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-gj8w-mvpf-x27x.md). For source conversion, trust decisions must be validated against data the attacker does not control, not the artifact's own metadata [E1](https://bugbountylibrary.com/r/blog%3Agiraffesecurity-dev-posts-dependabot-confusion.md).

## Variants seen
- Alias injection via `file:` in an MCP tool's version param, then dynamic import [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-wcjj-9m6g-2fr2.md)
- Repo config opting into a registry-supplied native install engine [E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-gj8w-mvpf-x27x.md)
- Git-to-registry conversion trusting the public package's repository field [E1](https://bugbountylibrary.com/r/blog%3Agiraffesecurity-dev-posts-dependabot-confusion.md)

## Why fixes fail
A semver-only regex is insufficient if alias syntaxes still parse; validation must happen before interpolation and reject non-semver outright [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-wcjj-9m6g-2fr2.md).

## Impact and severity
Arbitrary code execution as the developer or CI user, and access to private repositories [E1](https://bugbountylibrary.com/r/blog%3Agiraffesecurity-dev-posts-dependabot-confusion.md)[E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-wcjj-9m6g-2fr2.md)[E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-gj8w-mvpf-x27x.md). Severity rises when the sink runs in CI or on install, where credentials and tokens are present.

## Evidence
- E1: https://bugbountylibrary.com/r/blog%3Agiraffesecurity-dev-posts-dependabot-confusion.md
- E2: https://bugbountylibrary.com/r/ghsa%3AGHSA-wcjj-9m6g-2fr2.md
- E3: https://bugbountylibrary.com/r/ghsa%3AGHSA-gj8w-mvpf-x27x.md
