feat: SSRF guard for the document-fetch worker #4

Merged
AzSiAz merged 17 commits from feat/ssrf-guard into main 2026-07-11 18:20:02 +02:00
Owner

Objet

Ferme un SSRF exploitable : un utilisateur authentifié pouvait faire fetcher au
worker n'importe quelle cible interne (loopback, RFC1918, metadata, services
K8s…). Approche à la Discourse/Mastodon : garde applicatif primaire + couche
réseau (NetworkPolicy/Cilium) en backstop opt-in.

Changements

  • indara-core::net : is_public_ip (plages privées/réservées + dé-mapping
    IPv4-in-IPv6 dont ::a.b.c.d), validate_url, host_is_literal_private_ip.
  • indara-jobs : PublicOnlyResolver (DNS public fixe, filtrage IP, pinning
    anti-rebinding), build_fetch_client (redirections auto off), boucle de
    redirection revalidant chaque hop (schéma/credentials/host + IP littérale),
    classification fatale des hosts bloqués.
  • apps/server : fast-fail 400 à l'API (credentials, IP littérale privée).
  • charts/indara : NetworkPolicy + CiliumNetworkPolicy egress opt-in,
    worker.fetchDns en source unique (env app + règles egress).
  • Config : INDARA_FETCH_DNS, INDARA_FETCH_MAX_REDIRECTS. README documenté.

Point de sécurité notable

La revue whole-branch a rattrapé un bypass critique : reqwest ne consulte le
résolveur custom que pour les hosts par nom, pas les IP littérales — donc un
302 vers une IP privée littérale contournait le garde. Corrigé par un check
host_is_literal_private_ip à chaque hop (worker autoritaire indép. de l'API),
avec test comportemental.

Vérification

cargo test --workspace vert, cargo build --workspace clean, helm template
(flags on/off) OK. Design & plan : docs/superpowers/specs|plans/2026-07-11-ssrf-*.

## Objet Ferme un SSRF exploitable : un utilisateur authentifié pouvait faire fetcher au worker n'importe quelle cible interne (loopback, RFC1918, metadata, services K8s…). Approche à la Discourse/Mastodon : garde applicatif primaire + couche réseau (NetworkPolicy/Cilium) en backstop opt-in. ## Changements - `indara-core::net` : `is_public_ip` (plages privées/réservées + dé-mapping IPv4-in-IPv6 dont `::a.b.c.d`), `validate_url`, `host_is_literal_private_ip`. - `indara-jobs` : `PublicOnlyResolver` (DNS public fixe, filtrage IP, pinning anti-rebinding), `build_fetch_client` (redirections auto off), boucle de redirection revalidant chaque hop (schéma/credentials/host + IP littérale), classification fatale des hosts bloqués. - `apps/server` : fast-fail 400 à l'API (credentials, IP littérale privée). - `charts/indara` : NetworkPolicy + CiliumNetworkPolicy egress opt-in, `worker.fetchDns` en source unique (env app + règles egress). - Config : `INDARA_FETCH_DNS`, `INDARA_FETCH_MAX_REDIRECTS`. README documenté. ## Point de sécurité notable La revue whole-branch a rattrapé un bypass critique : reqwest ne consulte le résolveur custom que pour les hosts par nom, pas les IP littérales — donc un `302` vers une IP privée littérale contournait le garde. Corrigé par un check `host_is_literal_private_ip` à chaque hop (worker autoritaire indép. de l'API), avec test comportemental. ## Vérification `cargo test --workspace` vert, `cargo build --workspace` clean, `helm template` (flags on/off) OK. Design & plan : `docs/superpowers/specs|plans/2026-07-11-ssrf-*`.
AzSiAz self-assigned this 2026-07-11 17:54:19 +02:00
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A redirect response with no Location header fell through to
classify_fetch_response, whose error path calls error_for_status_ref()
and expects a reqwest::Error — but that returns Ok for a 3xx, so
expect_err panicked. Now handled in the redirect loop as a fatal
BadRedirect, and classify_fetch_response no longer panics on an
unexpected non-final status.
AzSiAz merged commit 5e98b5c31d into main 2026-07-11 18:20:02 +02:00
AzSiAz deleted branch feat/ssrf-guard 2026-07-11 18:20:14 +02: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
AzSiAz/Indara!4
No description provided.