## Ticket: CVE-2026-6321 — fast-uri path traversal via pre-decoded percent-encoded segments

**Advisory**: GHSA-q3j6-qgpj-74h6 — https://github.com/fastify/fast-uri/security/advisories/GHSA-q3j6-qgpj-74h6
**CVE**: CVE-2026-6321 | **CWE-22** (Path Traversal)
**Severity**: High — CVSS 7.5
**Package**: `fast-uri` (npm) | **Repository**: https://github.com/fastify/fast-uri

### Impact

`fast-uri`'s `normalize()` and `equal()` decode percent-encoded path
separators and dot segments **before** applying RFC 3986 dot-segment removal.
Because of this ordering, an encoded `%2e%2e%2f` sequence is first decoded into
a literal `../` and is then processed as a real dot segment during
normalization.

The consequence is that two distinct input URIs collapse onto the same
normalized path. Any path-prefix allowlist check built on top of
`normalize()` / `equal()` can therefore be bypassed: an attacker supplies an
encoded traversal sequence, the normalized result escapes the intended path
prefix, and the allowlist comparison no longer reflects the real target.

Any code that forwards an attacker-influenced URI string into `normalize()` or
`equal()` and then makes a path-prefix authorization decision on the result is
exploitable.

### Affected / fixed versions

Affected: `fast-uri` `<= 3.1.0`.
Fixed: **`3.1.1`**.

Reproduce on a vulnerable build (**`3.1.0`**) and verify the fix on
**`3.1.1`**.

### Where to look

The fix ships in the `3.1.1` release. Inspect the patch to confirm the root
cause and the guard:

```bash
git clone https://github.com/fastify/fast-uri.git
cd fast-uri && git diff v3.1.0 v3.1.1
```

The change corrects the ordering so that RFC 3986 dot-segment removal is no
longer fed pre-decoded percent-encoded separators / dot segments.

### Reproduction approach

No service, database, or browser is needed — the bug is observable purely
in-process with Node.js by comparing the string returned by `normalize()`.

1. In a scratch directory, install the **vulnerable** build:
   `npm install fast-uri@3.1.0`
2. Run a small Node script that calls `normalize()` on a URL whose path
   contains a percent-encoded `%2e%2e%2f` traversal sequence, and records the
   returned normalized path string.
3. Repeat with the **fixed** build: `npm install fast-uri@3.1.1`.

Use `normalize()` as the primary vector; `equal()` exhibits the same flaw and
can be shown as a secondary check.

### Expected result

| Build | `normalize()` of an encoded-traversal URL | Observable |
|-------|-------------------------------------------|-----------|
| `fast-uri@3.1.0` (vulnerable) | encoded `%2e%2e%2f` decoded first, then treated as `../` | normalized path **escapes the intended prefix** |
| `fast-uri@3.1.1` (fixed) | encoded segments no longer pre-decoded into dot segments | normalized path **stays confined**; returned string differs |

- **Vulnerable indicator**: the normalized path string escapes the intended
  path prefix.
- **Fixed indicator**: the normalized path remains confined to the prefix; the
  returned string differs from the vulnerable build's output.

### Expected artifacts

- `reproduction_steps.sh` — installs both versions and runs the PoC,
  printing the normalized output for each.
- `validation_verdict.json` — `verdict: confirmed`, with the vulnerable and
  fixed indicators captured.
- Logs capturing the script output for `3.1.0` and `3.1.1`.
