HomeIncident library
Incident library

INCIDENT REPLAY · 2026-01-22

Cloudflare Miami route leak (automated policy change)

An automated routing-policy change meant to remove Bogota prefixes deleted prefix-list filters from several BGP export policies on a Miami Junos router. The remaining terms matched every route of type 'internal', so the router advertised IBGP-learned IPv6 prefixes to transits and peers for 25 minutes. External traffic was pulled through Miami, congesting the Miami-Atlanta backbone, and about 12 Gbps was discarded by firewall filters.

Fits no familyRouting policy errorCatalog confidence: high

A reconstruction, not the operator's network. Hammerhead's authors rebuilt the change from the public record as a small network of their own. No configuration here is the operator's. What follows is what the tool reported on that reconstruction; it is not a claim that the tool would have prevented the outage.

What happened

Who: Cloudflare (AS13335)

The summary at the top is paraphrased from the sources below. Hammerhead's catalog rates its confidence in this account high.

Sources

What the public record says

  • An automated routing-policy change meant to remove Bogota prefixes deleted prefix-list filters from several BGP export policies on a Miami Junos router. The remaining terms matched every route of type 'internal', so the router advertised IBGP-learned IPv6 prefixes to transits and peers for 25 minutes. External traffic was pulled through Miami, congesting the Miami-Atlanta backbone, and about 12 Gbps was discarded by firewall filters.
  • Who: Cloudflare (AS13335)

What the reconstruction assumed

Decided by Hammerhead's authors to build a network that runs. None of it describes the operator's network.

Everything below is ours.

  • This incident rests on one or two write-ups. The routes that leaked were IPv6, carried in BGP. The replay here is the Junos policy edit over IPv4 stand-ins, which is the part a policy check sees; the Junos parser records no IPv6. The IPv6 variant (tools/replays/gen_family_ipv6.py) makes the same edit in the IPv6 address family of an IOS router, over IPv6 sessions, and fails for its cause: the term that lost its condition, and the export probe.
  • One Junos router (mia01) with the export policy, and one backbone router behind it originating what other sites announce. Transits are outside the snapshot, which is the operator's seat.
  • The term is ours: from { prefix-list SITE-BOG; route-type internal; } then accept, with the prefix-list line deleted by the change.
  • Cisco IOS syntax and stand-in address space throughout. A reconstruction, not any operator's configuration.

What Hammerhead said

Each run below is pinned by Hammerhead's replay tests at commit 0ee9732: the verdict and the quoted text are checked against the tool's output, and a run that disagrees fails the test. Reason lines are copied verbatim; a pinned line can be part of a longer reason. Every preflight run uses --max-blast-radius 100, so a change is never counted as caught merely because it touched many devices of a small network.

Hammerhead's own first-versus-now record

First verdict
fail, and partly by luck: route-type was not a condition the Junos parser modelled, so the term looked as if it had lost its ONLY match. Written inline (from route-type internal;) the condition was dropped without a trace.
Verdict now
fail, for the cause: the term lost a match condition (prefix-list SITE-BOG) and matches every route that is route-type internal; the announcement to the transit goes from 2 prefixes to 42. route-type is now parsed in both forms and decided by the session a route was learned over.

Quoted from docs/replays/families/individual.md. “First” is the tool before the fixes this replay prompted.

the Miami router and the backbone behind it; transits are outside the snapshot

now: fail

On the reconstruction, hammerhead preflight on a before and an after snapshot returned fail, for the reasons below.

Reason text pinned (some reason contains each line)

  • policy widening: mia01 route-map TRANSIT-OUT
  • lost a match condition (prefix-list SITE-BOG)
  • mia01 would announce 42 prefixes to

Pinned in tests/gauntlet/replays-individual.yaml.

IPv6 variant: the IPv6 export term to the transit loses its prefix condition, in IOS

now: fail

On the reconstruction, hammerhead preflight on a before and an after snapshot with --allow-flow-changes returned fail, for the reasons below.

Reason text pinned (some reason contains each line)

  • MIA01 route-map V6-TO-TRANSIT seq 10 (permit) lost its only match condition and now matches every route; bound out to 2001:db8:bb0::fe (IPv6 unicast)
  • policy widening: MIA01 would announce to 2001:db8:bb0::fe (AS 64530) routes it learns from other networks, which its IPv6 export policy stopped before

Caveats pinned (some caveat contains each line; a caveat never moves the verdict)

  • external advertisements: MIA01 would start announcing 1 prefix(es) to 2001:db8:bb0::fe (AS 64530) — 2001:db8:cf00::/40

Pinned in tests/gauntlet/replays-ipv6.yaml.

What changed in Hammerhead because of it

From the “What was fixed” table in Hammerhead's replay notes, where each fix landed after a failing test. “Found by” is copied as written; a family name means the batch this incident belongs to.

A permit clause that loses ONE of its match conditions

The widening check fired for a clause that lost its only match. A strict subset of the conditions it had, nothing added, is wider.

Found by: Cloudflare Miami

Junos from route-type

Unparsed in a from { } block, dropped without a trace inline. Now decided by the session the route was learned over. Any other unknown inline from condition is unparsed.

Found by: Cloudflare Miami

BGP IPv6 unicast modelled

A change to BGP IPv6 was unknown: IPv6 routes flooded over IPv4 sessions with no policy and no communities, and none was installed. One engine now runs both families on IOS, and IPv6 routes reach the IPv6 FIB.

Found by: F-root 2011, Angola Cables and Cloudflare Miami IPv6 variants

Limits

  • The configurations are a reconstruction. Where the record is silent, the assumptions above fill the gap; if one is wrong, the replay is wrong in that respect.
  • A verdict describes the reconstructed network's converged state. Timing, traffic load, and anything a device does that no configuration states, such as software reacting to a health check or a firmware bug, are outside it.
  • A pass is reported as a pass. A fail says a gate fired on the reconstruction, nothing more.
  • Why the catalog counts this outage as in scope: Simulating the candidate export policy shows internal routes newly advertised to external neighbours; a before/after advertised-routes diff on the Junos config flags it before push.

Imported from docs/replays/cloudflare-miami-2026/FACTS.md and the replay manifests in the Hammerhead repository at commit 0ee9732.