155 lines
5.4 KiB
Markdown
155 lines
5.4 KiB
Markdown
# Security Status
|
|
|
|
**Last Audit:** 2025-12-31
|
|
**Target Users:** Self-hosters, small businesses
|
|
|
|
> **Beta Release Criteria:** See [beta-checklist.md](beta-checklist.md) for overall release readiness assessment.
|
|
>
|
|
> This document demonstrates our proactive approach to security through systematic vulnerability tracking and remediation.
|
|
|
|
---
|
|
|
|
## Summary
|
|
|
|
| Phase | Status | Description |
|
|
|-------|--------|-------------|
|
|
| Phase 1-2 | ✅ Complete | Rate limiting, security headers, tests |
|
|
| Phase 3 | ✅ Complete | Critical fixes (token DoS, plaintext storage, fail-open) |
|
|
| Phase 4 | ✅ Complete | High priority (PKCE, WebAuthn, email re-auth, TOTP encryption) |
|
|
| Phase 5 | 🟡 In Progress | Security enhancements |
|
|
| Phase 6 | ⏳ Optional | Hardening & documentation |
|
|
|
|
---
|
|
|
|
## Outstanding Security Issues
|
|
|
|
---
|
|
|
|
### MEDIUM - Account Lockout Mechanism
|
|
|
|
**Files:** `app/controllers/sessions_controller.rb`, `app/models/user.rb`
|
|
**Impact:** Brute force attack mitigation
|
|
|
|
**Implementation:**
|
|
- Add `failed_login_attempts` and `locked_until` columns to users
|
|
- Progressive delays: 5 attempts → 5s, 10 → 1min, 15 → 15min, 20+ → 1hr
|
|
- Admin notification on lockout
|
|
- Configurable via `MAX_LOGIN_ATTEMPTS` ENV
|
|
|
|
---
|
|
|
|
### MEDIUM - Per-Account Rate Limiting
|
|
|
|
**Files:** `app/controllers/sessions_controller.rb`, `config/initializers/rack_attack.rb`
|
|
**Impact:** Distributed brute force prevention
|
|
|
|
**Current:** Global rate limiting only
|
|
**Needed:** Add per-email rate limiting (10 failed attempts/email/hour)
|
|
|
|
---
|
|
|
|
### LOW - WebAuthn Clone Detection Action
|
|
|
|
**File:** `app/controllers/sessions_controller.rb:252-256`
|
|
**Impact:** Cloned credential detection
|
|
|
|
**Current:** Logs warning on suspicious sign count
|
|
**Improvement:** Block authentication, notify user/admin
|
|
|
|
---
|
|
|
|
## Configuration Choices (Not Vulnerabilities)
|
|
|
|
These are policy decisions for self-hosters, not security bugs:
|
|
|
|
| Item | Default | Notes |
|
|
|------|---------|-------|
|
|
| Session cookie domain | Root domain | Enables SSO across subdomains. Add `SECURE_SUBDOMAIN_ISOLATION` ENV to disable |
|
|
| CSP policy | unsafe-inline, unsafe-eval | Required for Stimulus/Turbo. Audit JS to remove if needed |
|
|
| Logout redirect validation | Allows query params | Per OAuth 2.0 spec. Document behavior |
|
|
| Invitation token lifetime | 24 hours | Add `INVITATION_TOKEN_LIFETIME` ENV for high-security deployments |
|
|
| Password minimum length | 8 chars | Add `PASSWORD_MIN_LENGTH` ENV, consider zxcvbn |
|
|
| Admin self-demotion check | String comparison | Minor - use `.to_i` for integer comparison |
|
|
|
|
---
|
|
|
|
## Completed Fixes
|
|
|
|
### Phase 3 - Critical (December 2025)
|
|
|
|
**1. Token Lookup DoS** ✅
|
|
- Problem: O(n) BCrypt comparisons on token lookup
|
|
- Solution: HMAC-based token prefix for O(1) indexed lookup
|
|
- Files: `token_prefixable.rb`, token models, migration
|
|
|
|
**2. Plaintext Token Storage** ✅
|
|
- Problem: Access tokens stored in plaintext
|
|
- Solution: Removed `token` column, use BCrypt digest only
|
|
- Files: Migration, fixtures, tests
|
|
|
|
**3. Forward Auth Fail-Open** ✅
|
|
- Problem: Unmatched domains allowed by default
|
|
- Solution: Changed to fail-closed (403 for unconfigured domains)
|
|
- Files: `forward_auth_controller.rb`
|
|
|
|
---
|
|
|
|
### Phase 4 - High Priority (December 2025)
|
|
|
|
**4. PKCE Enforcement** ✅
|
|
- Problem: PKCE was optional
|
|
- Solution: Per-app PKCE with mandatory enforcement for public clients
|
|
- Files: Application model, OIDC controller, migration
|
|
|
|
**5. WebAuthn Info Disclosure** ✅
|
|
- Problem: `/webauthn/check` leaked user_id and preferred_method
|
|
- Solution: Minimal response, rate limiting (10/min), identical responses for non-existent users
|
|
- Files: `webauthn_controller.rb`
|
|
|
|
**6. OIDC State URL Encoding** ✅
|
|
- Problem: State parameter not consistently URL-encoded
|
|
- Solution: `CGI.escape()` on all redirect URLs
|
|
- Files: `oidc_controller.rb` (4 locations)
|
|
|
|
**7. Email Change Re-authentication** ✅
|
|
- Problem: Email could be changed without password
|
|
- Solution: Require current password for email changes
|
|
- Files: `profiles_controller.rb`, view
|
|
|
|
**12. TOTP Secret Encryption** ✅
|
|
- Problem: TOTP secrets stored in plaintext
|
|
- Solution: Rails `encrypts` with keys derived from SECRET_KEY_BASE
|
|
- Files: `user.rb`, `active_record_encryption.rb`
|
|
|
|
**13. WebAuthn Credential ID Enumeration** ✅
|
|
- Problem: Global credential lookup allowed enumeration via 404 vs 403 responses
|
|
- Solution: Scoped credential lookup to current user, identical responses
|
|
- Files: `webauthn_controller.rb`, `webauthn_credential_enumeration_test.rb`
|
|
|
|
---
|
|
|
|
## Security Strengths
|
|
|
|
- **Token security:** HMAC prefix + BCrypt, no plaintext storage
|
|
- **Authorization codes:** Pessimistic locking, single-use enforcement
|
|
- **Refresh tokens:** Family tracking for rotation attack detection
|
|
- **Reserved claims:** Validation prevents claim override attacks
|
|
- **Rate limiting:** Applied on all authentication endpoints
|
|
- **Forward auth:** Fail-closed by default
|
|
- **TOTP:** AES-256-GCM encryption at rest
|
|
- **Email changes:** Require password re-authentication
|
|
- **Credential isolation:** Scoped lookups prevent enumeration attacks
|
|
|
|
---
|
|
|
|
## Audit History
|
|
|
|
| Date | Event |
|
|
|------|-------|
|
|
| 2025-12-31 | Credential ID enumeration fix (scoped lookups) |
|
|
| 2025-12-31 | Security review - 1 new issue found (credential enumeration) |
|
|
| 2025-12-31 | Phase 4 completed (PKCE, WebAuthn, email re-auth, TOTP) |
|
|
| 2025-12-30 | Phase 3 completed (token DoS, plaintext storage, fail-open) |
|
|
| 2025-12-30 | Comprehensive security audit - 18 issues identified |
|
|
| Earlier | Phase 1-2 completed (rate limiting, headers, tests) |
|