Skip to content

Commit 208e21b

Browse files
sarg3ntclaude
andcommitted
fix(passkey): address Copilot review on #63
- Warn at startup if WebAuthn RPID resolved to "localhost" *and* BASE_URL was never explicitly set. Without this, an operator who forgot to configure BASE_URL in production gets a working-looking Passkey UI that fails at registration with an opaque origin-mismatch error from the browser. The warning makes the misconfig findable in the journal. - Set `Cache-Control: no-store` (+ `Pragma: no-cache` for old proxies) on the PasskeyRegisterBegin response. The body carries a single-use challenge + session_id bound to the requesting user; an intermediary cache could surface the same challenge to a different user. The /finish handler's per-user session-ID check would reject the cross-user case, but defense-in-depth costs two header lines. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 029ca5d commit 208e21b

2 files changed

Lines changed: 15 additions & 1 deletion

File tree

gearbox/cmd/server/main.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,17 @@ func main() {
416416
logger.Info("WebAuthn initialized",
417417
"rp_id", cfg.WebAuthnRPID,
418418
"origins", cfg.WebAuthnRPOrigins)
419+
// If RPID is "localhost" but BASE_URL was never explicitly set,
420+
// the operator probably forgot to configure it. Passkey UI will
421+
// render but registration will fail at the authenticator with an
422+
// opaque origin-mismatch error — warn loudly so the misconfig is
423+
// findable in the journal.
424+
if cfg.WebAuthnRPID == "localhost" && os.Getenv("BASE_URL") == "" {
425+
logger.Warn("WebAuthn RPID is 'localhost' because BASE_URL is unset — " +
426+
"passkey UI will appear, but registration from any non-localhost " +
427+
"hostname will fail with an origin-mismatch error. Set BASE_URL " +
428+
"(or WEBAUTHN_RP_ID/WEBAUTHN_RP_ORIGINS) in production.")
429+
}
419430
}
420431
} else {
421432
logger.Info("WebAuthn disabled (BASE_URL did not yield a hostname)")

gearbox/internal/framework/handler/passkeys.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,11 @@ func (h *Handler) PasskeyRegisterBegin(w http.ResponseWriter, r *http.Request) {
8989
return
9090
}
9191

92-
// Return options to client
92+
// Return options to client. The challenge is single-use and bound to
93+
// the session ID we just stored — never let an intermediary cache it.
9394
w.Header().Set("Content-Type", "application/json")
95+
w.Header().Set("Cache-Control", "no-store")
96+
w.Header().Set("Pragma", "no-cache")
9497
json.NewEncoder(w).Encode(PasskeyRegisterBeginResponse{ //#nosec G104
9598
Options: options,
9699
SessionID: sessionID,

0 commit comments

Comments
 (0)