Repository navigation
fix(security): require sign-in on seven ungated routers, and admin where they change shared state or reach outside (#16375) - #16418
Conversation
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Advanced Run ID: Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
✅ SSOT Configuration Compliance: Passing🎉 No new hardcoded values of either class — Known backlog in |
|
CI red at
I could not pin the exact reason |
|
Security review at Verified at this head:
|
…e they change shared state or reach outside (#16375) api.ide_integration, api.knowledge_crawl, api.knowledge_scrape, api.knowledge_site_map, api.run_jwt_router and api.web_research_settings were mounted with no auth dependency. Each router now depends on get_current_user; PUT /ide/config, the three knowledge POSTs and six web-research routes also need check_admin_permission. run_jwt stays authenticated-only (get_current_user accepts run-scoped JWTs). To make the edits possible: the knowledge and run-jwt models move to schemas_knowledge_web.py / schemas_run_jwt.py (no-local-schemas hook), and PATTERN_RULES moves to api/ide_pattern_rules.py so ide_integration.py drops from its 1110-line ceiling to 928 (ceiling lowered). Tests use the real dependencies.
…in only where it reaches outside (#16375) api.knowledge_extract was mounted with no auth dependency at all -- not even the router-level sign-in #16375's first pass gave the other six. It fetches a caller-chosen URL, spends an LLM call on a caller-supplied JSON Schema, and can write the knowledge base, so it gets the same Depends(get_current_user) at router level as the other six, plus Depends(check_admin_permission) on its one route. ExtractRequest/ExtractResponse move unchanged to api/schemas_knowledge_web.py, joining the other three knowledge routes' models there: the no-local-schemas hook (#6056) blocks any edit of a router file that still defines its own BaseModel. test_router_auth_16375.py's policy table and mount list grow to cover this seventh router.
test_the_policy_table_covers_every_route asserted set() against 23 expected routes: under fastapi>=0.139, app.routes holds one opaque _IncludedRouter wrapper per include_router call and no APIRoute, so reading it directly finds nothing (#15093's shape, already hit and fixed the same way on #16370). _routes() now walks each of the seven routers individually via autobot_shared.api_routing.router_routes.effective_routes and prepends the already-known static prefix from _MOUNTS, rather than trying to recover the include-time prefix from the framework -- matching test_skills_auth_16368.py's fix for the identical failure.
…6375) Batch 1 brought repo_tests/config_router_auth_coverage_test.py onto main, and 03's batch-2a build failed test_the_known_ungated_list_has_not_gone_stale on this branch: this PR adds Depends(get_current_user) to api.ide_integration, api.knowledge_crawl, api.knowledge_scrape, api.knowledge_site_map, api.run_jwt_router and api.web_research_settings, but all six were still listed in KNOWN_UNGATED, which only ever shrinks. Verified directly against enumerate_config_routers() rather than trusting the router diff alone: all six now resolve gated=True. api.knowledge_extract (also touched by this PR) was never in KNOWN_UNGATED to begin with, so nothing to remove there -- confirmed gated=True as well, for completeness. Test logic (KNOWN_UNGATED, UNREADABLE, the two assertions) untouched; only the six now-gated entries are removed from the frozenset.
92b29ef to
2ebcd96
Compare
|
Delta
The branch is 0 behind main, and CI has 23 checks pending with none red. |
Refs #16375
Single-issue rationale: this gates seven of #16375's routers, in one change of one kind: a router-level sign-in dependency plus per-route admin.
api.long_running_operationsgoes in the next PR, because it needs its own extraction to get under its size ceiling, and its websocket needs its own gate.api.chat_knowledgewaits for #16345, which edits that file.Thinking Path
app_factory.py:83,90mounts every registry router withinclude_router(router, prefix, tags)and nodependencies=.AuthenticationMiddlewareis never installed.service_auth_enforcement.pyonly blocksSERVICE_ONLY_PATHS.a12b4bf79. The keyword signals in security(api): 44 config-registered routers declare no auth gate, triaged by exposure (found by widening the router-auth guard, #16368) #16375 were leads, not proof. The trace is below, per router.knowledge_extracthas no dependency at all.long_running_operations' only dependency is a 503 availability check.api.ws_securitycounts as a gate, and the whole module is judged as one unit. Filed as a separate enumerator bug.knowledge_extractis in this PR.long_running_operationsis next.What an anonymous caller could reach (before this PR)
api.ide_integration(/api/ide)PUT /configupdate_configuration).POST /analyze,/quickfix,/hover,/batch-analyze,/completion;GET /rules,/categories,/severitiesapi.knowledge_crawlPOST /api/knowledge/crawlingest=True→store_fact, Redis and ChromaDB). An unguardedrobots.txtGET went to every seed and linked host (SSRF, #16378). Ascrape_template_idreached the headless template runner, whose own router needs admin.api.knowledge_scrapePOST /api/knowledge/scrapeingest=truewrote to theautobot_docsChromaDB collection.api.knowledge_site_mapPOST /api/knowledge/site-map<caller scheme://host:port/path>/sitemap.xml, plus every child<loc>of a sitemap index, with<loc>values reflected back. That's SSRF to internal hosts (#16378).api.knowledge_extractPOST /api/knowledge/extractautobot_docs.api.run_jwt_routerPOST /api/runs/{run_id}/jwt/refreshrun_idbinding. But a refreshed token has no absolute lifetime (#16379).api.web_research_settings(/api/web-research)PUT /settingssettings.json, includingrequire_user_confirmationand the rate limits.POST /testPOST /reset-circuit-breakers,/clear-cachePOST /enable,/disableGET /status,/settings,/usage-statsWhat Changed
Router-level
Depends(get_current_user)on all seven routers, so a route added later is gated by default.Per-route
Depends(check_admin_permission)on every route that changes shared config or drives the research browser:PUT /api/ide/config;POST /api/web-research/enable,/disable,/test,/clear-cache,/reset-circuit-breakers;PUT /api/web-research/settings/api/ide/*routes;POST /api/knowledge/crawl,/scrape,/site-map,/extract;POST /api/runs/{run_id}/jwt/refresh;GET /api/web-research/status,/settings,/usage-statsOwner decision: the web-research routes (crawl/scrape/site-map/extract) are for any signed-in user — each still reaches outside on a caller-chosen URL, but only the web-research settings mutations (
enable,disable,PUT /settings,test,clear-cache,reset-circuit-breakers) stay admin.run-JWT refresh stays authenticated-only.
get_current_useraccepts a run-scoped JWT (SEC-2 feat(security): run-scoped short-lived JWTs to limit blast radius of leaked agent credentials #6473), so a run refreshing its own token still passes. The handler still binds the token torun_id, and revokes it.Moves needed to edit at all:
api/schemas_knowledge_web.pyandapi/schemas_run_jwt.py. The no-local-schemas hook (feat(lint): add pre-commit hook to block new local BaseModel definitions in non-schema API endpoint files #6056) blocks any edit of a router that still defines its ownBaseModel, andschemas_knowledge.pyis at its ceiling.CrawlPageEntryhad no user and isn't re-imported.Room under the size ceiling:
PATTERN_RULES(184 lines) moves unchanged toapi/ide_pattern_rules.py, andapi.ide_integrationre-imports it.ide_integration.pygoes 1110 → 928 lines, and its ceiling is lowered in bothrepo_tests/python_file_size_ratchet_baseline.pyandscripts/python_file_size_known_large.py.Changelog:
changelog/unreleased/16375-router-auth.md.Behaviour change, stated plainly:
composables/knowledge/useWebResearch.ts:107-119.) Any signed-in user can reach these four routes, per the owner's decision above.stores/useWebResearchStore.ts: enable/disable, settings, test, clear-cache, reset-circuit-breakers.) A non-admin now gets 403 there.Verification
autobot-backend/tests/api/test_router_auth_16375.pytests with the real dependencies, on fix(security): authenticate every skills route, and require admin to change, fetch or execute (#16368) #16370's pattern. Under pytestauth_middlewareis the conftest stub, which grants admin, so the test overrides exactly the objects each router bound with the real functions fromreal_auth_middleware.crawl,scrape,site-map,extract— outbound I/O stubbed, so only the auth gate is under test) and onGET /api/ide/categories(the pre-existing control).tests/api/test_web_research_settings_guard.py,tests/unit/api/test_knowledge_crawl.py,api/knowledge_scrape_test.py,api/web_research_status_timeout_12751_test.py) run under the admin-granting stub. The new dependencies resolve to it there, as with fix(security): authenticate every skills route, and require admin to change, fetch or execute (#16368) #16370.Sequencing: #16370's router-auth guard adds a baseline of the ungated routers, with a stale-entry check. Once it merges, base gets merged here and these seven routers' entries get removed.
Filed while tracing: #16378 (SSRF in the robots.txt and sitemap fetches, which auth doesn't close), #16379 (unbounded run-JWT refresh), #16380 (web-research enable/disable always 500), #16381 (IDE completion cache key and unbounded input).
Model Used
Claude Opus 5 (
claude-opus-5)🤖 Generated with Claude Code