| 1 | Reviewed `bb1be838`. Two blockers remain in the revised address representation and cache bypass. |
| 2 | |
| 3 | | Round-4 finding | Disposition | |
| 4 | |---|---| |
| 5 | | R4-1: framework fallback to caller headers or loopback | **Partly.** The Rails case is fixed, but Django’s `ipware` can still select caller-controlled headers; see finding 1. | |
| 6 | | R4-2: cache variation uses rewritten inbound headers | **Partly.** Local requests bypass caching; backhauled requests do not. See finding 2. | |
| 7 | | R4-3: failed hop verification substitutes an infrastructure address | **Closed as a stated degraded mode.** [Plan:23](/home/paulca/amadan.net/carlos/platform-instance-moves-p7/docs/superpowers/plans/2026-09-28-instance-moves-p7-edge-door.md:23) matches the forwarding fallback. The public-EIP property remains a deployment assumption. | |
| 8 | | R4-4: gate and visitor partitions misstated | **Closed.** [Plan:14](/home/paulca/amadan.net/carlos/platform-instance-moves-p7/docs/superpowers/plans/2026-09-28-instance-moves-p7-edge-door.md:14) now distinguishes host/app-local keys, backhaul peer addresses, and analytics’ User-Agent contribution. | |
| 9 | |
| 10 | With the edge trusted for XFF, source tracing indicates that Rails `RemoteIp`, Express `trust proxy`, and Laravel TrustProxies resolve the supplied single address to `192.0.0.8`. The reviewed Go resolvers also select it after their alternate headers are stripped. I found no additional client-address header omission in those implementations. Sources: [Rails](https://raw.githubusercontent.com/rails/rails/v8.0.2/actionpack/lib/action_dispatch/middleware/remote_ip.rb), [Express’s proxy resolver](https://raw.githubusercontent.com/jshttp/proxy-addr/master/index.js), [Laravel](https://raw.githubusercontent.com/laravel/framework/12.x/src/Illuminate/Http/Middleware/TrustProxies.php), [Symfony’s underlying resolver](https://raw.githubusercontent.com/symfony/http-foundation/7.3/Request.php), [Go realip](https://raw.githubusercontent.com/tomasen/realip/master/realip.go), [Chi](https://raw.githubusercontent.com/go-chi/chi/master/middleware/realip.go). |
| 11 | |
| 12 | Frameworks configured to ignore forwarding can still report the backend peer, including loopback; that also happens for public requests and is not a new door-specific defect. |
| 13 | |
| 14 | 1. **P1 — Django-ipware can replace the dummy address with a caller-chosen address.** |
| 15 | |
| 16 | The strip list at [plan:662](/home/paulca/amadan.net/carlos/platform-instance-moves-p7/docs/superpowers/plans/2026-09-28-instance-moves-p7-edge-door.md:662) omits **`X-Forwarded` and `Forwarded-For`**. Both survive `stripForwardedHeaders` at [homemap.go:806](/home/paulca/amadan.net/carlos/platform-instance-moves-p7/internal/edge/homemap.go:806). |
| 17 | |
| 18 | Django-ipware delegates resolution to python-ipware. Its resolver checks both omitted headers and prefers a globally routable address over an earlier non-global candidate. [Django wrapper](https://raw.githubusercontent.com/un33k/django-ipware/master/ipware/ip.py), [python-ipware implementation](https://raw.githubusercontent.com/un33k/python-ipware/v3.0.0/python_ipware/python_ipware.py). |
| 19 | |
| 20 | Crucially, Python 3.13 classifies `192.0.0.8` as **private**, despite Go’s narrower `IsPrivate` result. [Python documentation](https://docs.python.org/3/library/ipaddress.html#ipaddress.IPv4Address.is_private). |
| 21 | |
| 22 | I executed the retrieved resolver source on Python 3.13.5 with a loopback backend peer: |
| 23 | |
| 24 | | Upstream headers | Resolved address | |
| 25 | |---|---| |
| 26 | | XFF `192.0.0.8` only | `192.0.0.8`, classified private | |
| 27 | | Same XFF plus `X-Forwarded: 8.8.8.8` | `8.8.8.8` | |
| 28 | | Same XFF plus `Forwarded-For: 8.8.8.8` | `8.8.8.8` | |
| 29 | | Public comparison: XFF `1.1.1.1` plus either alternate | `1.1.1.1` | |
| 30 | |
| 31 | Thus the new representation enables address spoofing that the public comparison does not. Applications using the resolved address for authorization or rate limits can grant additional authority. Even without alternate headers, a blanket private-address authorization check can classify the dummy as trusted. |
| 32 | |
| 33 | **Required:** strip these additional headers and resolve the Python private-address classification before relying on the no-privilege premise. Add actual Django-ipware tests alongside the Rails test, locally and across backhaul. |
| 34 | |
| 35 | 2. **P2 — The cache bypass disappears across backhaul.** |
| 36 | |
| 37 | [Plan:687](/home/paulca/amadan.net/carlos/platform-instance-moves-p7/docs/superpowers/plans/2026-09-28-instance-moves-p7-edge-door.md:687) explicitly keeps the door marker local, while [plan:689](/home/paulca/amadan.net/carlos/platform-instance-moves-p7/docs/superpowers/plans/2026-09-28-instance-moves-p7-edge-door.md:689) predicates cache exclusion solely on that marker. Consequently, the receiving edge still performs lookup and storage. |
| 38 | |
| 39 | For example, a verified door response with `Vary: X-Forwarded-For` is stored under inbound `192.0.0.8`. A direct public request supplying that XFF can retrieve it, although forwarding to the application would replace that header with the public caller’s address. Lookup uses inbound headers at [edge.go:1583](/home/paulca/amadan.net/carlos/platform-instance-moves-p7/internal/edge/edge.go:1583); storage does likewise at [edge.go:1702](/home/paulca/amadan.net/carlos/platform-instance-moves-p7/internal/edge/edge.go:1702). |
| 40 | |
| 41 | Failed hop verification also loses the bypass and strips XFF, restoring the empty-header collision from round 4. |
| 42 | |
| 43 | **Required:** make cache exclusion effective at the receiving edge, including the defined verification-failure path. Extend the cache tests to two edges, both request orderings, and mismatched mesh keys. |
| 44 | |
| 45 | **The plan is not ready to build.** |