carlos / platform Public
| Round-1 finding | Disposition | |---|---| | 1. Console re-signs another app’s record | Closed. Building records exclusively from the console database removes the copied-record signing attack. | | 2. Remote authorization loses app scope | Partly. The signed scope protects successfully verified hops, but verification failure still removes the restriction; advertised support is also untrusted. | | 3. Login re-resolution and lookup race | Closed. Authorization now checks the final up used for gate processing and dispatch. | | 4. Reserved handlers override ownership | Partly. Local door requests are excluded, but receiving edges reach reserved handlers before discovering the door mark. | | 5. Replay restores revoked keys | Partly. Expiry and persisted revisions bound ordinary replay, including the stated 72-hour fresh-box allowance. Counter rollback and persistence failures remain unresolved. | | 6. Malformed XFF remains authoritative | Closed. Replacing the header with one parsed address or loopback fixes forwarding and analytics. | | 7. Unbounded stale authorization | Closed. The separate refresh budget, retention bounds and read-time expiry establish a bounded failure contract. | | 8. Listener precedes mirror readiness | Closed. Task 7 initializes the mirror before opening the listener, defines empty-mirror behavior and retains shutdown handling. | | 9. Incorrect interfaces and missing integration cases | Closed for the original findings. Dispatch, fixtures, catchall publication, fold ownership and documentation destinations are corrected. A newly introduced store-interface mismatch appears below. | | 10. Signing-domain separation | Closed. The signed domain prefix and cross-kind rejection tests address the recommendation. |
- P1 — Failed door-hop verification silently removes app authorization.
Task 5 marks a request as door traffic only after successful verification; its negative test explicitly expects “no door mark” (plan:576, plan:587). The existing verifier strips forwarded headers and continues as an ordinary client on failure (homemap.go:793).
This happens during legitimate mesh-key propagation: the sender starts signing with .next before the receiver mirrors it. A door request scoped to A, targeting B’s prefix behind A’s front hostname, then reaches B without the ownership check.
Fix: Treat attempted door hops as a distinct protocol outcome. Missing, malformed or unverifiable scope/signature must terminate the request, never downgrade it to public traffic. Test two edges with different mirrored key generations and assert that B’s handler is never called.
- P1 —
door: truedoes not prove the destination enforces door scope.
The plan makes an edges-file boolean the compatibility gate (plan:541), but edges files are unsigned JSON (homemap.go:478). A bucket writer can advertise support for an old edge; stale metadata after rollback can do the same. That edge rejects the new MAC as a plain-hop signature, then serves the request without scope enforcement.
There is also a selection race: Task 5 checks Owner(host), then backhaul independently calls Home(host) (plan:577, edge.go:2621). A refresh can switch the destination to an unsupported box between those reads.
Fix: Require authenticated support from the actual receiving endpoint before sending application traffic, through a protocol old receivers cannot process as ordinary requests. Select owner, capability and dial target from one snapshot. Test forged capability metadata, rollback and a refresh between selection and dialing.
- P1 — Re-reading the door mark happens after reserved handlers have already returned.
Task 5 checks isDoor before the steering and tunnel branches, but only discovers a received door hop afterward (plan:575). Current execution reaches steering, tunnel registration and the store door before acceptHop (edge.go:1329, edge.go:1366).
A signed door hop targeting a hostname reserved only on the receiving box reaches that handler while isDoor is still false. Re-reading the mark cannot undo the response.
Fix: Authenticate or reject attempted door hops before any reserved-handler dispatch, then apply the reserved-name exclusion. Add receiving-side tests for all three handlers, including overlapping wildcard routes and normalized hostname variants.
- P1 — The specified unconditional publication operation does not exist.
Task 2 requires Put “unconditionally (no etag)” (plan:424). However, release.Store.Put(..., "") is explicitly create-only; a nonempty argument requires the current ETag (store.go:37).
Following the specified call permits the first publication but rejects every revoke, subsequent mint and renewal. Retrying the same call cannot repair it.
Fix: Specify a supported publication operation. One option is a conditional-write retry using the bucket only for its ETag, with record contents always rebuilt from the database. Alternatively, explicitly add an overwrite interface and its implementations. Test replacement of existing and tampered objects.
- P1 — Concurrent publishers can overwrite a completed revocation with an older record.
Database mutation followed by an independent read/sign/write is not an ordered publication protocol (plan:428). A renewal reads revision 4 containing key K and pauses. A revoke publishes revision 5 without K and returns success. The renewal then overwrites it with revision 4.
Boxes that missed revision 5 continue admitting K even with a healthy bucket. The scheduled repair may be 12 hours away.
Fix: Serialize per-app publication across mutations and renewals, including every console permitted to publish. Read keys and revision in one consistent database snapshot. Conditional-write retries must rebuild from current database state rather than reuse stale signed bytes. Add a deterministic delayed-publisher test proving revision 4 cannot replace revision 5 after successful revocation.
- P1 — Restoring the console database can reuse revisions with different authorization.
Revisions come solely from the database, while mirrors reject only values below their high-water mark (plan:419, plan:551).
Suppose revision 10 revoked K. Restoring revision 9 restores K; another mutation creates revision 10 containing K again. Even boxes that observed the revocation accept that equal revision. Periodic re-signing can extend this authorization indefinitely. Conversely, resetting the counter far below existing marks prevents legitimate replacement keys from working.
Fix: Define a restore-safe authority-generation and recovery protocol before building. Recovery must invalidate uncertain credentials and advance verifier trust explicitly. Also reject conflicting key sets at the same revision while allowing expiry-only renewal. Test database rollback, counter reset and equal-revision conflicts.
- P2 — High-water persistence has no failure or crash contract.
Task 4 specifies atomic rename after a refresh raises a mark, but not persistence-before-activation, durable synchronization, load errors or retry after a failed write (plan:552). If the in-memory mark advances but writing fails, subsequent refreshes need not “raise” it again. Restart then loses revocation knowledge.
Fix: Persist accepted advances before exposing the corresponding authorization state, retain pending persistence work after failure, and define refusal behavior for unreadable or corrupt state. Explicitly classify missing state after data loss under the documented fresh-box allowance. Test write/rename failure, restart and corrupt-state loading.
The plan is not ready to build until these authorization, publication and recovery contracts are corrected.