web: Referrer-Policy same-origin; a password shown once emptied on pagehide
test / test (push) Canceled after 0s
test / test (push) Canceled after 0s
D-B3. Fetch Standard § 3.2 "append a request `Origin` header": under Referrer-Policy no-referrer a request that is not GET/HEAD and not CORS - a form POST, a navigation - gets Origin "null". originAllowed() refuses "null" whenever Sec-Fetch-Site is absent, and Fetch Metadata § 3 sends Sec-Fetch-Site only to a potentially trustworthy URL: every form, sign-in included, failed in Safari before 16.4 and in any browser on a plain-HTTP panel at a name. same-origin nulls Origin only for a request not same origin with its URL and still sends no referrer to another site. The comments of the origin check say so. D-B13. noStore keeps the page out of HTTP caches (RFC 9111 § 5.2.2.5); a history mechanism "might honor" it (§ 6), and Pragma in a response "was never specified" (§ 5.4) - it goes. CopyField.Secret marks the password of out-app-created data-secret, and panel.js empties such fields on pagehide (HTML § 7.5.9, "unload a Document", step 10.2: fired whether or not the document is kept for history traversal). Measured 2026-10-15 in Chrome 152 (the desktop app's browser), a local probe page with Cache-Control: no-store and a data-secret field: Back reloads it (pageshow persisted=false, a new load), so this browser keeps no-store pages out of its back/forward cache; the handler, fired on the same page, empties the field. Firefox and Safari not measured. Tests: the header value; the created page has the password data-secret and the login not, no Pragma. Stand (2026-10-15, make e2e, exit 0): new form_post_with_the_browsers_origin - Referrer-Policy: same-origin on a page; a POST carrying the page's own Origin and no Sec-Fetch-Site, what a browser sends now, passes; one carrying Origin: null, what it sent before, is 403. (A first run failed where the e2e client and a view test scraped the old input markup; both read data-secret now.) go vet and go test ./... pass on the server (24 packages). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -393,14 +393,15 @@ func (h *Handlers) recalcApp(appID int64) error {
|
||||
return h.recalcRateLimit(store.RateLimitScopeApp, appID)
|
||||
}
|
||||
|
||||
// noStore keeps a page that shows a secret out of every cache and out of the
|
||||
// browser's history copy: the password on it is shown once.
|
||||
// RFC 9111 § 5.2.2.5: no-store keeps it out of every HTTP cache, private and
|
||||
// shared. The history part is not what it governs (§ 6), and Pragma has no
|
||||
// defined meaning in a response (§ 5.4): assumptions-2026-10-14, D-B13.
|
||||
// noStore keeps a page that shows a secret out of every HTTP cache, private
|
||||
// and shared (RFC 9111 § 5.2.2.5): the password on it is shown once. A
|
||||
// browser's history - the back/forward cache - is not what it governs: § 6,
|
||||
// a history mechanism "might honor" no-store. So the page's field is also
|
||||
// marked secret, and panel.js empties it when the page is left (pagehide).
|
||||
// No Pragma: in a response it "was never specified" (§ 5.4). Assumptions
|
||||
// D-B13.
|
||||
func noStore(w http.ResponseWriter) {
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
w.Header().Set("Pragma", "no-cache")
|
||||
}
|
||||
|
||||
// blankAppForm is the application form as it opens: any address of the domain,
|
||||
|
||||
@@ -314,11 +314,11 @@ func TestPasswordIsShownOnceAndNeverCached(t *testing.T) {
|
||||
rec := postFormAs(s.h.HandleApplicationCreate, globalPrincipal, "/outbound/domains/1/applications/new", s.paths, url.Values{
|
||||
"login": {"prod-server"}, "mode": {"list"}, "addresses": {"alerts@example.org"},
|
||||
})
|
||||
if rec.Code != http.StatusCreated || rec.Header().Get("Cache-Control") != "no-store" || rec.Header().Get("Pragma") != "no-cache" {
|
||||
if rec.Code != http.StatusCreated || rec.Header().Get("Cache-Control") != "no-store" || rec.Header().Get("Pragma") != "" {
|
||||
t.Fatalf("create = %d, Cache-Control %q", rec.Code, rec.Header().Get("Cache-Control"))
|
||||
}
|
||||
password := s.sasl["prod-server"]
|
||||
has(t, "created", rec.Body.String(), `<h2>Shown once</h2>`, `value="`+password+`"`, `value="prod-server"`,
|
||||
has(t, "created", rec.Body.String(), `<h2>Shown once</h2>`, `value="`+password+`" data-secret>`, `value="prod-server">`,
|
||||
`If this one is lost, generate another from the application's page.`, `alerts@example.org`, `465 SSL/TLS · 587 STARTTLS`,
|
||||
`href="/outbound/domains/1">Done — back to example.org</a>`)
|
||||
lacks(t, "created", rec.Body.String(), `stopped working`)
|
||||
|
||||
Reference in New Issue
Block a user