Skip to content

feat: wave 1 — remote attach + security baseline - #19

Merged
wra-sol merged 1 commit into
mainfrom
feat/wave-1-attach-and-security-baseline
Jun 29, 2026
Merged

feat: wave 1 — remote attach + security baseline#19
wra-sol merged 1 commit into
mainfrom
feat/wave-1-attach-and-security-baseline

Conversation

@wra-sol

@wra-sol wra-sol commented Jun 29, 2026

Copy link
Copy Markdown
Owner

Summary

Wave 1 of the opencode manager improvement plan. Four slices, one PR:

1.1 — Accept Basic auth at the manager edge (closes #2)

The manager's auth gate now accepts Authorization: Basic opencode:<password> in addition to the oc_session cookie. This unblocks the documented opencode attach https://<app> -p <password> terminal-reconnect feature, which was broken because the manager only accepted cookies and redirected Basic-auth clients to an HTML login form the TUI can't render.

Also adds ?auth_token=<password> query-param fallback (matching the opencode server's own feature) and strips it before proxying so the credential doesn't leak to the child's logs.

Non-browser clients (Basic auth / auth_token) now get a clean 401 Unauthorized instead of a 302 to an HTML form.

1.2 — Security headers on all responses (closes #3)

  • Content-Security-Policy on manager HTML pages (frame-ancestors 'none', style-src 'unsafe-inline' for the inline CSS)
  • X-Frame-Options: DENY — clickjacking protection
  • X-Content-Type-Options: nosniff — MIME sniffing protection
  • Referrer-Policy: same-origin
  • Strict-Transport-Security — only when X-Forwarded-Proto: https (reuses the existing proto detection)
  • Proxied opencode responses get the safe subset (no CSP imposed on the opencode UI, but still can't be framed)

1.3 — CSRF tokens on all state-changing POSTs (closes #4)

  • Per-session token: hmac_sha256(session_secret, "csrf") — stable across requests, rotates on password change
  • Double-submit nonce cookie for login / first-run (no session yet) — prevents login CSRF
  • All state-changing POSTs verified: /manage/login, /manage/logout, /manage/restart, /manage/revalidate, /manage/keys/rotate, /setup, /test-key, /test-github, /test-mcp
  • Proxied opencode API POSTs are NOT checked (they have their own auth via the session gate; opencode attach only does GETs to the proxied API)
  • JavaScript fetch calls updated to include the CSRF token from the form
  • SameSite=Strict cookie stays as defense-in-depth

1.4 — Fix rate limiter + login lockout (closes #5)

Three fixes:

  1. Primary key is socket peer address, not X-Forwarded-For. The old code trusted XFF unconditionally — an attacker could spoof a new IP per request for unlimited login attempts. New TRUSTED_XFF_HOPS env var (default 1 for Railway's single proxy hop) controls how many XFF hops to trust.
  2. All POST routes covered (was only 6, now includes /manage/restart, /manage/revalidate, /manage/keys/rotate). GET routes also rate-limited at 120/60s (health exempt). Separate GET/POST buckets so heavy GET traffic doesn't exhaust the POST budget.
  3. Exponential login lockout after 5 consecutive failures: 60s → 300s → 900s. Successful login resets the counter.

Tests

28 new test cases covering all four slices:

  • Basic auth correct/wrong-username/wrong-password, auth_token query param, cookie+Basic coexistence, auth_token stripping
  • Security headers on login/dashboard/proxy, HSTS only on HTTPS
  • CSRF missing/wrong/correct on restart, login nonce flow, first-run setup without CSRF → 403, token stability
  • Login lockout after 5 failures, lockout resets on success, POST rate limiting on management routes

All 108 tests pass. ruff check clean.

Configuration

New optional env var:

  • TRUSTED_XFF_HOPS (default 1) — number of X-Forwarded-For hops to trust for client IP extraction. Set to 0 to ignore XFF entirely (use socket peer only).

@wra-sol wra-sol added enhancement New feature or request wave-1 Wave 1: remote attach + security baseline security Security hardening labels Jun 29, 2026
Wave 1.1 — Accept Basic auth at the manager edge (closes #2)
- _authenticated() now accepts Authorization: Basic opencode:<password>
  in addition to the oc_session cookie, unblocking
- ?auth_token=<password> query param fallback for headerless clients
- Non-browser clients (Basic auth / auth_token) get 401 instead of 302
  to an HTML login form they can't render
- auth_token stripped from query before proxying to the child

Wave 1.2 — Security headers on all responses (closes #3)
- CSP (frame-ancestors 'none', style-src 'unsafe-inline') on manager HTML
- X-Frame-Options: DENY, X-Content-Type-Options: nosniff, Referrer-Policy
- HSTS only when X-Forwarded-Proto: https
- Proxied responses get the safe subset (no CSP on opencode UI)

Wave 1.3 — CSRF tokens on all state-changing POSTs (closes #4)
- Per-session token: hmac_sha256(session_secret, 'csrf')
- Double-submit nonce cookie for login / first-run (no session yet)
- All state-changing POSTs verified: login, logout, restart, revalidate,
  rotate, setup, test-key, test-github, test-mcp
- Proxied opencode API POSTs NOT checked (own auth via session gate)
- JS fetch calls updated to include csrf_token from the form

Wave 1.4 — Fix rate limiter + login lockout (closes #5)
- Primary key is socket peer address, not X-Forwarded-For
- TRUSTED_XFF_HOPS env var (default 1) controls XFF trust depth
- Separate GET/POST buckets so GET traffic doesn't exhaust POST budget
- All POST routes covered (was only 6, now includes restart/rotate/etc.)
- GET routes rate-limited at 120/60s (health exempt)
- Exponential login lockout after 5 failures: 60s → 300s → 900s
- Successful login resets the lockout counter

Tests: 28 new test cases covering all four slices. 108 total, all passing.
Lint: ruff clean.
@wra-sol
wra-sol force-pushed the feat/wave-1-attach-and-security-baseline branch from c92bcf3 to 6c8657e Compare June 29, 2026 02:45
@wra-sol
wra-sol merged commit 903f7b4 into main Jun 29, 2026
8 checks passed
@wra-sol
wra-sol deleted the feat/wave-1-attach-and-security-baseline branch June 29, 2026 02:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request security Security hardening wave-1 Wave 1: remote attach + security baseline

Projects

None yet

1 participant