# Redirect rule forms that fail without warning

Measured observations from one experiment, with the scope limit stated first because it
changes how you may use these numbers.

Version 1.0 · 2026-09-09

---

## Scope limit — read this before the table

All observations below come from a **local Cloudflare Pages simulator (wrangler 4.65.0)**,
not from a production edge. We make **no claim** that a production edge behaves the same
way, and no claim of the form "platform X does not support Y."

One experiment. One tool version. No independent replication.

**Why the findings are still worth acting on:** the argument for avoiding these forms does
not depend on the measurement. Staking a migration on behaviour that the vendor's
documentation does not specify is a bad trade whichever way the measurement lands.

## What was run

- **48 rule forms** = 4 source shapes × 3 destination shapes × 4 status values.
- Each rule given an independent source prefix, so rules cannot shadow one another.
- **3 requests per rule = 144 requests.** All three agreed for every rule.
- **4 controls**, all of which had to pass or the run was void: environment serves normally;
  a pre-existing rule still works; a nonexistent path returns a real 404; the baseline is
  unchanged once the test rules are removed. All four passed.

## Results by source shape

| Source shape | Example | Worked? | Warned? |
|---|---|---|---|
| Relative path | `/a/` | Yes | — |
| Relative splat | `/a/*` | Yes | — |
| **Absolute URL** | `https://host/a/` | **No — 12/12 rules, 36/36 requests 404** | **Yes**, named at parse time with a line number |
| **Protocol-relative** | `//host/a/` | **No — 12/12 rules, 36/36 requests 404** | **No — 11 of 12 produced no signal at any layer** |

## Results by destination shape

| Destination + status | Observed |
|---|---|
| Site-relative, any status | Works |
| Cross-origin absolute + `301` / `302` | **Works** — a real cross-origin redirect |
| Cross-origin absolute + `200` | Fails (404), and is **named at parse time** |
| Protocol-relative cross-origin + `200` | Returns **200 with local content** — not a proxy, and not an error either |

## The two forms worth memorising

**Protocol-relative source (`//host/path`) is the dangerous one.** It begins with `/`, so it
survives a "must start with a slash" relative-path check, is counted among the valid rules,
never warns at any layer, and never matches a request. Written, accepted, silent, dead. Of
the 48 forms tested this was the only shape with no diagnostic anywhere. (One of its twelve
rules *was* named at parse time — but for its destination, not its source.)

**Cross-origin proxying with `200` is not a thing here.** A `200` rule pointing at another
origin does not proxy; the request falls through. The same cross-origin destination with
`301` or `302` works correctly. If you need content from another origin under your own path,
that is an architecture decision, not a redirect rule.

## Two gates worth building

1. **Source side.** Reject any rule whose source is not a site-relative path — and reject
   the protocol-relative form *explicitly*, because the naive check passes it.
2. **Destination side.** Reject any rule with a cross-origin destination and status `200`.
   Do **not** reject cross-origin `301`/`302`: those are legitimate and load-bearing.

Both are string checks over the deployed rules file. Neither needs a network.

## How to re-measure on your own host

1. Generate a matrix of rules with unique, non-overlapping source prefixes.
2. Serve the built site locally with your host's own simulator, if it has one.
3. Request each rule's source path three times; record status, `Location`, and body size.
4. Capture the simulator's **startup output** — that is where parse-time diagnostics appear,
   and the difference between "fails loudly" and "fails silently" lives entirely there.
5. Run your controls in the same session, and decide *before* looking at results that a
   failing control voids the run.

Step 5 is the one that makes the rest of it evidence rather than anecdote.
