Skip to content

feat: Add /api/v1 prefix and security hardening#16

Merged
StuMason merged 3 commits intomainfrom
feature/security-fixes-and-api-v1
Jan 18, 2026
Merged

feat: Add /api/v1 prefix and security hardening#16
StuMason merged 3 commits intomainfrom
feature/security-fixes-and-api-v1

Conversation

@StuMason
Copy link
Owner

Summary

  • Add /api/v1 prefix to all user data endpoints (breaking change for API consumers)
  • Implement security hardening across CSRF, OAuth, session cookies, and login protection
  • Add SRI hashes to CDN scripts

Changes

API Versioning

  • All user data endpoints now under /api/v1/users/{user_id}/...
  • /health and /oauth/* remain at root level (no version prefix)

Security Fixes (High Priority)

  • CSRF: Tightened exclusions - /admin/sync, /admin/api-keys/, /admin/settings now require CSRF token (HTMX already sends it)
  • OAuth callback validation: Require HTTPS in production, validate URL is well-formed
  • Login rate limiting: Lock out IP after 5 failed attempts for 15 minutes

Security Fixes (Medium Priority)

  • Bounded OAuth states: Max 1000 entries to prevent memory exhaustion
  • Session cookies: Added secure, httponly, samesite=lax flags
  • SRI hashes: Added integrity attributes to htmx, chart.js, chartjs-adapter-date-fns

Test plan

  • uv run pytest - 74 tests pass
  • uv run ruff check src/ - All checks passed
  • uv run mypy src/polar_flow_server - No errors
  • Manual API testing with new /api/v1 prefix
  • Callback URL validation rejects HTTP, accepts HTTPS
  • Old paths return 404

Stu Mason + AI me@stumason.dev

API Versioning:
- Add /api/v1 prefix to all user data endpoints
- Keep /health and /oauth at root level (no version prefix)

Security Fixes (High Priority):
- Tighten CSRF exclusions - remove /admin/sync, /admin/api-keys/,
  /admin/settings (HTMX already sends CSRF token)
- Add callback URL validation for OAuth - require HTTPS in production,
  validate URL is well-formed
- Add login rate limiting - lock out after 5 failed attempts for 15 min

Security Fixes (Medium Priority):
- Bound OAuth state dictionaries with maxsize=1000 to prevent memory
  exhaustion attacks
- Add explicit session cookie security: secure, httponly, samesite=lax
- Add SRI integrity hashes to CDN scripts (htmx, chart.js, date-fns)

Documentation:
- Update README API examples with /api/v1 prefix

Stu Mason + AI <me@stumason.dev>
Stu Mason + AI <me@stumason.dev>
@claude
Copy link

claude bot commented Jan 18, 2026

Pull Request Review: Add /api/v1 prefix and security hardening

Summary

This PR implements significant security improvements and introduces API versioning with:

  • Breaking change: All user data endpoints moved to /api/v1/users/{user_id}/...
  • Security hardening: login rate limiting, bounded OAuth state storage, callback URL validation
  • Enhanced cookie security and CSRF protection
  • SRI integrity checks for CDN dependencies

Security Improvements (Strengths)

1. Login Rate Limiting (admin/routes.py:57-145)

Excellent addition with IP-based tracking, proxy header support, periodic cleanup, and successful login clearing.

2. Bounded OAuth State Cache

Both BoundedTTLCache and BoundedOAuthStateCache prevent memory exhaustion with LRU eviction (max 1000 entries) and automatic TTL-based expiry (10 minutes).

3. OAuth Callback URL Validation (api/keys.py:103-133)

Enforces HTTPS in production, validates URL structure, prevents open redirect vulnerabilities.

4. Session Cookie Security (app.py:101-110)

Implements secure=True (HTTPS only), httponly=True (prevents XSS), samesite=lax (CSRF protection).

5. CSRF Protection Tightening (app.py:113-132)

Reduced exclusion list with proper handling of external OAuth callbacks and API key auth endpoints.

6. SRI Integrity Checks (templates/base.html)

Added integrity hashes for HTMX, Chart.js, and adapter to prevent supply chain attacks.


Security Concerns

CRITICAL: State Validation Logic Error

Location: src/polar_flow_server/admin/routes.py:988-993

The current code has a bug where if pop returns None (state not found), it skips the expiry check and continues processing. This should fail immediately when state is not found.

HIGH: Missing Test Coverage

The new security classes lack unit tests for LoginRateLimiter, BoundedTTLCache, BoundedOAuthStateCache, and OAuth callback validation. Security regressions could be introduced without detection.

MEDIUM: IP Spoofing Risk

Location: src/polar_flow_server/admin/routes.py:483-496

The _get_client_ip function trusts X-Forwarded-For header unconditionally. If not behind a proxy, attackers can spoof this header to bypass rate limiting. Recommend adding TRUST_PROXY_HEADERS setting.

MEDIUM: Weak Lockout Duration

15-minute lockout after 5 attempts is relatively weak for admin login. Consider increasing to 30-60 minutes or implementing exponential backoff.


Overall Assessment

Strengths: Addresses real security concerns, clean implementation, API versioning, excellent SRI checks.

Concerns: Critical state validation bug, missing test coverage, IP spoofing risk, code duplication.


Recommendation: REQUEST CHANGES

Required before merge:

  1. Fix OAuth state validation bug (admin/routes.py:988-993)
  2. Add unit tests for LoginRateLimiter, BoundedTTLCache, BoundedOAuthStateCache
  3. Add test for callback URL validation
  4. Add integration test verifying /users/* returns 404

Recommended (can be follow-up PR):

  • Consolidate cache implementations into core/security.py
  • Add TRUST_PROXY_HEADERS setting
  • Increase lockout duration or add exponential backoff
  • Add CHANGELOG entry

Great work on the security hardening! The approach is sound and addresses real vulnerabilities. With the bug fix and test coverage, this will be a strong addition.

@claude
Copy link

claude bot commented Jan 18, 2026

Pull Request Review

This PR introduces API versioning with /api/v1 prefix and implements comprehensive security hardening. Overall, this is a well-executed security improvement with good defensive practices. However, there are several security concerns and potential issues that should be addressed.


🔴 Critical Issues

1. IP Spoofing Vulnerability in Rate Limiting (src/polar_flow_server/admin/routes.py:483-496)

The _get_client_ip() function trusts proxy headers (X-Forwarded-For, X-Real-IP) without validation:

def _get_client_ip(request: Request[Any, Any, Any]) -> str:
    forwarded_for = request.headers.get("x-forwarded-for")
    if forwarded_for:
        return forwarded_for.split(",")[0].strip()  # ⚠️ VULNERABLE

Attack scenario:

  • Attacker sends custom X-Forwarded-For: 1.2.3.4 header
  • Rate limiter tracks fake IP instead of real IP
  • Attacker bypasses rate limiting by changing the header value on each request

Fix: Only trust proxy headers when behind a known proxy. Add configuration to specify trusted proxy IPs and validate:

# Only trust X-Forwarded-For if request comes from trusted proxy
TRUSTED_PROXIES = {"127.0.0.1", "::1"}  # Add nginx/Coolify IPs
if request.client and request.client.host in TRUSTED_PROXIES:
    # Then trust X-Forwarded-For

Or use Litestar's built-in client IP handling with configured trusted proxies.


2. Missing CSRF Validation on Critical Admin Endpoints (src/polar_flow_server/app.py:118-131)

The CSRF exclusion list is too broad:

exclude=[
    "/admin/settings",  # ⚠️ Should require CSRF - modifies OAuth credentials
    "/admin/sync",      # ⚠️ Should require CSRF - triggers data sync
    "/admin/api-keys/", # ⚠️ Should require CSRF - regenerates/revokes keys
]

Attack scenario:

  • Attacker hosts malicious page with: <form action="https://victim-server.com/admin/sync" method="POST">
  • Logged-in admin visits attacker's page
  • Form auto-submits, triggering unintended sync/key regeneration

Fix: Remove these from CSRF exclusions. The PR description says "HTMX already sends CSRF token" - if that's true, these endpoints should work fine with CSRF enabled.


3. Callback URL Validation Bypass (src/polar_flow_server/api/keys.py:104-113)

The callback URL validation allows http://localhost in debug mode but the check has issues:

is_debug = settings.log_level == "DEBUG"
if parsed.scheme == "http":
    netloc_lower = parsed.netloc.lower()
    if not (netloc_lower.startswith("localhost") or ...):
        if not is_debug:  # ⚠️ This check is too late
            return False, "HTTP only allowed for localhost"

Issues:

  1. log_level == "DEBUG" is not a reliable production check - use explicit environment flag
  2. Logic flaw: The if not is_debug check is inside the localhost check, making it confusing
  3. Missing port validation - attacker could use http://localhost.evil.com

Fix:

# Use explicit environment flag
is_production = settings.deployment_mode == DeploymentMode.SAAS or settings.base_url is not None

if parsed.scheme == "http":
    # Only allow http for actual localhost in dev
    if is_production:
        return False, "HTTPS required in production"
    if not (netloc_lower == "localhost" or netloc_lower.startswith("localhost:")
            or netloc_lower == "127.0.0.1" or netloc_lower.startswith("127.0.0.1:")):
        return False, "HTTP only allowed for localhost"

🟡 High Priority Issues

4. Race Conditions in Rate Limiter Cleanup (src/polar_flow_server/admin/routes.py:116-132)

The cleanup logic modifies dictionaries during iteration:

for ip in list(self._attempts.keys()):
    self._attempts[ip] = [t for t in self._attempts[ip] if t > cutoff]
    if not self._attempts[ip]:
        del self._attempts[ip]  # ⚠️ Modifying during iteration (even with list())

While list() creates a copy of keys, this is still a code smell and could cause issues in concurrent scenarios. Python's GIL protects against crashes, but you could get inconsistent state.

Fix: Use dictionary comprehension for atomic updates:

self._attempts = {
    ip: [t for t in attempts if t > cutoff]
    for ip, attempts in self._attempts.items()
    if any(t > cutoff for t in attempts)
}

5. Missing Rate Limiter Synchronization (src/polar_flow_server/admin/routes.py:188)

_login_rate_limiter = LoginRateLimiter(max_attempts=5, lockout_minutes=15)

This global instance is accessed by multiple async requests concurrently without any locking. While individual dictionary operations are atomic due to GIL, compound operations like "check + increment" are not thread-safe.

Attack scenario:

  • 5 concurrent login attempts from same IP
  • All read attempts list before any increments
  • All pass the threshold check
  • Attacker gets unlimited attempts

Fix: Add async lock for critical sections:

import asyncio

class LoginRateLimiter:
    def __init__(self, ...):
        self._lock = asyncio.Lock()

    async def record_failure(self, ip: str) -> bool:
        async with self._lock:
            # existing logic

Same issue exists in BoundedTTLCache and BoundedOAuthStateCache.


6. SRI Hash Rotation Risk (src/polar_flow_server/templates/base.html:7-31)

The PR adds SRI integrity hashes for CDN scripts:

<script src="https://unpkg.com/htmx.org@2.0.4"
        integrity="sha384-HGfztofotfshcF7+8n44JQL2oJmowVChPTg48S+jvZoztPfvwD79OC/LTtG6dMp+"
        crossorigin="anonymous"></script>

Issue: If the CDN silently updates the file at that URL (e.g., for a security patch), the SRI hash will break and the app will stop working. Unpkg and jsDelivr sometimes update patch versions.

Recommendations:

  1. Document SRI hash sources (where did these hashes come from?)
  2. Add monitoring/alerts for SRI failures
  3. Consider self-hosting these libraries for more control

7. OAuth State Cache Size Still Vulnerable (src/polar_flow_server/admin/routes.py:42-50, keys.py:43-59)

Both caches set maxsize=1000:

_oauth_states = BoundedTTLCache(maxsize=1000, ttl_minutes=10)
_saas_oauth_states = BoundedOAuthStateCache(maxsize=1000, ttl_minutes=10)

Issue: 1000 concurrent OAuth flows is unrealistic for most deployments. An attacker can still fill the cache with 1000 fake states, evicting legitimate OAuth flows.

Fix:

  1. Lower default to maxsize=100 (more realistic)
  2. Make it configurable via settings
  3. Add logging when eviction happens (indicates potential attack)

🟢 Medium Priority Issues

8. Missing Input Validation (src/polar_flow_server/api/keys.py:211)

The oauth_start_saas endpoint accepts arbitrary callback_url and client_id from query params without length limits.

Risk: Attacker could send extremely long URLs to cause memory issues or logging DoS.

Fix: Add validation:

if len(callback_url) > 2048:
    raise ValidationException("callback_url too long")
if client_id and len(client_id) > 255:
    raise ValidationException("client_id too long")

9. Error Message Information Disclosure (src/polar_flow_server/admin/routes.py:510-514)

The rate limiting error reveals the exact lockout duration:

"error": "Too many failed attempts. Please try again in 15 minutes."

Minor issue: This helps attackers time their attacks. Consider generic message: "Too many failed attempts. Please try again later."


10. Missing Security Headers

The app doesn't set security headers like:

  • X-Content-Type-Options: nosniff
  • X-Frame-Options: DENY
  • Content-Security-Policy
  • Strict-Transport-Security (HSTS)

Recommendation: Add Litestar middleware for security headers, especially important given the admin dashboard serves HTML.


📝 Code Quality & Style

11. Inconsistent Error Handling (src/polar_flow_server/api/keys.py:272-282)

OAuth callback tries to redirect with error, but has multiple code paths that could fail silently:

if oauth_state and oauth_state in _saas_oauth_states:
    state_data = _saas_oauth_states.pop(oauth_state)
    if state_data:  # ⚠️ Could be None after pop
        callback_url = state_data["callback_url"]
        # ...redirect
# Falls through to raise exception if state_data is None

Style issue: .pop() returns None if key doesn't exist, but we already checked in _saas_oauth_states. This is redundant and confusing.

Fix: Simplify:

if oauth_state in _saas_oauth_states:
    state_data = _saas_oauth_states.pop(oauth_state)
    callback_url = state_data["callback_url"]  # Safe, we know it exists

12. Missing Tests for Security Features

None of the new security classes have tests:

  • BoundedTTLCache
  • LoginRateLimiter
  • BoundedOAuthStateCache
  • _validate_callback_url

Critical gaps:

  • No tests for rate limiting bypass scenarios
  • No tests for OAuth state eviction
  • No tests for callback URL validation edge cases

Recommendation: Add comprehensive tests for all security-critical code paths.


13. Magic Numbers (src/polar_flow_server/admin/routes.py:188)

_login_rate_limiter = LoginRateLimiter(max_attempts=5, lockout_minutes=15)

These should be configurable via settings, not hardcoded.


✅ Positive Aspects

  1. Good use of bounded caches - Prevents unbounded memory growth (but see size concerns above)
  2. Constant-time comparison - secrets.compare_digest() for API keys (auth.py:97, 235)
  3. Secure session cookies - httponly, secure, samesite=lax (app.py:107-109)
  4. API versioning - Clean /api/v1 prefix for future compatibility
  5. SRI integrity hashes - Good defense-in-depth (but see rotation concerns)
  6. One-time OAuth state use - .pop() ensures states can't be replayed

📋 Summary & Recommendation

Category Count
Critical (🔴) 3
High (🟡) 4
Medium (🟢) 3
Style/Quality 3

Overall Assessment: ⚠️ REQUEST CHANGES

This PR makes significant security improvements, but the IP spoofing vulnerability and missing CSRF protection are critical issues that must be fixed before merge. The rate limiter synchronization issues could also lead to bypasses under load.

Must Fix Before Merge:

  1. Fix IP spoofing vulnerability in _get_client_ip() (Add Claude Code GitHub Workflow #1)
  2. Remove CSRF exclusions for /admin/settings, /admin/sync, /admin/api-keys/ (feat: Complete V3 API Integration with HTMX Admin Panel #2)
  3. Fix callback URL validation logic (feat: Complete Polar Experience - Dashboard, API, Biosensing & Analytics #3)
  4. Add async locks to rate limiter classes (feat: API key authentication and Alembic migrations #5)

Recommended for Follow-up PR:

  • Add comprehensive security tests
  • Implement security headers middleware
  • Make rate limit config values configurable
  • Add monitoring/alerting for SRI failures

Great work on the security hardening! The approach is solid - just needs these fixes to be production-ready. 🔒

Security fixes:
- Fix IP spoofing: only trust proxy headers from localhost (reverse proxy)
- Fix callback URL validation: explicit production check, strict localhost
- Add async locks to rate limiter and OAuth caches (thread safety)
- Fix race conditions: use dict comprehension for atomic cleanup
- Lower OAuth state cache to 100 entries, log when full
- Add client_id length validation (max 255 chars)
- Simplify OAuth callback error handling

The CSRF exclusion concern in the review was a misread - we already
removed /admin/sync, /admin/api-keys/, /admin/settings from exclusions.

Stu Mason + AI <me@stumason.dev>
@claude
Copy link

claude bot commented Jan 18, 2026

Pull Request Review: Security Hardening & API Versioning

Summary

This PR adds significant security improvements and introduces API versioning with the /api/v1 prefix. The changes include:

  • Bounded TTL caches for OAuth states (prevents memory exhaustion)
  • Login rate limiting (prevents brute force attacks)
  • Callback URL validation with HTTPS enforcement
  • SRI integrity checks for CDN resources
  • Enhanced CSRF protection configuration
  • Session cookie security flags
  • API versioning under /api/v1

File-by-File Analysis

src/polar_flow_server/admin/routes.py (lines 58-150)

Changes:

  • Replaced simple dict _oauth_states with BoundedTTLCache class
  • Added LoginRateLimiter class for brute force protection
  • Added _get_client_ip() helper with proxy header validation

Security - EXCELLENT:

  1. BoundedTTLCache (lines 58-114):

    • ✅ Prevents memory exhaustion with maxsize=100
    • ✅ Thread-safe via asyncio.Lock
    • ✅ Automatic cleanup of expired entries
    • ✅ LRU eviction when full with warning logs
    • ✅ Atomic updates via dict comprehension
  2. LoginRateLimiter (lines 121-200):

    • ✅ Prevents brute force: 5 attempts, 15min lockout
    • ✅ Thread-safe with asyncio.Lock
    • ✅ Periodic cleanup to prevent memory growth
    • ✅ IP-based tracking
    • ✅ Success clears failed attempts
  3. _get_client_ip() (lines 494-517):

    • Critical security: Only trusts X-Forwarded-For from localhost
    • ✅ Prevents IP spoofing attacks
    • ✅ Proper handling of proxy headers for Coolify/nginx deployments

Bugs: None identified

Style: ✅ Excellent documentation, clear separation of concerns

Improvements:

  1. Consider making rate limiter configurable via settings:
    _login_rate_limiter = LoginRateLimiter(
        max_attempts=settings.login_max_attempts,
        lockout_minutes=settings.login_lockout_minutes
    )

src/polar_flow_server/admin/routes.py (lines 491-568)

Changes:

  • Added rate limit check before authentication
  • Shows lockout message to user
  • Records failures/successes

Security - EXCELLENT:

  • ✅ Rate limit checked BEFORE password validation (prevents timing attacks)
  • ✅ Generic error message ("Too many failed attempts") doesn't leak info
  • ✅ Successful login clears failed attempts

Bugs: None

Style: ✅ Clean integration with existing flow


src/polar_flow_server/admin/routes.py (lines 964-1019)

Changes:

  • OAuth flow now uses bounded cache with async methods
  • Removed manual cleanup (handled by cache)

Security - EXCELLENT:

  • ✅ CSRF protection maintained
  • ✅ One-time state usage enforced
  • ✅ Memory exhaustion prevented

Bugs: None

Style: ✅ Much cleaner than manual dict management


src/polar_flow_server/api/keys.py (lines 37-147)

Changes:

  • Added BoundedOAuthStateCache for SaaS OAuth states
  • Added _validate_callback_url() with comprehensive checks
  • Added _is_localhost() helper

Security - OUTSTANDING:

  1. BoundedOAuthStateCache (lines 37-105):

    • ✅ Same excellent design as admin cache
    • ✅ Stores callback_url and client_id with expiry
    • ✅ Thread-safe, bounded, auto-cleanup
  2. _validate_callback_url() (lines 108-147):

    • ✅ Length limit (2048 chars) prevents DoS
    • ✅ Scheme validation (http/https only)
    • ✅ HTTPS enforcement in production
    • ✅ HTTP only for localhost in dev
    • ✅ Proper production detection via deployment_mode or base_url

Critical Security Note:
Line 138: is_production = settings.deployment_mode.value == "saas" or settings.base_url is not None

This is clever but could be more explicit. Consider:

# More explicit production check
is_production = settings.deployment_mode == DeploymentMode.SAAS or (
    settings.base_url is not None and not settings.base_url.startswith("http://localhost")
)

Bugs: None identified

Style: ✅ Excellent defensive coding


src/polar_flow_server/api/keys.py (lines 231-309)

Changes:

  • Added callback URL validation before OAuth flow
  • Added client_id length validation
  • Integrated bounded cache
  • Improved error handling with callback redirects

Security - EXCELLENT:

  1. ✅ Validates callback_url BEFORE initiating OAuth (line 235)
  2. ✅ Validates client_id length (line 240)
  3. ✅ Graceful error handling - redirects to callback with error params

Bugs: None

Style: ✅ Clean error handling flow


src/polar_flow_server/app.py (lines 101-156)

Changes:

  • Added session security flags based on debug mode
  • Enhanced CSRF exclusions
  • Updated exclusions for /api/v1 prefix

Security - EXCELLENT:

  1. Session Config (lines 105-112):

    • secure=not is_debug - HTTPS-only in production
    • httponly=True - Prevents XSS access
    • samesite="lax" - CSRF protection
  2. CSRF Config (lines 118-134):

    • ✅ Excludes entry points (login, setup)
    • ✅ Excludes external OAuth callbacks
    • ✅ Excludes health check
    • Critically excludes /api/v1/users/ for API key auth
    • ✅ Good comments explaining each exclusion

Potential Issue:
Line 148: /admin/settings was REMOVED from CSRF exclusions in the diff, but I don't see it in the new code. This is CORRECT - settings pages SHOULD have CSRF protection. Just confirming this was intentional. ✅

Bugs: None

Style: ✅ Excellent comments, clear reasoning


src/polar_flow_server/api/init.py

Changes:

  • Introduced API versioning structure
  • Created api_v1_router with /api/v1 prefix
  • Health and OAuth remain at root level

Security: ✅ Clean separation of versioned vs unversioned endpoints

Design:

  • ✅ Health check at root (common convention)
  • ✅ OAuth at root (external URLs shouldn't change)
  • ✅ All user data under /api/v1

Bugs: None

Style: ✅ Excellent comments explaining routing structure

Improvement: Consider adding a root redirect or API docs at /api:

@get("/api")
async def api_index() -> dict[str, Any]:
    return {"versions": ["v1"], "current": "v1", "docs": "/api/v1/docs"}

src/polar_flow_server/templates/base.html

Changes:

  • Added SRI integrity hashes for HTMX and Chart.js
  • Added crossorigin attributes
  • Added comment explaining Tailwind CDN limitation

Security - EXCELLENT:

  1. ✅ SRI for HTMX 2.0.4
  2. ✅ SRI for Chart.js 4.4.0
  3. ✅ SRI for chartjs-adapter-date-fns 3.0.0
  4. crossorigin="anonymous" required for SRI
  5. ✅ Honest comment about Tailwind not supporting SRI

Verification Needed:
Let me verify the SRI hashes are correct (this is critical):

⚠️ IMPORTANT: I cannot verify if these SRI hashes are correct without fetching the actual CDN resources. The PR author should verify these hashes match the actual files:

# Verify HTMX
curl -s https://unpkg.com/htmx.org@2.0.4 | openssl dgst -sha384 -binary | openssl base64 -A

# Verify Chart.js
curl -s https://cdn.jsdelivr.net/npm/chart.js@4.4.0 | openssl dgst -sha384 -binary | openssl base64 -A

# Verify chartjs-adapter
curl -s https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns@3.0.0 | openssl dgst -sha384 -binary | openssl base64 -A

Bugs: None (assuming hashes are correct)

Style: ✅ Clean, well-commented


README.md

Changes:

  • Updated all API endpoint examples to use /api/v1 prefix
  • Added note that health check requires no auth

Bugs: None

Style: ✅ Consistent, clear


Overall Assessment

Security Improvements: 🟢 OUTSTANDING

  1. Memory Exhaustion Prevention: Bounded caches with maxsize limits
  2. Brute Force Protection: Login rate limiting with IP-based lockouts
  3. CSRF Protection: Enhanced configuration with proper exclusions
  4. Session Security: Secure, HttpOnly, SameSite flags
  5. Callback URL Validation: Length limits, scheme validation, HTTPS enforcement
  6. IP Spoofing Prevention: Trusted proxy validation
  7. SRI Integrity: CDN resource integrity checks
  8. Thread Safety: Proper asyncio locking throughout

Code Quality: 🟢 EXCELLENT

  1. ✅ Clean class design with single responsibility
  2. ✅ Comprehensive documentation
  3. ✅ Proper error handling
  4. ✅ Defensive programming throughout

Breaking Changes: ⚠️ YES (Expected)

  • All API endpoints now require /api/v1 prefix
  • README updated with migration path
  • This is a planned breaking change for versioning

Recommendations

Critical (Must Address):

None - all critical security concerns are properly addressed.

High Priority:

  1. Verify SRI Hashes (see commands above) - Critical for SRI to work
  2. Consider Config-Driven Rate Limits:
    # In config.py
    login_max_attempts: int = Field(default=5)
    login_lockout_minutes: int = Field(default=15)
    oauth_state_cache_size: int = Field(default=100)

Nice to Have:

  1. Add API version index endpoint at /api
  2. Add migration guide for existing users about /api/v1 prefix
  3. Consider adding metrics/logging for rate limit hits
  4. Add integration tests for rate limiting and lockout behavior

Verdict: ✅ APPROVE

This is an exceptionally well-executed security hardening PR. The code demonstrates:

  • Deep understanding of security threats (memory exhaustion, brute force, IP spoofing, CSRF)
  • Proper mitigation techniques (bounded caches, rate limiting, SRI, secure cookies)
  • Clean, maintainable implementation with excellent documentation
  • Thread-safe async patterns throughout

The only blocking item is verifying the SRI hashes are correct. Once verified, this should be merged immediately.

Excellent work! 🎉

@StuMason
Copy link
Owner Author

SRI Hash Verification ✅

All three SRI hashes have been verified:

HTMX 2.0.4:
  Generated: HGfztofotfshcF7+8n44JQL2oJmowVChPTg48S+jvZoztPfvwD79OC/LTtG6dMp+
  In file:   HGfztofotfshcF7+8n44JQL2oJmowVChPTg48S+jvZoztPfvwD79OC/LTtG6dMp+
  ✅ MATCH

Chart.js 4.4.0:
  Generated: e6nUZLBkQ86NJ6TVVKAeSaK8jWa3NhkYWZFomE39AvDbQWeie9PlQqM3pmYW5d1g
  In file:   e6nUZLBkQ86NJ6TVVKAeSaK8jWa3NhkYWZFomE39AvDbQWeie9PlQqM3pmYW5d1g
  ✅ MATCH

chartjs-adapter-date-fns 3.0.0:
  Generated: cVMg8E3QFwTvGCDuK+ET4PD341jF3W8nO1auiXfuZNQkzbUUiBGLsIQUE+b1mxws
  In file:   cVMg8E3QFwTvGCDuK+ET4PD341jF3W8nO1auiXfuZNQkzbUUiBGLsIQUE+b1mxws
  ✅ MATCH

Verification command used:

curl -sL "https://unpkg.com/htmx.org@2.0.4" | openssl dgst -sha384 -binary | openssl base64 -A

Ready to merge! 🚀

@StuMason StuMason merged commit bff0e21 into main Jan 18, 2026
5 checks passed
@StuMason StuMason deleted the feature/security-fixes-and-api-v1 branch January 18, 2026 06:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant