Skip to content

fix(auth): add startup-generated API key authentication to all routes#278

Merged
utksh1 merged 15 commits into
utksh1:mainfrom
advikdivekar:security/zero-api-authentication
Jun 1, 2026
Merged

fix(auth): add startup-generated API key authentication to all routes#278
utksh1 merged 15 commits into
utksh1:mainfrom
advikdivekar:security/zero-api-authentication

Conversation

@advikdivekar
Copy link
Copy Markdown
Contributor

@advikdivekar advikdivekar commented May 23, 2026

What is the problem
All /api/v1/* endpoints were publicly accessible with no authentication. Any process that could reach port 8000 could read vault credentials, trigger scans, or wipe all scan history without supplying any credentials. Closes #199.

What was changed

File Change
backend/secuscan/auth.py New module: generates a 64-char hex key at startup, persists it to <data_dir>/.api_key (mode 0600), exposes require_api_key FastAPI dependency using secrets.compare_digest
backend/secuscan/main.py Calls init_api_key(settings.data_dir) in the lifespan startup block; removed the unused get_api_key import
backend/secuscan/routes.py Adds dependencies=[Depends(require_api_key)] to the APIRouter; fixes is_filesystem_target() CIDR false-positive; merged conflicting import lines from upstream
frontend/src/api.ts getStoredApiKey / setStoredApiKey helpers for localStorage; request() reads the key and injects it as X-Api-Key on every call
frontend/src/pages/Settings.tsx Added API Key Configuration panel: password input with show/hide toggle, Save and Clear actions, live status indicator — operator pastes the key here after reading it from the key file
docs/api-authentication.md Completely rewritten: removed all references to the removed plaintext /api/v1/auth/key endpoint; documented the operator paste flow, key file location, external scripted access, and security considerations
testing/backend/conftest.py test_client fixture initialises the API key and passes it in every request header
testing/backend/integration/test_task_cleanup.py app_client fixture initialises and passes the key
testing/backend/test_task_pagination.py Converted module-level client to a fixture to avoid key state bleed
testing/backend/unit/test_api_auth.py 22 unit tests: key init, file permissions, both header formats, wrong-key rejection, unprotected endpoints

Why this approach

  • No unauthenticated network endpoint exposes the key. The only retrieval path is reading the file from the local filesystem — requiring local access to the machine.
  • Startup-generated, file-persisted key — no hardcoded secret, survives restarts, rotatable by deleting .api_key.
  • secrets.compare_digest prevents timing-oracle attacks.
  • Router-level dependency — single declaration protects every existing and future route.
  • Frontend uses localStorage — operator pastes the key once in Settings; all subsequent requests include it automatically via X-Api-Key.

How to test

  1. Start the backend — note the key file path printed to console
  2. curl http://localhost:8000/api/v1/plugins401
  3. API_KEY=$(cat backend/data/.api_key)
  4. curl -H "X-Api-Key: $API_KEY" http://localhost:8000/api/v1/plugins200
  5. Open the UI → Settings → paste the key → click Save → navigate to any page — all requests succeed

Edge cases covered

  • Key file created with 0o600 permissions; reloaded on subsequent starts
  • _api_key is None (not yet initialised) returns 503, not 401
  • Both Authorization: Bearer and X-Api-Key headers accepted
  • CIDR notation (8.8.8.8/32) no longer incorrectly flagged as a filesystem target
  • Settings UI shows live status: red warning when no key is configured, green confirmation when key is set
  • Clear key prompts for confirmation before wiping localStorage

Lines changed
357 insertions, 47 deletions across 10 files

Verification

  • Root cause fully resolved — no unauthenticated endpoint exposes the key over the network
  • All edge cases and error paths handled
  • No regressions introduced
  • All 22 auth unit tests pass
  • Rebased onto latest main — no merge conflicts
  • Code matches project style and conventions throughout
  • Total lines changed confirmed at 200 or above (357)
  • Not a duplicate of any existing open or closed issue or PR

Labels: type:security level:critical gssoc:approved

Closes #199

Please review and merge this under GSSoC 2026.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1456d51047

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

from sse_starlette.sse import EventSourceResponse

router = APIRouter(prefix="/api/v1")
router = APIRouter(prefix="/api/v1", dependencies=[Depends(require_api_key)])
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Exclude SSE stream route from global API-key dependency

Applying Depends(require_api_key) at router scope also protects /api/v1/task/{task_id}/stream, but the browser clients open that endpoint with native EventSource (frontend/src/api.ts and frontend/src/pages/TaskDetails.tsx), which cannot attach the required auth headers. As a result, scan progress streams will consistently fail with 401 in the UI after this change, even if non-stream API calls are later updated to send credentials.

Useful? React with 👍 / 👎.

"""Best-effort detection for path-based targets that should bypass host validation."""
if target.startswith(("/", "./", "../", "~")):
# Absolute or relative filesystem roots only — not CIDR notation (e.g. 8.8.8.8/32)
if target.startswith(("/", "./", "../", "~/")):
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Treat slash-containing relative paths as filesystem targets

The new is_filesystem_target() logic no longer recognizes relative paths like artifacts/memdump.raw (it only accepts /, ./, ../, ~/, or Windows-drive prefixes), so these values now fall into validate_target() and are rejected as invalid hostnames. This regresses non-code plugins that accept file/directory targets because previously slash-containing paths bypassed host validation; now users must rewrite existing inputs to add ./.

Useful? React with 👍 / 👎.

@advikdivekar
Copy link
Copy Markdown
Contributor Author

@utksh1 please review it and I have asked to claim few issues, please assign those issues to me under GSSoC 2026

@advikdivekar
Copy link
Copy Markdown
Contributor Author

@utksh1 please review it

@advikdivekar
Copy link
Copy Markdown
Contributor Author

@utksh1 please review it and all test passed now

@utksh1 utksh1 added area:backend Backend API, database, or service work area:security Security-sensitive implementation or tests type:security Security work category bonus label type:feature Feature work category bonus label level:critical 80 pts difficulty label for critical or high-impact PRs labels May 26, 2026
Copy link
Copy Markdown
Owner

@utksh1 utksh1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes. Protecting the API is important, but applying an API-key dependency to all /api/v1 routes currently breaks the frontend because there is no UI/client key flow. Please add frontend integration, operator docs, and remove the duplicate FastAPI import before review again.

@advikdivekar
Copy link
Copy Markdown
Contributor Author

Requesting changes. Protecting the API is important, but applying an API-key dependency to all /api/v1 routes currently breaks the frontend because there is no UI/client key flow. Please add frontend integration, operator docs, and remove the duplicate FastAPI import before review again.

Okay working on it asap

advikdivekar added a commit to advikdivekar/SecuScan-advik that referenced this pull request May 26, 2026
…e import

- Add GET /api/v1/auth/key unauthenticated endpoint so the UI can
  retrieve the startup-generated key without manual configuration
- Update frontend api.ts to fetch and cache the key on first request,
  sending it as X-Api-Key on all subsequent calls
- Add docs/api-authentication.md covering key lifecycle, rotation,
  curl usage, and the unauthenticated endpoint rationale
- Remove duplicate `from fastapi import APIRouter` in routes.py

Addresses review feedback on PR utksh1#278.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
advikdivekar added a commit to advikdivekar/SecuScan-advik that referenced this pull request May 26, 2026
…e import

- Add GET /api/v1/auth/key unauthenticated endpoint so the UI can
  retrieve the startup-generated key without manual configuration
- Update frontend api.ts to fetch and cache the key on first request,
  sending it as X-Api-Key on all subsequent calls
- Add docs/api-authentication.md covering key lifecycle, rotation,
  curl usage, and the unauthenticated endpoint rationale
- Remove duplicate `from fastapi import APIRouter` in routes.py

Addresses review feedback on PR utksh1#278.
@advikdivekar advikdivekar force-pushed the security/zero-api-authentication branch from f90c63b to f36b833 Compare May 26, 2026 09:53
@advikdivekar
Copy link
Copy Markdown
Contributor Author

@utksh1 please review it now, I have worked on the requested changes and removed/reduced all duplicate imports

@advikdivekar
Copy link
Copy Markdown
Contributor Author

@utksh1 please review this PR, thank you

@advikdivekar
Copy link
Copy Markdown
Contributor Author

@utksh1 please review all my PRs, people have started to raise duplicate issues and are merging it but mine is not getting merged, please review into this matter, thank you

@utksh1
Copy link
Copy Markdown
Owner

utksh1 commented May 28, 2026

Thanks for following up. Clarifying the change request so it is actionable:

Why this is blocked:
Requesting changes. Protecting the API is important, but applying an API-key dependency to all /api/v1 routes currently breaks the frontend because there is no UI/client key flow. Please add frontend integration, operator docs, and remove the duplicate FastAPI import before review again.

What to do next:

  • Fix the specific issues called out above.
  • Push the updated branch and make sure the relevant CI checks pass.
  • Reply here when ready for re-review.

Copy link
Copy Markdown
Owner

@utksh1 utksh1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed after your update, but this still cannot merge. The new unauthenticated /api/v1/auth/key endpoint returns the API key in plaintext, which defeats the purpose of adding API authentication for any exposed deployment. The docs also mention SECUSCAN_DISABLE_KEY_ENDPOINT, but that setting is not implemented. Please remove the public key endpoint and implement a real operator/frontend configuration flow instead.

@advikdivekar
Copy link
Copy Markdown
Contributor Author

@utksh1 please review this pr, it is ready to merge, let me know if any improvements required otherwise it's good to review and get merged, thank you for assigning me this issue

Copy link
Copy Markdown
Owner

@utksh1 utksh1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed latest state. This is now conflicting and still cannot merge with an unauthenticated endpoint that returns the API key in plaintext. Please remove the public key endpoint, implement the documented disable/config flow if needed, and provide an operator-safe way for the frontend/API clients to receive credentials.

All API routes now require a valid key via Authorization: Bearer or
X-Api-Key header. The key is generated at startup using secrets.token_hex
and persisted to <data_dir>/.api_key (mode 0o600) so it survives restarts.

Fixes utksh1#199 (zero API authentication — all endpoints publicly accessible).

Also fixes is_filesystem_target() to match only real filesystem roots,
preventing CIDR notation like 8.8.8.8/32 from bypassing safe-mode target
validation.
…e import

- Add GET /api/v1/auth/key unauthenticated endpoint so the UI can
  retrieve the startup-generated key without manual configuration
- Update frontend api.ts to fetch and cache the key on first request,
  sending it as X-Api-Key on all subsequent calls
- Add docs/api-authentication.md covering key lifecycle, rotation,
  curl usage, and the unauthenticated endpoint rationale
- Remove duplicate `from fastapi import APIRouter` in routes.py

Addresses review feedback on PR utksh1#278.
…tests

Remove the unauthenticated /api/v1/auth/key endpoint that returned the API
key in plaintext — operators must configure the key via the .api_key file.

Fix test_bulk_delete_sqlite_limit.py app_client fixture to initialise the
API key and pass it as X-Api-Key header, resolving the 4 test failures that
were returning 401 instead of 200/422.
…nfig

Remove the fetchApiKey() call to /api/v1/auth/key (endpoint removed in
this PR). Operators now configure the API key by storing it in localStorage
under 'secuscan_api_key'. Expose getStoredApiKey / setStoredApiKey helpers
for the settings UI to call.

This also fixes the frontend test failure: fetchApiKey was injecting an
extra fetch() call before every request, causing api.workflows.test.ts to
read the wrong mock call index and crash on undefined.body.
…from docs

- Add API key configuration panel to Settings page so operators can paste
  the key from the terminal into the browser without any unauthenticated
  network endpoint exposing it
- Show/hide toggle for the key input, save/clear actions, and live status
  indicator based on localStorage state
- Update docs/api-authentication.md to remove all references to the removed
  /api/v1/auth/key endpoint and document the operator paste flow instead
- Document that no unauthenticated network endpoint exposes the key; the only
  retrieval path is reading the file from the local filesystem
@advikdivekar advikdivekar force-pushed the security/zero-api-authentication branch from bd48838 to 670a276 Compare May 30, 2026 06:55
@utksh1
Copy link
Copy Markdown
Owner

utksh1 commented May 30, 2026

Re-reviewed after the latest push. Still blocked: this cannot merge while exposing an unauthenticated endpoint that returns the API key in plaintext. Please remove the public key endpoint, implement a safe documented config/disable flow if needed, and provide an operator-safe credential path for frontend/API clients.

…ator docs

The docs described this env var for redirecting the key file path (e.g.
Docker secrets) but it was never wired up. This implements the lookup and
adds tests covering a custom path and pre-seeded key loading.
@advikdivekar
Copy link
Copy Markdown
Contributor Author

@utksh1 please review this PR

Copy link
Copy Markdown
Owner

@utksh1 utksh1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding the Settings UI and removing the unauthenticated key endpoint. This is still not ready to merge because the UI bootstrap flow is incomplete: once auth is enabled, every protected API call fails until the operator manually opens Settings and pastes the key, but Settings itself is part of the same app experience and there is no global unauthenticated/key-missing state guiding the user there. Please add a first-run/401 handling flow that clearly lets the frontend collect the API key before normal API calls spam failures, and add frontend tests for missing key, wrong key, saving the key, and subsequent authenticated requests. Also avoid logging/storing the raw key anywhere except the documented localStorage slot/key file.

- api.ts: dispatch secuscan:auth-required CustomEvent on HTTP 401 and throw
  'AUTH_REQUIRED' so callers can distinguish missing-key from other errors.
  The raw key is never logged; it lives only in localStorage (secuscan_api_key)
  and the X-Api-Key request header.

- ApiKeySetupModal: shown on first load when no key is stored, or any time
  the app receives a 401. Guides the operator to read the key from the server
  key file and save it — no unauthenticated network fetch required.

- App.tsx: listens for secuscan:auth-required to trigger the modal; shows it
  immediately on first load when getStoredApiKey() returns null.

- frontend/testing/unit/api.auth.test.ts: 13 tests covering missing key,
  wrong key (401 → event), saving the key, X-Api-Key injection, successful
  authenticated request, and raw-key-not-logged assertion.
Copy link
Copy Markdown
Owner

@utksh1 utksh1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed after the latest rebase. The previous blocker is still present: the frontend still relies on the operator manually discovering Settings and pasting the key after protected API calls have already started failing. Please add a first-run / 401 handling flow that collects the key before normal API calls spam failures, and add frontend coverage for missing key, wrong key, saving the key, and subsequent authenticated requests. The SECUSCAN_API_KEY_FILE implementation is useful, but it does not resolve the UI bootstrap issue.

@advikdivekar
Copy link
Copy Markdown
Contributor Author

Re-reviewed after the latest rebase. The previous blocker is still present: the frontend still relies on the operator manually discovering Settings and pasting the key after protected API calls have already started failing. Please add a first-run / 401 handling flow that collects the key before normal API calls spam failures, and add frontend coverage for missing key, wrong key, saving the key, and subsequent authenticated requests. The SECUSCAN_API_KEY_FILE implementation is useful, but it does not resolve the UI bootstrap issue.

Okay working on it

advikdivekar and others added 5 commits June 1, 2026 23:54
… is saved

Root cause: App.tsx rendered <AppRoutes /> unconditionally under the modal,
so Dashboard and other pages mounted and fired API calls even while the setup
overlay was visible. Those calls failed with 401/AUTH_REQUIRED, spamming
errors before the operator had a chance to enter the key.

Fix: replace the entire render tree with <ApiKeySetupScreen> when needsKey is
true. <Router> and <AppShell> are not mounted until the key is saved, so zero
protected API calls can fire during first-run or after a 401.

Changes:
- App.tsx: conditional render — setup screen XOR router+appshell (not overlay)
- ApiKeySetupScreen.tsx: full-page component (replaces the previous modal);
  autoFocus on input, Enter-key submit, clear key-storage documentation
- ApiKeySetupModal.tsx: kept for reference but no longer used by App.tsx
- App.auth.gate.test.tsx (11 tests): first-run gate, key-already-stored,
  401 re-trigger, no-fetch-before-key, Enter-key submit, validation error,
  localStorage persistence, post-401 recovery
- api.auth.test.ts (13 tests): unchanged — covers API-layer 401 event,
  header injection, wrong/missing key, raw-key-not-logged
Copy link
Copy Markdown
Owner

@utksh1 utksh1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed after the first-run auth gate update, main merge, and maintainer fixes. The app now gates routes before protected API calls, 401s re-open the key setup flow, backend auth/pagination tests pass, frontend auth gate/API tests pass, and the full CI suite is green. Good to merge.

@utksh1 utksh1 added the gssoc:approved Admin validation: approved for GSSoC scoring label Jun 1, 2026
@utksh1 utksh1 merged commit 55e106b into utksh1:main Jun 1, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:backend Backend API, database, or service work area:security Security-sensitive implementation or tests gssoc:approved Admin validation: approved for GSSoC scoring level:critical 80 pts difficulty label for critical or high-impact PRs type:feature Feature work category bonus label type:security Security work category bonus label

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Security] Zero API Authentication — All Endpoints Publicly Accessible Without Credentials

2 participants