Commit Graph

5 Commits

Author SHA1 Message Date
xarmian a2eaac4a37 fix(server): reject CORS wildcard when credentials are on (TASK-664) (#188)
PAD_CORS_ORIGINS accepted any string (including '*') while the CORS
middleware ran with AllowCredentials=true unconditionally. Browsers
refuse the combination per the Fetch spec, so a typo like
PAD_CORS_ORIGINS=* "worked" in curl but failed silently from every
real browser — and without an explicit carve-out, an anon cross-origin
fetch still rode the victim's cookies when origins were empty.

- parseCORSOrigins: explicitly drop '*' with a log warning. When '*'
  was the ONLY configured origin, fall back to localhost defaults
  rather than producing an empty allowlist.
- corsAllowCredentials: new helper — AllowCredentials=true only when
  an operator has set PAD_CORS_ORIGINS. Default false keeps a browser
  on a different origin from piggy-backing cookies on the user's
  session when no remote origin was expected in the first place.
- server.go: wire up corsAllowCredentials(s.corsOrigins) into the
  cors.Options.

Tests:
- TestParseCORSOrigins gains three '*'-handling cases (lone '*',
  mixed, trailing '*').
- TestCorsAllowCredentials covers empty/whitespace default, explicit
  origins, and tab-only input.

Parent: PLAN-643 (OSS Security Hardening).
2026-04-21 23:59:54 -04:00
xarmian 4297689e23 fix(server): add script-src-attr 'none' to CSP (TASK-648) (#171)
Inline event handlers (onerror, onload, onclick, …) bypass the
script-src directive per CSP spec. Without script-src-attr 'none' an
attacker who slips markup past the DOMPurify sanitizer can still
execute JavaScript via event attributes — defeating the whole point of
the nonce-based script-src.

Add 'script-src-attr 'none'' to both CSP headers:
- internal/server/middleware_security.go — strict policy for API responses
- internal/server/server.go — nonce-based policy for HTML pages

Defense-in-depth for TASK-647 (comment markdown sanitizer) and for any
future regression in HTML-emitting paths.

Parent: PLAN-643 (OSS Security Hardening).
2026-04-21 18:37:18 -04:00
xarmian 30fe60d666 feat: session binding, nonce-based CSP, and auth hardening (#75)
* feat: add session binding, nonce-based CSP, and auth hardening

Security hardening for Pad Cloud (PLAN-15 / TASK-171):

- Bind sessions to User-Agent hash; mismatch invalidates session
- Store client IP on session creation for audit trail
- Increase bcrypt cost from 10 to 12
- Upgrade invitation codes to 128-bit entropy with hashed storage
- Replace CSP unsafe-inline with per-request nonce for SvelteKit scripts
- Move SecurityHeaders to main router so SPA gets headers too

* fix: enforce session binding on auth cookie fallbacks and fix invitation code uniqueness

- Add validateSessionCookie() helper that checks UA binding, replacing
  raw ValidateSession() calls in handleSessionCheck, handleGetCurrentUser,
  and handleUpdateCurrentUser that bypassed the new session binding
- Store invitation ID in code column instead of empty string to satisfy
  the NOT NULL UNIQUE constraint (previously broke on second invitation)
- Skip code/join_url in invitation listings for hashed invitations where
  the plaintext is not recoverable
2026-04-08 13:58:58 -04:00
xarmian 313bc48419 fix: allow SvelteKit inline scripts in CSP to prevent white screen
The script-src 'self' CSP directive blocked SvelteKit's inline bootstrap
scripts, causing a white screen on mobile browsers which enforce CSP
strictly. Add 'unsafe-inline' as a temporary fix until nonce-based CSP
is implemented (TASK-163).
2026-04-05 20:10:58 +00:00
xarmian 8aa6481421 PHASE-12: Security Hardening for Pad Cloud (#67)
* feat: enforce RBAC role checks on all mutation endpoints (TASK-150)

Add requireMinRole helper and role enforcement to 30+ mutation handlers.
Viewers are now blocked from all state-changing operations, editors can
mutate items/docs/comments/views but not collections/webhooks/workspace
settings, and only owners can perform administrative operations.

Includes 11 integration tests with real auth covering viewer/editor/owner
access across items, collections, documents, comments, agent roles,
item links, and workspace operations.

* fix: scope search results to user's workspaces (TASK-151)

Search without a ?workspace= param previously returned results from all
workspaces in the database. Now the handler resolves the authenticated
user's workspace memberships and passes their IDs to the store query,
ensuring results only include items from workspaces the user belongs to.

Fresh installs (no users) retain unscoped search for backward compat.
Includes integration test proving cross-workspace isolation.

* fix: add webhook URL validation and SSRF protection (TASK-152)

Webhook creation now validates URLs before accepting them: only HTTP(S)
schemes allowed, embedded credentials rejected, private/reserved IPs
blocked (loopback, RFC1918, link-local, cloud metadata 169.254.169.254),
and hostnames are DNS-resolved to verify they don't point to private IPs.

Defense-in-depth check also added to the dispatcher's deliver function
so existing webhooks with unsafe URLs are blocked at delivery time.

* feat: add CSRF protection with double-submit cookie pattern (TASK-153)

Implements CSRF middleware that validates X-CSRF-Token header matches
the pad_csrf cookie on all state-changing API requests. Bearer token
auth, auth endpoints, and fresh installs are exempt. The frontend
client reads the CSRF cookie and attaches the header on mutations.

* feat: add per-endpoint rate limiting middleware (TASK-154)

Adds IP-based rate limiting for auth endpoints (5/min login, 3/hr
password reset, 5/hr registration) and user-based limits for API
(100/min) and search (30/min). Uses golang.org/x/time/rate with
automatic stale-entry cleanup. Adds chi RealIP middleware for
correct client IP behind proxies. Returns 429 with Retry-After.

* fix: sanitize error responses and remove PII from logs (TASK-155)

Replace all writeError(500, err.Error()) calls with writeInternalError
that logs the real error server-side and returns a generic message to
clients. Remove email addresses, user IDs, and password reset tokens
from log output to prevent PII leakage.

* feat: add security headers, configurable CORS, and secure cookies (TASK-160)

Add SecurityHeaders middleware (CSP, X-Frame-Options, nosniff,
Referrer-Policy, Permissions-Policy). Make CORS origins configurable
via PAD_CORS_ORIGINS env var. Add PAD_SECURE_COOKIES for TLS
deployments (sets Secure flag on session/CSRF cookies and enables
HSTS). Also adds X-CSRF-Token to CORS allowed headers.

* fix: address PR review — lazy router init and trusted IP for rate limits

Fix two issues flagged by Codex:

1. CORS/HSTS config was ignored because setupRouter() ran in New()
   before SetCORSOrigins/SetSecureCookies were called. Now uses
   sync.Once to lazily build the router on first ServeHTTP/Listen.

2. Rate limiter read X-Real-IP directly from untrusted headers,
   allowing clients to spoof IPs. Now uses RemoteAddr only (which
   chimiddleware.RealIP already sanitizes from trusted proxy headers).
2026-04-05 10:26:00 -04:00