bug(download): /d/{token} is behind requireSession — Secure Client receives the LOGIN PAGE, never a bundle #5
Labels
No labels
kind/bug
kind/chore
kind/docs
kind/feature
priority/critical
priority/high
priority/low
priority/medium
size/L
size/M
size/S
size/XL
status/deferred
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
frankenbit/purser#5
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The defect
internal/web/server.go:46The one-time download URL requires a browser session. A client that cannot log in gets a
303to/login, follows it, and receives HTML.Measured — nginx access log, 2026-08-05
Cisco Secure Client parsed 2,353 bytes of login form as PKCS#12. That is the operator-reported "unable to decode the provided data" — it is decoding an HTML page.
The identical
303appears against the previous certificate at13:23:40, so this has been the failure the whole time.🔴 What it cost
An entire afternoon was spent on the bundle ENCODING — SHA-256 vs SHA-1 MAC, AES-256 vs 3DES — with three separate fixes shipped. Every bundle measured was fine. None ever reached Secure Client. The browser path always worked because Safari carries the session cookie, which is why Keychain import succeeded and masked the real failure.
The design fault
A one-time download token IS a credential. Requiring a session on top of it defeats the point of an unguessable URL handed to a client that cannot authenticate interactively — and that client is precisely the one the feature exists for.
The
downloadstable already carries the controls that matter:expires_at(15 min) andfetch_count.Scope
GET /d/{token}serves on the token alone — no session requiredfetch_count=4, so it is not one-shot today. State what it should be.GET /d/{token}returnsapplication/x-pkcs12, nottext/html. The test must fail on the current code.Related
alcatraz-infra#422— Purser design recordPURSER_P12_ENCODINGnowlegacy-des(3DES + SHA-1 MAC), which matches the shape the operator imports successfully. Keep it — it is correct — but it was never the cause.Anchor
Found by the operator, 2026-08-05, from the one asymmetry that mattered: same file, Keychain accepts it, Secure Client does not. No amount of byte-level probing of the bundle could have produced that.
AC sweep @
main— all five verified against the substrate, then ticked. Closed 2026-08-05 with every box empty.🔑 AC5 is the one that could have been faked and was not. ✅ It asserts the CONTENT TYPE, not merely a 200 — and
TestDownload_UnauthenticatedFailuresDoNotRedirectcarries the negative half: "a refusal became a redirect, which is #5 again." ⚠️ A test asserting onlystatus == 200would have passed on a login page.📌 The ticks were applied after reading each one against
main, not from memory of the merge. Per § Acceptance-criteria tick discipline, a state-asserting tick is a claim about the world and gets re-derived from the substrate.