• v1.0.0 e69e419721

    v1.0.0 — Security hardening (pentest response)
    Deploy GitHub Pages / deploy (push) Canceled after 0s
    Stable

    ahmed released this 2026-10-09 17:01:26 +03:00 | 14 commits to main since this release

    Security response to the anwarpy.ai pentest report (2026-10-09). No site flow/UX changes.

    Fixed

    F1 — Single-box DoS froze pages (High)

    Traefik edge caps on the page router (web/docker-compose.vps.yml):

    • pagelimit: 60 req/min average, burst 120 per IP
    • connlimit: 50 in-flight requests per IP
    • apilimit (API router) untouched

    Verified: 200-request flood at concurrency 20 → 127×200, 73×429, zero timeouts, zero 5xx; immediate recovery 200 in 0.69s.

    F2 — Unlimited anonymous signup/login (Medium)

    New web/src/middleware.ts — per-IP in-memory rate limits:

    • POST /api/users (signup): 5/hour
    • POST /api/users/login: 10/15min
    • POST /api/users/forgot-password: 5/hour

    Over limit → 429 JSON محاولات كثيرة جدًا. حاول بعد قليل. + Retry-After. Auth pages already fall back to a generic error on non-ok responses — no UI change. In-memory buckets are correct for the single-container deployment (file comment notes a shared store is needed if scaling horizontally).

    Verified: local signup 5×201 → 6th 429; login 10×401 → 11th 429; prod signup 5×201 → 6th 429.

    F4 — No HSTS (Low)

    Strict-Transport-Security: max-age=63072000; includeSubDomains on all routes (pages, static assets, /api) via web/next.config.mjs. No preload flag (not submitted to the preload list).

    Verified: header present in prod on / and an /api path.

    F7 — Login-timing user enumeration (Low)

    web/src/collections/Users.ts: when no user matches the login email, a dummy PBKDF2 (600000 iterations / 32 bytes / sha256 — identical cost to Payload's real verify) runs before the standard invalid-credentials error, closing the response-time enumeration vector. Admin-unlock branch unchanged.

    Verified: wrong-password login real email ≈107ms vs ghost ≈86ms (Δ ≈ 21ms, was ~300ms); raw pbkdf2@600k = 47ms confirms the dummy hash executes.

    Housekeeping

    • Pentester throwaway accounts deleted from prod DB (ids 42–48: kali.recon999@…, token.probe@…, 5× rl*@recon.example) — verified count 0.

    Verified, no change needed

    • F3 client-writable verificationToken: existing beforeChange untrusted branch already strips it — proven on the new build (response contains no token; role forced free; emailVerified forced false; stripeCustomerId null).
    • F5 CUPS port 631: owner decision — leave open.
    • F6 Stripe webhook: correct pending behavior (503 until configured; stripe-signature verified once keys are set).
    • F8 /api/media readable: required for anonymous <img> cover rendering.
    • F9 /admin: auth-gated, robots-disallowed.

    Deployment

    • Merged via PR #1; deployed to the VPS with docker compose -f docker-compose.vps.yml up -d --build.
    • Flow regression in prod: /, /articles, post page, /api/posts/first-arabic-agent all 200; temp-user login 200 + /api/users/me 200; all test rows deleted from dev and prod DBs.
    • Auth hashing remains PBKDF2-SHA256 600k iterations (Payload 3.90 + Postgres behind Traefik).
    Downloads