Skip to content

feat(self-host): local console sign-in without Cloudflare Access - #106

Closed
b3nw wants to merge 2 commits into
openclaw:mainfrom
b3nw:feat/local-auth
Closed

feat(self-host): local console sign-in without Cloudflare Access#106
b3nw wants to merge 2 commits into
openclaw:mainfrom
b3nw:feat/local-auth

Conversation

@b3nw

@b3nw b3nw commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Summary

Local console sign-in for the Docker self-host profile — the token-based login #104 anticipated ("could follow if demand materializes"; closes #105). Two focused commits, no new secrets:

  • worker/local-auth.tsPOST /v1/session/login verifies the raw admin token against the existing CLAWROUTER_ADMIN_TOKEN_SHA256 digest and mints a 256-bit opaque session token, stored SHA-256-hashed in POLICY_KV with a 12-hour TTL and delivered as an HttpOnly; SameSite=Lax cookie (Secure on https origins and behind x-forwarded-proto: https proxies). POST /v1/session/logout revokes. Failed sign-ins are capped per client and globally (client addresses are spoofable on a bare workerd host). The sign-in identity (CLAWROUTER_LOCAL_ADMIN_EMAIL, default admin@local, validated at container startup) auto-provisions a user record, so policy bindings, usage attribution, and feat(access): self-service maintainer keys; fix(proxy): pre-stream upstream error normalization #103's self-service maintainer keys attach to a real principal — and sessions honor live record state: disabling, deleting, or demoting the user revokes/downgrades on the next request.
  • verifiedAccessSession dispatches Cloudflare Access first, then the local cookie — every session consumer (dashboard shell, playground, /v1/session/*, admin CSRF) works unchanged. The dashboard shell serves unauthenticated in local mode so the SPA can present sign-in; every API behind it stays session-gated.
  • Fail-closed both ways, and opt-in: nothing changes until the operator sets CLAWROUTER_LOCAL_AUTH=enabled (validated at container startup; without it the profile keeps the 0.1.0 API-only posture), and local auth is refused whenever CLAWROUTER_ACCESS_* is configured, so a stray flag cannot open a login form on a managed deployment.
  • Admin console: when the session bootstrap fails with access_session_required and /v1 advertises sessionLogin (only emitted in local mode), the SPA renders a sign-in form instead of the localhost demo fallback; success re-runs the normal refresh. The cookie is never visible to page JS.
  • The bearer-token admin fallback session is now labeled admin_token instead of the incorrect cloudflare_access (visible only in session echoes).

Verification

  • worker:check (127 tests + typecheck; 10 new local-auth tests: cookie/session lifecycle, disabled/deleted/demoted-user revocation, expiry, CSRF, per-client and spoofed-address rate limiting, forwarded-proto Secure flag, Access-configured refusal), admin check/test/build (26 tests), scripts tests incl. new entrypoint local-auth coverage, worker:e2e, and both Playwright projects (screenshot baselines unchanged).
  • Live workerd proof (self-host config): fresh browser at /dashboard/home gets the sign-in card; wrong token → inline login_invalid; correct token → full admin console (admin@local, maintainer-keys panel live); cookie HttpOnly SameSite=Lax; reload keeps the session; logout revokes server-side; 11th bad attempt → 429 including for the correct token; cross-origin login → 403 access_csrf_required; disabled mode → login 404, dashboard 401, previously-minted cookies rejected, /v1 stops advertising login; bearer-token admin API unchanged.
  • Full CI green on fork (self-host Docker smoke, admin incl. browser tests, worker-package): https://github.com/b3nw/clawrouter/actions/runs/29664255255
  • Review: 8-angle structured review (recall-biased, verified findings); accepted findings fixed in-branch — session revocation on user deletion/demotion, a global sign-in cap against spoofed client addresses, x-forwarded-proto Secure-cookie handling, startup validation of the admin email, and session-literal dedup. Per-isolate limiter state and the per-request user resolve (parity with the Access path) documented as accepted.

Deployment status

No production impact. The Cloudflare deployment path is untouched — without CLAWROUTER_LOCAL_AUTH the new module is inert, and it hard-refuses to activate when Access variables exist. Self-host upgrades keep the API-only 0.1.0 posture unchanged; operators opt in with CLAWROUTER_LOCAL_AUTH=enabled.

Remaining risk

  • One shared admin identity (the admin token); per-user passwords over the existing ACCESS_CONTROL user records are the natural phase 2 if wanted.
  • No sign-out button in the shell yet — sessions expire after 12 h and POST /v1/session/logout works today.
  • The login rate limiter is per-isolate memory: right-sized for the single-process self-host profile, not a distributed limiter (and irrelevant on Cloudflare where local auth cannot activate).
  • Browser OAuth and GitHub auto-provisioning remain unavailable in self-host, as documented.

Live proof

Redacted transcripts from the self-host profile on local workerd (wrangler dev --local with the rendered self-host config, exactly as the container runs it). Admin token value redacted; the session cookie value is redacted 64-hex.

Opt-in enabled (CLAWROUTER_LOCAL_AUTH=enabled) — sign-in lifecycle, protected APIs, logout, brute-force throttle
$ curl -s $BASE/v1/health
{"ok":true,"service":"clawrouter-edge","runtime":"typescript","environment":"production","observability":{"mode":"metadata_only","requestContentRetentionDefault":true}}
$ curl -si -X POST $BASE/v1/session/login -H "content-type: application/json" --data "{\"token\": \"wrong-token\"}"  # invalid token
HTTP/1.1 401 Unauthorized
{"error":{"code":"login_invalid","message":"invalid sign-in token"}}

$ curl -si -X POST $BASE/v1/session/login -H "content-type: application/json" --data "{\"token\": \"$ADMIN_TOKEN\"}"  # correct token (value redacted)
HTTP/1.1 200 OK
Set-Cookie: clawrouter_session=<redacted-64-hex>; Max-Age=43200; Path=/; HttpOnly; SameSite=Lax
{"ok":true,"session":{"authenticated":true,"auth":"local","role":"admin","email":"admin@local","subject":null,"tenantId":"default","groups":[]}}

$ curl -s -b cookies.txt -o /dev/null -w "HTTP %{http_code}" $BASE/v1/session  # session resolves
HTTP 200

$ curl -s -b cookies.txt $BASE/v1/admin/overview  # admin API via session cookie
{"policiesTotal":0,"policiesActive":0,"keysTotal":0,"keysActive":0,"tenantsTotal":0,"providerCount":21,"openaiCompatibleProviders":13,"manifestRoutes":32,"monthlyBudgetMicros":0,"requestCostMicros":0}
HTTP 200

$ curl -s -b cookies.txt $BASE/v1/session/credentials  # maintainer keys endpoint
{"credentials":[]}
HTTP 200

$ curl -si -b cookies.txt -X POST $BASE/v1/session/logout
HTTP/1.1 200 OK
Set-Cookie: clawrouter_session=; Max-Age=0; Path=/; HttpOnly; SameSite=Lax
{"ok":true}

$ curl -s -b cookies.txt -o /dev/null -w "HTTP %{http_code}" $BASE/v1/session  # revoked cookie rejected
HTTP 401

$ for i in $(seq 1 11); do ... --data "{\"token\": \"bad\"}"; done  # brute-force throttle (10/min cap)
401 401 401 401 401 401 401 401 429 429 429 
Browser drive — headless Chromium against /dashboard/home
== browser drive (Playwright, headless Chromium) ==
STEP login-screen: login card rendered on /dashboard/home
STEP wrong-token: error shown: invalid admin token
STEP signed-in: sidebar identity: admin@local
STEP cookie: clawrouter_session httpOnly=true sameSite=Lax
STEP reload: still signed in as admin@local
STEP admin-nav: admin navigation visible
DONE
Disabled (the default) — 0.1.0 posture retained
$ # container restarted with CLAWROUTER_LOCAL_AUTH=disabled

$ curl -s -X POST -H "content-type: application/json" --data "{\"token\": \"$ADMIN_TOKEN\"}" -w "\nHTTP %{http_code}" $BASE/v1/session/login  # login route absent
{"error":{"code":"route_not_found","message":"route not found"}}
HTTP 404

$ curl -s -o /dev/null -w "HTTP %{http_code}" $BASE/dashboard/home  # dashboard fail-closed (0.1.0 behavior)
HTTP 401

$ curl -s -b cookies.txt -o /dev/null -w "HTTP %{http_code}" $BASE/v1/session  # previously-minted cookie rejected
HTTP 401

$ curl -s -H "authorization: Bearer $ADMIN_TOKEN" -o /dev/null -w "HTTP %{http_code}" $BASE/v1/admin/overview  # bearer admin API unchanged
HTTP 200

$ curl -s $BASE/v1 | grep -c sessionLogin  # login not advertised
0

@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal priority bug or improvement with limited blast radius. merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. merge-risk: 🚨 security-boundary 🚨 Merging this PR could weaken sandboxing, authorization, credentials, or sensitive data. labels Jul 18, 2026
@clawsweeper

clawsweeper Bot commented Jul 18, 2026

Copy link
Copy Markdown

Codex review: needs real behavior proof before merge. Reviewed August 2, 2026, 7:10 PM ET / 23:10 UTC.

ClawSweeper review

What this changes

The branch adds opt-in local admin-token browser sessions for the Docker self-host console, including a login screen, revocable hashed cookies, and local session support for existing admin APIs.

Merge readiness

Blocked until stronger real behavior proof is added - 7 items remain

Keep this PR open: current main still lacks local self-host console sign-in, but the proposed HTTPS reverse-proxy path has a blocking CSRF-origin defect. The shared check compares the browser’s external HTTPS Origin with the internally forwarded HTTP Worker URL, so legitimate local login and logout return 403.

Priority: P2
Reviewed head: 1e45dd917bed08698818026e867690019ecb1d25
Owner decision: Required. See Decision needed.

Review scores

Measure Result What it means
Overall readiness 🦐 gold shrimp (3/6) The patch is focused and supported by loopback proof, but the confirmed P1 proxy-origin defect blocks merge.
Proof confidence 🦐 gold shrimp (3/6) Needs stronger real behavior proof before merge: The supplied loopback transcripts and browser drive demonstrate local behavior but not the TLS-terminating-proxy path; add redacted post-fix login, authenticated request, and logout diagnostics through that proxy. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
Patch quality 🦐 gold shrimp (3/6) Security review found an item that needs attention.

Verification

Check Result Evidence
Real behavior Needs proof Needs stronger real behavior proof before merge: The supplied loopback transcripts and browser drive demonstrate local behavior but not the TLS-terminating-proxy path; add redacted post-fix login, authenticated request, and logout diagnostics through that proxy. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
Evidence reviewed 5 items Current main lacks the requested console path: Current self-host documentation says Cloudflare Access is absent and console sign-in is unavailable, so the central user problem has not already landed on main.
Forwarded HTTPS breaks the proposed same-origin check: The branch compares Origin to the internal request URL while separately accepting x-forwarded-proto for Secure cookies. A browser served at external HTTPS but forwarded to an internal HTTP Worker therefore receives access_session_required CSRF rejection from login or logout.
The test covers only the cookie attribute, not proxied origin validation: The TLS-terminating-proxy test sets x-forwarded-proto and asserts Secure, but keeps the HTTP request URL and Origin aligned, so it cannot expose the external HTTPS Origin failure.
Findings 1 actionable finding [P1] Reconstruct the external HTTPS origin before CSRF checks
Security Needs attention CSRF check uses the internal proxy origin: The external browser Origin is HTTPS while the forwarded Worker request URL can be HTTP, making legitimate login and logout requests fail the shared same-origin check.

How this fits together

The self-host profile runs ClawRouter’s Worker without Cloudflare Access and currently relies on an admin bearer token for management. This PR adds a browser session layer so the dashboard and existing session-protected APIs can work for an explicitly enabled local deployment.

flowchart LR
  Operator[Self-host operator] --> Dashboard[Dashboard sign-in form]
  Dashboard --> Login[Local login endpoint]
  Login --> TokenCheck[Admin token verification]
  TokenCheck --> SessionStore[Hashed session in policy storage]
  SessionStore --> SessionCheck[Session resolution]
  SessionCheck --> AdminAPIs[Dashboard and admin APIs]
Loading

Decision needed

Question Recommendation
Should ClawRouter support opt-in local admin-token browser sessions as a first-party self-host authentication mode once the proxy-origin defect is repaired? Sponsor opt-in local sessions: Accept the API-only default plus explicit enablement, subject to repairing and proving the HTTPS reverse-proxy path.

Why: The implementation adds a durable authentication mode and environment configuration beyond the v0.1.0 API-only self-host posture; the prior self-host discussion anticipated this possibility but does not establish explicit maintainer acceptance.

Before merge

  • Add real behavior proof - Needs stronger real behavior proof before merge: The supplied loopback transcripts and browser drive demonstrate local behavior but not the TLS-terminating-proxy path; add redacted post-fix login, authenticated request, and logout diagnostics through that proxy. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
  • Reconstruct the external HTTPS origin before CSRF checks (P1) - sameOrigin compares the browser Origin with request.url, but a TLS-terminating proxy forwards an external HTTPS page to an internal HTTP Worker. The new login and logout handlers call this helper, so a legitimate proxied console gets access_csrf_required; derive the externally visible origin while keeping mismatched origins blocked.
  • Resolve security concern: CSRF check uses the internal proxy origin - The external browser Origin is HTTPS while the forwarded Worker request URL can be HTTP, making legitimate login and logout requests fail the shared same-origin check.
  • Resolve merge risk (P1) - A TLS-terminating reverse proxy makes the external browser Origin HTTPS while the Worker request URL is HTTP, causing local login and logout to fail even though the cookie is marked Secure.
  • Resolve merge risk (P1) - The feature introduces a deployment-wide admin-token browser-authentication boundary; forwarded-origin reconstruction must preserve rejection of attacker-controlled cross-origin requests.
  • Resolve merge risk (P1) - Even after the mechanical repair, accepting a first-party local browser-session mode is a product-direction choice because it adds a new supported auth mode and configuration surface.

Findings

  • [P1] Reconstruct the external HTTPS origin before CSRF checks — worker/access.ts:109-113
  • [medium] CSRF check uses the internal proxy origin — worker/access.ts:109
Agent review details

Security

Needs attention: The new local-session boundary fails closed but incorrectly rejects the intended HTTPS reverse-proxy request path.

Review metrics

Metric Value Why it matters
Patch surface 15 files changed; 608 added, 15 removed The feature crosses Worker authentication, self-host startup configuration, dashboard UI, documentation, and focused tests.

Root-cause cluster

Relationship: fixed_by_candidate
Canonical: #105
Summary: This PR is the open candidate implementation for the self-host local-console-sign-in request.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Merge-risk options

Maintainer options:

  1. Repair proxy origin validation (recommended)
    Derive the externally visible HTTPS origin for same-origin checks, cover login and logout through the proxy, and retain explicit mismatched-Origin rejection.
  2. Pause pending direction
    Do not merge the local console feature until maintainers accept this authentication mode and the documented reverse-proxy path is proven.
Copy recommended automerge instruction
@clawsweeper automerge

Special instructions:
Preserve cross-origin rejection while supporting a TLS-terminating self-host proxy; add focused login and logout regression tests and redacted proxy proof.

Technical review

Best possible solution:

Preserve the opt-in default, repair same-origin validation for a trusted TLS-terminating proxy, add login/logout regression coverage including malicious-Origin rejection, and land the feature only if maintainers sponsor local admin-token sessions as a first-party self-host capability.

Do we have a high-confidence way to reproduce the issue?

Yes. Send an external HTTPS Origin with x-forwarded-proto set to https to an internally HTTP Worker request; the proposed same-origin comparison returns access_session_required before local login or logout.

Is this the best way to solve the issue?

No. The opt-in local-session approach is bounded, but it must reconstruct the externally visible proxy origin before it can safely support its documented deployment path.

Full review comments:

  • [P1] Reconstruct the external HTTPS origin before CSRF checks — worker/access.ts:109-113
    sameOrigin compares the browser Origin with request.url, but a TLS-terminating proxy forwards an external HTTPS page to an internal HTTP Worker. The new login and logout handlers call this helper, so a legitimate proxied console gets access_csrf_required; derive the externally visible origin while keeping mismatched origins blocked.
    Confidence: 0.98

Overall correctness: patch is incorrect
Overall confidence: 0.98

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against c4002ddb154e.

Labels

Label justifications:

  • P2: This is a useful self-host feature with a bounded but concrete operator-facing failure.
  • merge-risk: 🚨 availability: The claimed TLS-terminating-proxy path cannot complete legitimate local login or logout.
  • merge-risk: 🚨 security-boundary: The patch adds browser sessions and CSRF origin validation around a deployment-wide administrator token.
  • rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🦐 gold shrimp and patch quality is 🦐 gold shrimp.
  • status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs stronger real behavior proof before merge: The supplied loopback transcripts and browser drive demonstrate local behavior but not the TLS-terminating-proxy path; add redacted post-fix login, authenticated request, and logout diagnostics through that proxy. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.

Evidence

Security concerns:

  • [medium] CSRF check uses the internal proxy origin — worker/access.ts:109
    The external browser Origin is HTTPS while the forwarded Worker request URL can be HTTP, making legitimate login and logout requests fail the shared same-origin check.
    Confidence: 0.98

Acceptance criteria:

  • [P1] pnpm worker:check.
  • [P1] Redacted TLS-terminating-proxy transcript showing successful login, authenticated /v1/session, successful logout, and a mismatched-Origin 403.

What I checked:

  • Current main lacks the requested console path: Current self-host documentation says Cloudflare Access is absent and console sign-in is unavailable, so the central user problem has not already landed on main. (docs/self-hosting.md:101, c4002ddb154e)
  • Forwarded HTTPS breaks the proposed same-origin check: The branch compares Origin to the internal request URL while separately accepting x-forwarded-proto for Secure cookies. A browser served at external HTTPS but forwarded to an internal HTTP Worker therefore receives access_session_required CSRF rejection from login or logout. (worker/access.ts:109, 1e45dd917bed)
  • The test covers only the cookie attribute, not proxied origin validation: The TLS-terminating-proxy test sets x-forwarded-proto and asserts Secure, but keeps the HTTP request URL and Origin aligned, so it cannot expose the external HTTPS Origin failure. (worker/test/local-auth.test.mjs:125, 1e45dd917bed)
  • Shared CSRF helper provenance: The existing same-origin helper predates this feature and was attributed by blame to the v0.1.0 release tree; this PR newly routes local login and logout through it without adapting it for the claimed proxy behavior. (worker/access.ts:104, 13845ee2bdc0)
  • Release and main-only check: The PR head is not contained in any local release tag or current main; the latest release v0.1.0 points to a pre-feature commit, so this work is neither shipped nor implemented on main. (worker/local-auth.ts:1, 1e45dd917bed)

Likely related people:

  • steipete: Git blame attributes the existing same-origin helper used by the new session endpoints to the v0.1.0 release tree; related self-host work was also authored under this handle. (role: introduced the shared CSRF helper; confidence: medium; commits: 13845ee2bdc0; files: worker/access.ts, worker/index.ts, docs/self-hosting.md)

Rank-up moves

Optional improvements that raise the rating; they are not merge blockers.

  • Repair forwarded-origin CSRF validation with focused login and logout coverage.
  • Add redacted real proxy proof after the fix; do not include tokens, IP addresses, or private endpoints.

Rating scale

Score Internal tier Crab rank Meaning
6/6 S 🦀 challenger crab Exceptional readiness
5/6 A 🦞 diamond lobster Very strong readiness
4/6 B 🐚 platinum hermit Good normal PR; ordinary maintainer review
3/6 C 🦐 gold shrimp Useful, but confidence is limited
2/6 D 🦪 silver shellfish Proof or implementation needs work
1/6 F 🧂 unranked krab Not merge-ready
N/A NA 🌊 off-meta tidepool Rating does not apply

Overall follows the weaker of proof and patch quality.
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

Workflow

  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

History

Review history (31 earlier review cycles; latest 8 shown)
  • reviewed 2026-08-01T22:36:33.703Z sha 1e45dd9 :: needs real behavior proof before merge. :: [P1] Honor forwarded HTTPS in local-login CSRF validation
  • reviewed 2026-08-02T01:43:23.805Z sha 1e45dd9 :: needs real behavior proof before merge. :: [P1] Honor forwarded HTTPS in local-login CSRF validation
  • reviewed 2026-08-02T07:46:44.110Z sha 1e45dd9 :: needs real behavior proof before merge. :: [P1] Honor forwarded HTTPS in CSRF validation
  • reviewed 2026-08-02T12:47:27.529Z sha 1e45dd9 :: needs real behavior proof before merge. :: [P1] Honor forwarded HTTPS in CSRF validation
  • reviewed 2026-08-02T15:10:48.255Z sha 1e45dd9 :: needs real behavior proof before merge. :: [P1] Honor forwarded HTTPS in local-login CSRF validation
  • reviewed 2026-08-02T16:20:16.344Z sha 1e45dd9 :: needs real behavior proof before merge. :: [P1] Honor forwarded HTTPS in same-origin checks
  • reviewed 2026-08-02T17:33:55.363Z sha 1e45dd9 :: needs real behavior proof before merge. :: [P1] Honor forwarded HTTPS in local-session CSRF validation
  • reviewed 2026-08-02T21:05:07.229Z sha 1e45dd9 :: needs real behavior proof before merge. :: [P1] Reconstruct the external HTTPS origin before CSRF checks

@b3nw
b3nw force-pushed the feat/local-auth branch from 6be02bf to b73aa09 Compare July 18, 2026 22:45
@b3nw

b3nw commented Jul 18, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the review:

  • CHANGELOG.md edit removed — release context stays in the PR body per repo policy.
  • Upgrade posture documenteddocs/self-hosting.md now states explicitly that upgrading past 0.1.0 enables console sign-in by default (dashboard shell + /v1/session/login become reachable, login still requires the admin token, every API behind the shell stays session-gated) and that CLAWROUTER_LOCAL_AUTH=disabled before restart retains the 0.1.0 API-only posture.
  • Live proof — redacted workerd transcripts (sign-in lifecycle, protected API access, logout revocation, brute-force throttle, disabled-mode rejection) and the headless-browser drive log are being added to the PR body.

On the maintainer decision (default-on vs opt-in): both modes are implemented and covered by tests, and the entrypoint validates the flag at startup — flipping the default is a one-line change in deploy/self-host/entrypoint.mjs ("enabled""disabled" in localAuthMode).

@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. labels Jul 18, 2026
b3nw added 2 commits July 18, 2026 22:53
…e Access

Add an opt-in local-auth mode for the Docker/workerd profile: POST
/v1/session/login exchanges the admin token for a rate-limited, KV-backed
12-hour session cookie (HttpOnly, SameSite=Lax) that verifiedAccessSession
resolves alongside Cloudflare Access. Sessions honor live user-record state
(disable, delete, or demote revokes); sign-in failures are capped per client
and globally since client addresses are spoofable on a bare workerd host; the
Secure cookie flag follows x-forwarded-proto for TLS-terminating proxies. The
dashboard shell serves unauthenticated in local mode; logout revokes the
stored session. Off by default: the self-host entrypoint validates
CLAWROUTER_LOCAL_AUTH at startup and only activates on an explicit
"enabled", and the Worker refuses local auth whenever Cloudflare Access
variables are configured. The bearer-token admin fallback session is now
labeled admin_token instead of cloudflare_access.
When the session bootstrap fails with access_session_required and the
service index advertises /v1/session/login, the console renders a sign-in
form instead of falling back to localhost demo data. Successful sign-in
re-runs the normal refresh; the session cookie itself stays HttpOnly and
invisible to the SPA.
@b3nw
b3nw force-pushed the feat/local-auth branch from b73aa09 to 1e45dd9 Compare July 18, 2026 22:53
@b3nw

b3nw commented Jul 18, 2026

Copy link
Copy Markdown
Contributor Author

Resolved the open decision from the contributor side by taking the conservative option: local sign-in is now opt-in (1e45dd9). The self-host entrypoint defaults CLAWROUTER_LOCAL_AUTH to disabled, so upgrades keep the 0.1.0 API-only posture byte-for-byte until an operator sets enabled — which should also clear the merge-risk: compatibility concern. Docs, .env.example, and tests updated to match.

@clawsweeper clawsweeper Bot added proof: sufficient Contributor real behavior proof is sufficient. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Jul 18, 2026
@clawsweeper clawsweeper Bot added status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. and removed status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. proof: sufficient Contributor real behavior proof is sufficient. labels Jul 30, 2026
@clawsweeper clawsweeper Bot added merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. merge-risk: 🚨 availability 🚨 Merging this PR could cause crashes, hangs, restart loops, stalls, or process outages. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. merge-risk: 🚨 security-boundary 🚨 Merging this PR could weaken sandboxing, authorization, credentials, or sensitive data. and removed merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. merge-risk: 🚨 security-boundary 🚨 Merging this PR could weaken sandboxing, authorization, credentials, or sensitive data. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. labels Aug 2, 2026
@steipete

Copy link
Copy Markdown
Contributor

Thanks @b3nw — this is careful work and the shape (opt-in, fail-closed, refused whenever CLAWROUTER_ACCESS_* is set, sessions honoring live record state) is right. A security-focused review found two blockers with live reproductions, so I'm holding it rather than landing:

1. Origin validation fails on the documented secure deployment path (P1). Behind a TLS-terminating reverse proxy, the browser sends an external https Origin while the worker sees an internal http request, so sameOrigin returns false and sign-in breaks — on exactly the remote setup the feature documents. The probe:

{"direct":true,"tlsTerminatingProxy":false,"crossOriginAttacker":false}

Direct works and the malicious cross-origin case is correctly rejected; the supported proxy case is the one that fails. A fix needs an explicit trusted external-origin contract (e.g. validating against the proxy-facing origin via x-forwarded-proto/x-forwarded-host), not a relaxation.

2. Brute-force caps are bypassable with concurrency (P2). The contract advertises 10/client and 50/global, but attempts are counted after awaiting request processing, so parallel requests all slip through:

{"concurrent":{"401":100},"nextSequentialStatus":429}

100 concurrent wrong-token logins all returned 401; only the next sequential attempt saw 429. The reservation needs to happen atomically before the await.

Both paths are green in the current tests, so coverage for them is the other gap: proxy login/logout, malicious-Origin rejection, and a concurrent-limiter test.

Since this adds a new authentication mode, the final call is Peter's — I'm leaving it open with these findings rather than closing it. If you want to push a revision addressing the two items above plus the tests, that would be very welcome.

@steipete

Copy link
Copy Markdown
Contributor

Superseded by #113, which is now merged — thank you @b3nw, your commits 99ae48e and 1e45dd9 are preserved in it with attribution intact, and you're credited in the changelog.

Peter's call was explicit: "we absolutely need a way to self-host this and not use Cloudflare login." So the feature is in. #113 keeps your product boundary exactly as designed (opt-in, refused whenever Cloudflare Access is configured, KV-backed revocable sessions) and adds the hardening the review turned up:

  • CLAWROUTER_PUBLIC_ORIGIN is declared by the operator and validated at startup; the public origin is accepted only when X-Forwarded-Proto + X-Forwarded-Host reconstruct that exact origin — so forwarded headers are never blanket-trusted, and sign-in now works behind a TLS-terminating proxy.
  • Secure is derived only from direct HTTPS or that exact trusted origin.
  • Both per-client and global rate-limit capacity are reserved synchronously before the first await, so the concurrent bypass is closed.
  • The duplicated sameOrigin logic is centralized across login/logout, admin mutations, playground, and credential writes.
  • New tests: proxy login/logout, malicious origin with and without the config, concurrent limiter, forwarded-header spoofing, and fail-closed startup.

Verified live against the real self-host image behind Caddy over TLS: sign-in 200 with Secure; HttpOnly; SameSite=Lax, logout revoking the session, a forged cross-origin login refused 403, and a 100-request burst with distinct spoofed client addresses capped at exactly 50. Shipping in the next patch release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-risk: 🚨 availability 🚨 Merging this PR could cause crashes, hangs, restart loops, stalls, or process outages. merge-risk: 🚨 security-boundary 🚨 Merging this PR could weaken sandboxing, authorization, credentials, or sensitive data. P2 Normal priority bug or improvement with limited blast radius. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Self-host: local console sign-in without Cloudflare Access (follow-up to #95 / #104)

2 participants