fix(auth): add startup-generated API key authentication to all routes#278
Conversation
There was a problem hiding this comment.
💡 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)]) |
There was a problem hiding this comment.
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(("/", "./", "../", "~/")): |
There was a problem hiding this comment.
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 👍 / 👎.
|
@utksh1 please review it and I have asked to claim few issues, please assign those issues to me under GSSoC 2026 |
|
@utksh1 please review it |
|
@utksh1 please review it and all test passed now |
utksh1
left a comment
There was a problem hiding this comment.
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 |
…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>
…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.
f90c63b to
f36b833
Compare
|
@utksh1 please review it now, I have worked on the requested changes and removed/reduced all duplicate imports |
|
@utksh1 please review this PR, thank you |
|
@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 |
|
Thanks for following up. Clarifying the change request so it is actionable: Why this is blocked: What to do next:
|
utksh1
left a comment
There was a problem hiding this comment.
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.
|
@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 |
utksh1
left a comment
There was a problem hiding this comment.
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
bd48838 to
670a276
Compare
|
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.
|
@utksh1 please review this PR |
utksh1
left a comment
There was a problem hiding this comment.
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.
utksh1
left a comment
There was a problem hiding this comment.
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 |
… 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
utksh1
left a comment
There was a problem hiding this comment.
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.
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
backend/secuscan/auth.py<data_dir>/.api_key(mode0600), exposesrequire_api_keyFastAPI dependency usingsecrets.compare_digestbackend/secuscan/main.pyinit_api_key(settings.data_dir)in the lifespan startup block; removed the unusedget_api_keyimportbackend/secuscan/routes.pydependencies=[Depends(require_api_key)]to theAPIRouter; fixesis_filesystem_target()CIDR false-positive; merged conflicting import lines from upstreamfrontend/src/api.tsgetStoredApiKey/setStoredApiKeyhelpers forlocalStorage;request()reads the key and injects it asX-Api-Keyon every callfrontend/src/pages/Settings.tsxdocs/api-authentication.md/api/v1/auth/keyendpoint; documented the operator paste flow, key file location, external scripted access, and security considerationstesting/backend/conftest.pytest_clientfixture initialises the API key and passes it in every request headertesting/backend/integration/test_task_cleanup.pyapp_clientfixture initialises and passes the keytesting/backend/test_task_pagination.pytesting/backend/unit/test_api_auth.pyWhy this approach
.api_key.secrets.compare_digestprevents timing-oracle attacks.X-Api-Key.How to test
curl http://localhost:8000/api/v1/plugins→401API_KEY=$(cat backend/data/.api_key)curl -H "X-Api-Key: $API_KEY" http://localhost:8000/api/v1/plugins→200Edge cases covered
0o600permissions; reloaded on subsequent starts_api_key is None(not yet initialised) returns503, not401Authorization: BearerandX-Api-Keyheaders accepted8.8.8.8/32) no longer incorrectly flagged as a filesystem targetLines changed
357 insertions, 47 deletions across 10 files
Verification
Labels:
type:securitylevel:criticalgssoc:approvedCloses #199
Please review and merge this under GSSoC 2026.