Remediate PEN-01 through PEN-07 (pentest round 4)

- PEN-01: fix extractBearerFromRequest to validate Bearer prefix
  using strings.SplitN + EqualFold; add TestExtractBearerFromRequest
- PEN-02: security headers confirmed present after redeploy (live
  probe 2026-03-15)
- PEN-03: accepted — Swagger UI self-hosting disproportionate to risk
- PEN-04: accepted — OpenAPI spec intentionally public
- PEN-05: accepted — gRPC port 9443 intentionally public
- PEN-06: remove RecordLoginFailure from REST TOTP-missing branch
  to match gRPC handler (DEF-08); add
  TestTOTPMissingDoesNotIncrementLockout
- PEN-07: accepted — per-account hard lockout covers the same threat
- Update AUDIT.md: all 7 PEN findings resolved (4 fixed, 3 accepted)

Security: PEN-01 removed a defence-in-depth gap where any 8+ char
Authorization value was accepted as a Bearer token. PEN-06 closed an
account-lockout-via-omission attack vector on TOTP-enrolled accounts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-14 23:14:47 -07:00
parent 1121b7d4fd
commit 5c242f8abb
3 changed files with 133 additions and 12 deletions

View File

@@ -271,8 +271,13 @@ func (s *Server) handleLogin(w http.ResponseWriter, r *http.Request) {
// TOTP check (if enrolled).
if acct.TOTPRequired {
if req.TOTPCode == "" {
// Security (DEF-08 / PEN-06): do NOT increment the lockout counter
// for a missing TOTP code. A missing code means the client needs to
// re-prompt the user — it is not a credential failure. Incrementing
// here would let an attacker trigger account lockout by omitting the
// code after a correct password guess, and would penalise well-behaved
// clients that call Login in two steps (password first, TOTP second).
s.writeAudit(r, model.EventLoginFail, &acct.ID, nil, `{"reason":"totp_missing"}`)
_ = s.db.RecordLoginFailure(acct.ID)
middleware.WriteError(w, http.StatusUnauthorized, "TOTP code required", "totp_required")
return
}