|
| 1 | +# Security Review — Tracking |
| 2 | + |
| 3 | +Status of findings from the multi-surface security review (OIDC/OAuth2, ForwardAuth, |
| 4 | +WebAuthn/TOTP, sessions, admin/config). Work landed on branch |
| 5 | +`security/forward-auth-and-consent-csrf`. |
| 6 | + |
| 7 | +## ✅ Done (branch `security/forward-auth-and-consent-csrf`) |
| 8 | + |
| 9 | +All HIGH findings are closed. Each fix has tests; suite is green. |
| 10 | + |
| 11 | +| Commit | Fix | Sev | |
| 12 | +|--------|-----|-----| |
| 13 | +| `703d24e` | ForwardAuth fail-open when no host header; consent endpoint CSRF | HIGH ×2 | |
| 14 | +| `8a095e4` | Bearer API-key skipped group check at use-time | HIGH | |
| 15 | +| `96a657e` | Open redirect via unvalidated `X-Forwarded-Host` in login redirect | HIGH | |
| 16 | +| `84ed462` | `CLINCH_HOST` made mandatory in deployed envs; dropped request-host fallback | MEDIUM | |
| 17 | +| `f38ac2e` | TOTP code replay within drift window (+ latent plaintext backup-code bug) | HIGH | |
| 18 | +| `406a79d` | SSRF via `backchannel_logout_uri` (metadata/loopback/RFC1918) | HIGH | |
| 19 | +| `57d7d1f` | Host-auth regex unanchored (`evil-example.com` matched) | HIGH | |
| 20 | +| `89bd5f1` | Disabled user could complete 2FA mid-flow / keep session; enforce active status | HIGH | |
| 21 | +| `cd862c7` | TOTP/backup/OAuth/PKCE `code` params not filtered from logs | MEDIUM | |
| 22 | +| `2426687` | `revoke_family!` didn't revoke access tokens on refresh-token reuse | HIGH | |
| 23 | +| `44892e3` | WebAuthn clone detection logged but didn't block; false-positive on synced passkeys | HIGH | |
| 24 | +| `d49e7ce` | CSP `unsafe-inline` removed (script-src + style-src → nonces) | HIGH | |
| 25 | + |
| 26 | +**Verified false positive (no change):** PKCE *is* required by default — |
| 27 | +`require_pkce` column defaults to `true` (`db/schema.rb`), token endpoint enforces |
| 28 | +it, admin UI exposes the opt-out. Operational check: confirm no legacy confidential |
| 29 | +apps sit on `require_pkce = false`. |
| 30 | + |
| 31 | +**Follow-up before relying on CSP change:** do one manual browser pass (DevTools |
| 32 | +console) on `/signin`, OAuth consent, a Turbo navigation, dark-mode toggle, and a |
| 33 | +WebAuthn sign-in — expect zero CSP violations. Dev is report-only so violations |
| 34 | +surface as warnings without breaking. Fallback if style-src surprises: keep |
| 35 | +`style-src 'unsafe-inline'`, ship script-src only. |
| 36 | + |
| 37 | +## ☐ Remaining — MEDIUM |
| 38 | + |
| 39 | +- [ ] **`id_token_hint` ignored at OIDC logout** — any client can redirect logout to |
| 40 | + any other registered client's post-logout URI. Validate the hint's `aud` and |
| 41 | + scope the redirect to that app. `app/controllers/oidc_controller.rb` (logout). |
| 42 | +- [ ] **`offline_access` doesn't gate refresh-token issuance** — refresh tokens are |
| 43 | + minted unconditionally; gate on the granted scope. |
| 44 | + `app/controllers/oidc_controller.rb` (authorization_code grant, ~line 564). |
| 45 | +- [ ] **CSP-report endpoint hardening** — unauthenticated, no rate limit / body-size |
| 46 | + cap, logs raw CRLF (log injection). Sanitize values, cap size, rate-limit. |
| 47 | + `app/controllers/api/csp_controller.rb`. |
| 48 | +- [ ] **Port not stripped from `X-Forwarded-Host`** in main verify + bearer paths → |
| 49 | + 403 outages on non-standard ports (also a correctness bug). Reuse the |
| 50 | + port-stripping done in `check_forward_auth_token`. |
| 51 | + `app/controllers/api/forward_auth_controller.rb`. |
| 52 | +- [ ] **WebAuthn `acr:"2"` without enforced user verification** — `user_verification: |
| 53 | + "preferred"` lets a PIN-less key authenticate yet reports verified 2FA. Use |
| 54 | + `"required"`, or downgrade `acr` to `"1"` when the UV flag is absent. |
| 55 | + `app/controllers/sessions_controller.rb` (webauthn_challenge/verify), |
| 56 | + `app/controllers/webauthn_controller.rb`. |
| 57 | +- [ ] **`RESERVED_CLAIMS` incomplete** — missing `at_hash`/`auth_time`/`acr`; and |
| 58 | + `ApplicationUserClaims` has no reserved-name validation (User/Group do). Could |
| 59 | + let a custom claim overwrite a security claim. `app/services/oidc_jwt_service.rb`, |
| 60 | + `app/models/application_user_claim.rb`. |
| 61 | +- [ ] **`reset_session` not called on login** — defensive best practice for an IdP; |
| 62 | + clears pre-auth session state. `app/controllers/concerns/authentication.rb` |
| 63 | + (`start_new_session_for`). |
| 64 | +- [x] **Hardcoded private IP `192.168.2.246`** in `config/environments/production.rb` |
| 65 | + — removed; it was redundant with the `192.168.0.0/16` regex already in the |
| 66 | + `CLINCH_ALLOW_INTERNAL_IPS` block. |
| 67 | +- [ ] **CSP `form-action` widened by unvalidated `redirect_uri`** before auth — only |
| 68 | + add to `form-action` if the client_id+redirect_uri is a registered pair. |
| 69 | + `app/controllers/concerns/authentication.rb` (`allow_oauth_redirect_in_csp`). |
| 70 | +- [ ] **SVG `style` attribute permits `url()`/`expression()`** — mitigated today by |
| 71 | + `Content-Disposition: attachment`, but fragile. Sanitize CSS values or drop |
| 72 | + `style` from the allowlist. `app/models/svg_scrubber.rb`. |
| 73 | +- [ ] **WebAuthn error messages leak internals** — return generic errors to client, |
| 74 | + log detail server-side. `app/controllers/sessions_controller.rb`, |
| 75 | + `app/controllers/webauthn_controller.rb`. |
| 76 | +- [ ] **Account enumeration via webauthn challenge** — distinguishes "user not found" |
| 77 | + vs "no passkey". Return a uniform message. `app/controllers/sessions_controller.rb` |
| 78 | + (`webauthn_challenge`). |
| 79 | +- [ ] **`token_family_id` only 31 bits** (`SecureRandom.random_number(2**31)`) — |
| 80 | + birthday collision ~46k; use a UUID/string. `app/models/oidc_refresh_token.rb`. |
| 81 | +- [ ] **Session cookie uses sequential integer DB id** — HMAC-signed so not forgeable, |
| 82 | + but consider a random `token` column (Rails 8 generator default). |
| 83 | + `app/models/session.rb`, `app/controllers/concerns/authentication.rb`. |
| 84 | +- [ ] **Login rate-limit is IP-only** — no account lockout (distributed brute force / |
| 85 | + credential stuffing). Add failed-count + `locked_until` on users. |
| 86 | +- [ ] **Backup-code rate limit not reset on success** and is cache-based (resets on |
| 87 | + cache flush). Reset on success; consider DB-backed counter. `app/models/user.rb`. |
| 88 | + |
| 89 | +## ☐ Remaining — LOW / INFO |
| 90 | + |
| 91 | +- [ ] Public clients can't revoke their own tokens (revoke endpoint requires secret). |
| 92 | +- [ ] Basic-auth client creds not URL-decoded per RFC 6749 §2.3.1. |
| 93 | +- [ ] `token_hmac` columns nullable at DB level despite model `presence: true`. |
| 94 | +- [ ] Group names allow commas → injection into `X-Remote-Groups` (false memberships |
| 95 | + downstream). Add a format validator. `app/models/group.rb`. |
| 96 | +- [ ] `fa_token` leaks in redirect URL / Referer / history (60s TTL, host-bound). |
| 97 | +- [ ] Admin `domain_pattern` allows ReDoS — add a format validator. |
| 98 | + `app/models/application.rb`. |
| 99 | +- [ ] Forced-TOTP-setup login path can redirect-loop (`totp_required` + no TOTP). |
| 100 | +- [ ] `complete_setup` creates an unprompted session for any authenticated user. |
| 101 | +- [ ] Password min length only 8 — consider 12 + a max (bcrypt 72-byte truncation). |
| 102 | +- [ ] `support_unencrypted_data: true` left enabled (TOTP secret encryption migration). |
| 103 | + `config/initializers/active_record_encryption.rb`. |
| 104 | +- [ ] All crypto keys derived from a single `SECRET_KEY_BASE` root — document setting |
| 105 | + independent `ACTIVE_RECORD_ENCRYPTION_*` keys in production. |
| 106 | +- [ ] Log injection via user `email_address` in ForwardAuth logs (strip CRLF / use |
| 107 | + structured logging). `app/controllers/api/forward_auth_controller.rb`. |
| 108 | +- [ ] WebAuthn RP ID is the registrable domain (cross-subdomain credential roaming) — |
| 109 | + set `CLINCH_RP_ID` to the exact host unless roaming is intended. |
| 110 | + `config/initializers/webauthn.rb`. |
0 commit comments