Skip to content

fix(api): stop exempting operational data endpoints from auth - #1291

Open
guzzi235 wants to merge 1 commit into
RightNow-AI:mainfrom
guzzi235:fix/harden-public-endpoints
Open

guzzi235 wants to merge 1 commit into
RightNow-AI:mainfrom
guzzi235:fix/harden-public-endpoints

Conversation

@guzzi235

Copy link
Copy Markdown

Summary

The auth middleware exempted a long list of GET endpoints from requiring the api_key — /api/agents, /api/sessions, /api/config, /api/config/schema, /api/channels, /api/hands(/active), /api/skills, /api/integrations(/available|/health), /api/workflows, /api/approvals, /api/budget(/agents), /api/providers, /api/models(/aliases), /api/network/status, /api/a2a/agents, /api/cron/*, /api/uploads/*, and the SSE /api/logs/stream — with the stated rationale "so the SPA can render before the user enters their API key".

In practice this means any unauthenticated visitor who reaches a deployed instance can browse real operational data: session lists, the full config (via /api/config and /api/config/schema, which also leaks provider/channel field names and structure), installed integrations, channel and hand status, agent details, cron jobs, and uploaded files. Only mutating requests (POST/PUT/DELETE) were actually gated — reads were wide open.

I found this by deploying an instance publicly behind a reverse proxy: opening the dashboard URL and dismissing the "API Key Required" modal (without entering anything) still showed real data underneath it.

Fix

Trims the exemption list to what's actually needed for the page shell to load and for the key-entry flow itself to work:

  • Static assets (/, /logo.png, /favicon.ico)
  • Health/version checks (/api/health, /api/health/detail, /api/status, /api/version)
  • A2A discovery card (/.well-known/agent.json, /a2a/* on GET) — meant to be public per the A2A spec
  • GitHub Copilot OAuth callback path
  • /api/auth/login, /api/auth/logout, /api/auth/check (GET) — the auth mechanism itself, which necessarily can't require the key first to validate a freshly-typed one

Every other endpoint now requires the api_key as the "Unlock Dashboard" UI already implies it does.

Test plan

  • Verified against a real deployment: /api/sessions, /api/config, /api/channels all return 401 without a key, 200 with the correct key.
  • The dashboard's own index page (/) still loads without a key, as designed — only the data calls behind the "Unlock Dashboard" screen now correctly require it.

🤖 Generated with Claude Code

The auth middleware exempted a long list of GET endpoints from
requiring the api_key — /api/agents, /api/sessions, /api/config,
/api/config/schema, /api/channels, /api/hands(/active), /api/skills,
/api/integrations(/available|/health), /api/workflows, /api/approvals,
/api/budget(/agents), /api/providers, /api/models(/aliases),
/api/network/status, /api/a2a/agents, /api/cron/*, /api/uploads/*,
and the SSE /api/logs/stream — with the stated rationale "so the SPA
can render before the user enters their API key".

In practice this means any unauthenticated visitor who reaches a
deployed instance can browse real operational data: session lists,
the full config (via /api/config and /api/config/schema — which also
leaks provider/channel field names and structure), installed
integrations, channel and hand status, agent details, cron jobs, and
uploaded files. None of that requires proving you hold the api_key;
only mutating requests (POST/PUT/DELETE) were actually gated.

This trims the exemption list to what's needed for the page shell to
load and for the API-key entry flow itself to work: static assets,
health/version checks, the A2A discovery card, OAuth callback path,
and /api/auth/login|logout|check (the mechanism that validates a
freshly-typed key, which necessarily can't require the key first).
Every other endpoint now requires the api_key as intended.

## Test plan
- Verified against a real deployment: /api/sessions, /api/config,
  /api/channels all return 401 without a key, 200 with the correct
  key.
- The dashboard's own index page ("/") still loads without a key, as
  designed — only the data calls behind the "Unlock Dashboard" screen
  now correctly require it.
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