fix(api): SSRF guard on ingest; whitelist PATCH /api/sources #46

Merged
sirius merged 2 commits from feat/ssrf-ingest-source-whitelist into master 2026-09-01 00:54:14 -04:00
Owner

Summary

Non-auth hardening: fail-closed SSRF check on ingest triggers, and mass-assignment lock on feed source updates. Service stays private/unauthenticated.

Changes

  • POST /api/ingest/rss: reject feed_url unless camera_scraper.is_public_url (HTTP 400). Link-local metadata (http://169.254.169.254/...) is not fetched.
  • POST /api/ingest/gdelt: require the GDELT API host to be public; if query is URL-shaped (http/https + host), reject private/loopback/link-local targets with 400.
  • PATCH /api/sources/{id}: FeedSourceUpdate allowlist (name, url, config, enabled) with extra="forbid" → 422 on any other key. Unset fields are not written.

Tests

tests/test_ingest_ssrf_and_sources.py — 4 passed (ASGI, ingest mocked so private URLs never fetch).
pytest -q: 4 new tests green. Unrelated pre-existing failure: test_ingest_fire_row_idempotent (fires table, not touched).

Out of scope

Authn/authz, masscan, TiTiler, DB indexes.

## Summary Non-auth hardening: fail-closed SSRF check on ingest triggers, and mass-assignment lock on feed source updates. Service stays private/unauthenticated. ## Changes - `POST /api/ingest/rss`: reject `feed_url` unless `camera_scraper.is_public_url` (HTTP 400). Link-local metadata (`http://169.254.169.254/...`) is not fetched. - `POST /api/ingest/gdelt`: require the GDELT API host to be public; if `query` is URL-shaped (`http`/`https` + host), reject private/loopback/link-local targets with 400. - `PATCH /api/sources/{id}`: `FeedSourceUpdate` allowlist (`name`, `url`, `config`, `enabled`) with `extra="forbid"` → 422 on any other key. Unset fields are not written. ## Tests `tests/test_ingest_ssrf_and_sources.py` — 4 passed (ASGI, ingest mocked so private URLs never fetch). `pytest -q`: 4 new tests green. Unrelated pre-existing failure: `test_ingest_fire_row_idempotent` (fires table, not touched). ## Out of scope Authn/authz, masscan, TiTiler, DB indexes.
sirius added 1 commit 2026-09-01 00:46:04 -04:00
Reject private/loopback/link-local hosts on POST /api/ingest/rss and
URL-shaped GDELT queries via camera_scraper.is_public_url (HTTP 400).
PATCH /api/sources/{id} only accepts name, url, config, enabled (422 else).
sirius added 1 commit 2026-09-01 00:54:06 -04:00
sirius merged commit 6ff2fd0351 into master 2026-09-01 00:54:14 -04:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: sirius/osint-dashboard#46
No description provided.