agnes-the-ai-analyst/src
Vojtech c5948f26fc
fix(api): harden API surface before Swagger (issue #336) (#339)
* fix(api): harden API surface before Swagger — 9 findings from issue #336

ADV-001: POST /api/sync/table-subscriptions now checks can_access() per
table entry, matching the gate already on POST /api/sync/settings.

ADV-002: GET /webhooks/jira/health gated behind require_admin; jira_domain
removed from response to prevent anonymous info disclosure.

ADV-003: GET /api/version no longer exposes commit_sha or schema_version.

ADV-005: /docs, /redoc, /openapi.json now require a valid session via custom
FastAPI routes (docs_url=None, redoc_url=None, openapi_url=None).

ADV-006: /cli/ and /webhooks/ added to _API_PATH_PREFIXES so future
auth-gated routes there return JSON 401 not an HTML redirect.

ADV-007: GET /api/catalog/tables wired to CatalogTablesResponse model.

ADV-008: TableSubscriptionUpdate.tables capped at max_length=500.

ADV-009: GET /api/users and GET /auth/admin/tokens accept limit/offset
(default 1000, max 10000); repositories updated accordingly.

Tests: 11 new regression tests in TestApiHardening336; test_jira_webhooks
fixture updated with seeded admin user; OpenAPI snapshot regenerated.

* fix(test): update test_journey_jira health check to use admin auth after ADV-002 gate

* fix(security): close /auth/bootstrap auth-bypass + BREAKING markers on ADV-002/003/005

Reviewer-flagged regression introduced by ADV-009's pagination on
UserRepository.list_all(): the silent default LIMIT 1000 broke the
bootstrap check at app/auth/router.py and the startup no-password
warning at app/main.py — both call list_all() with no args and depend
on exhaustive enumeration.

On an instance with >1000 users where no password-holder lands in
the email-sorted first page, [u for u in list_all() if
u.get('password_hash')] becomes empty → bootstrap re-opens → an
unauthenticated caller can claim admin via /auth/bootstrap. Real
auth-bypass on a security-sensitive boot path.

Fix:
- src/repositories/users.py: list_all() restored to no-arg, returns
  EVERY row (no LIMIT). Comment explicitly warns against re-adding
  pagination here. API-surface pagination moved to a new
  list_paginated(limit, offset) method with its own docstring.
- app/api/users.py: GET /api/users now calls list_paginated().
  Existing query-param validation (limit <= 10000) preserved.

Regression guards in tests/test_security.py::TestApiHardening336:
- test_users_list_all_returns_every_row_no_silent_limit asserts
  list_all() takes no params other than self (via inspect.signature)
  so a future cleanup can't accidentally re-add limit/offset.
- test_users_list_paginated_is_separate_method asserts the
  paginated variant is a distinct method, not an overload.

CHANGELOG: added **BREAKING** markers per CLAUDE.md release
discipline to three pre-existing ADV bullets that are observable
breaking changes for external consumers:
- ADV-002 (webhook health going from anonymous to admin-only)
- ADV-003 (/api/version dropping commit_sha + schema_version)
- ADV-005 (/docs, /redoc, /openapi.json going from anonymous to
  session-required)

* release: 0.54.25 — API hardening before Swagger (ADV-001..009) + bootstrap-bypass regression fix

---------

Co-authored-by: ZdenekSrotyr <zdenek.srotyr@keboola.com>
2026-05-18 15:13:21 +02:00
..
observability feat(observability): optional PostHog integration (#231) 2026-05-08 17:57:10 +04:00
repositories fix(api): harden API surface before Swagger (issue #336) (#339) 2026-05-18 15:13:21 +02:00
store_guardrails fix(store): promote-on-approve looks up version_no by submission_id (live agnes-development bug) (#330) 2026-05-15 21:21:14 +02:00
__init__.py Extract Keboola into connectors/keboola module 2026-03-09 12:22:16 +01:00
audit_helpers.py Activity Center: audit log + telemetry + sessions + agnes_* tables (#278) 2026-05-12 22:41:19 +02:00
catalog_export.py feat(observability): request_id end-to-end + dev debug toolbar + centralized logging (#136) 2026-04-29 22:54:21 +02:00
category_icons.py Add /marketplace browse page + Model B opt-in stack composition (#230) 2026-05-08 14:22:19 +02:00
claude_md.py fix(claude_md): load default via importlib.resources — survives /app/config bind-mount 2026-05-04 06:53:47 +02:00
db.py feat(marketplace): telemetry v46 + flea inner parity + listing polish (#329) 2026-05-15 20:58:03 +02:00
fts.py feat(memory): DuckDB FTS BM25 search for knowledge items (#121) (#326) 2026-05-15 20:10:59 +02:00
identifier_validation.py fix(security): #81 Group D — extractor-side identifier validation (squashed) (#97) 2026-04-27 21:46:17 +02:00
initial_workspace.py feat(initial-workspace): per-instance agnes init override (#292) 2026-05-13 20:35:01 +00:00
marketplace.py feat(marketplace): telemetry v46 + flea inner parity + listing polish (#329) 2026-05-15 20:58:03 +02:00
marketplace_asset_mirror.py Marketplace UX overhaul: rich plugin/skill/agent detail + filename rename (#251) 2026-05-12 08:38:39 +00:00
marketplace_asset_validation.py Marketplace UX overhaul: rich plugin/skill/agent detail + filename rename (#251) 2026-05-12 08:38:39 +00:00
marketplace_filter.py Marketplace UX overhaul: rich plugin/skill/agent detail + filename rename (#251) 2026-05-12 08:38:39 +00:00
marketplace_listing.py Activity Center: audit log + telemetry + sessions + agnes_* tables (#278) 2026-05-12 22:41:19 +02:00
marketplace_metadata.py Marketplace UX overhaul: rich plugin/skill/agent detail + filename rename (#251) 2026-05-12 08:38:39 +00:00
marketplace_urls.py perf(marketplace): cache cover photos + restore Curated filter spacing (#294) 2026-05-14 10:09:32 +02:00
orchestrator.py feat(observability): optional PostHog integration (#231) 2026-05-08 17:57:10 +04:00
orchestrator_security.py docs: consolidate and de-clutter the documentation tree (#306) 2026-05-14 18:54:22 +00:00
profiler.py Keboola cutover: native parquet path + sync correctness + auto-discover protection (#190) 2026-05-07 12:12:14 +02:00
rbac.py Activity Center: audit log + telemetry + sessions + agnes_* tables (#278) 2026-05-12 22:41:19 +02:00
remote_query.py fix(v2): #134 BigQuery cross-project errors return structured 502/400 + BqAccess facade (#138) 2026-04-30 10:11:20 +02:00
sanitize_news.py feat(home): state-aware /home + /setup-advanced + schema v26 (#228) 2026-05-08 18:28:47 +02:00
scheduler.py Keboola cutover: native parquet path + sync correctness + auto-discover protection (#190) 2026-05-07 12:12:14 +02:00
sql_safe.py feat(v2): claude-driven fetch primitives + 0.14.0 (#102) 2026-04-29 01:07:19 +02:00
store_categories.py feat(store): /store + /my-ai-stack — community marketplace + per-user composition 2026-05-05 02:53:49 +02:00
store_naming.py Flea-market upload guardrails + soft delete + JOIN-based admin queue (#233) 2026-05-09 17:32:53 +04:00
usage_ask.py fix(store): promote-on-approve looks up version_no by submission_id (live agnes-development bug) (#330) 2026-05-15 21:21:14 +02:00
welcome_template.py feat(setup): configurable instance brand + connector setup overhaul (#268) 2026-05-12 17:10:08 +02:00