feat/snapshot-object-storage #5

Merged
AzSiAz merged 18 commits from feat/snapshot-object-storage into main 2026-07-14 18:09:46 +02:00
Owner

feat: snapshots en stockage objet S3 + durcissement du fetch

Ferme le constat de sécurité « corps distants illimités et stockage persistant » : le worker pouvait être OOM par une réponse volumineuse ou une bombe gzip (res.text() sans limite ni contrôle MIME), et le HTML brut s'accumulait sans fin dans Po$

Design : docs/superpowers/specs/2026-07-12-snapshot-object-storage-design.md
Plan : docs/superpowers/plans/2026-07-12-snapshot-object-storage.md

Ce que ça change

  • Durcissement du fetch : allowlist MIME (INDARA_FETCH_ALLOWED_CONTENT_TYPES), plafond d'octets décompressés (INDARA_FETCH_MAX_BYTES, 10 Mio), rejets précoces par en-têtes, streaming direct vers S3 sans buffering du corps entier. Erre$
  • Snapshots en S3 (dépendance obligatoire, Ceph RGW/MinIO/Garage) : document_sources devient une table pointeur (s3_key, size_bytes, content_type, sha256), les octets bruts vivent sous sources/{uuid}. Décodage charset à la lecture$
  • Nettoyage : snapshot_tombstones rempli par trigger (suppression transactionnelle) + job de drain (INDARA_SNAPSHOT_DRAIN_INTERVAL_SECS, 1 h) + janitor d'orphelins (garde 24 h, quotidien).
  • Serveur : upload client S3-avant-transaction, limites de corps par route (INDARA_BODY_LIMIT_DEFAULT 1 Mio, INDARA_BODY_LIMIT_DOCUMENTS 32 Mio), fail-fast S3 au boot (server + worker).
  • Déploiement : MinIO en compose dev, values s3.* + env + NetworkPolicies gated dans la chart, README chart (lifecycle AbortIncompleteMultipartUpload + provisioning Rook).

Vérifications

  • 85 tests unitaires + 5 intégration DB (RLS inclus) verts ; migrations rejouées sur base vierge.
  • E2E réel (MinIO + dex) : upload 2 Mio → 202 avec objet S3 et sha256 concordant en base ; 40 Mio → 413 ; route à limite par défaut → 413 ; bombe gzip coupée au plafond décompressé (test automatisé).
  • Revue multi-agents par tâche + revue finale whole-branch : chaîne de sécurité fermée sur les deux chemins d'ingress, aucun chemin de fuite de cycle de vie identifié.

Notes de revue

  • Les follow-ups de la revue finale sont traités dans la branche : sémantique des limites par route verrouillée par tests (miroir exact du montage OpenApiRouter + layer-override), chemin abort-multipart prouvé sans objet visible (plafond et err$
  • MinIO (builds 2025) rejette AbortIncompleteMultipartUpload (auto-expiration interne) : le compose l'applique en best-effort ; S3/RGW réels reçoivent la règle.

🤖 Generated with Claude Code

# feat: snapshots en stockage objet S3 + durcissement du fetch Ferme le constat de sécurité « corps distants illimités et stockage persistant » : le worker pouvait être OOM par une réponse volumineuse ou une bombe gzip (`res.text()` sans limite ni contrôle MIME), et le HTML brut s'accumulait sans fin dans Po$ Design : `docs/superpowers/specs/2026-07-12-snapshot-object-storage-design.md` Plan : `docs/superpowers/plans/2026-07-12-snapshot-object-storage.md` ## Ce que ça change - **Durcissement du fetch** : allowlist MIME (`INDARA_FETCH_ALLOWED_CONTENT_TYPES`), plafond d'octets **décompressés** (`INDARA_FETCH_MAX_BYTES`, 10 Mio), rejets précoces par en-têtes, streaming direct vers S3 sans buffering du corps entier. Erre$ - **Snapshots en S3** (dépendance obligatoire, Ceph RGW/MinIO/Garage) : `document_sources` devient une table pointeur (`s3_key`, `size_bytes`, `content_type`, `sha256`), les octets bruts vivent sous `sources/{uuid}`. Décodage charset à la lecture$ - **Nettoyage** : `snapshot_tombstones` rempli par trigger (suppression transactionnelle) + job de drain (`INDARA_SNAPSHOT_DRAIN_INTERVAL_SECS`, 1 h) + janitor d'orphelins (garde 24 h, quotidien). - **Serveur** : upload client S3-avant-transaction, limites de corps par route (`INDARA_BODY_LIMIT_DEFAULT` 1 Mio, `INDARA_BODY_LIMIT_DOCUMENTS` 32 Mio), fail-fast S3 au boot (server + worker). - **Déploiement** : MinIO en compose dev, values `s3.*` + env + NetworkPolicies gated dans la chart, README chart (lifecycle `AbortIncompleteMultipartUpload` + provisioning Rook). ## Vérifications - 85 tests unitaires + 5 intégration DB (RLS inclus) verts ; migrations rejouées sur base vierge. - E2E réel (MinIO + dex) : upload 2 Mio → 202 avec objet S3 et sha256 concordant en base ; 40 Mio → 413 ; route à limite par défaut → 413 ; bombe gzip coupée au plafond décompressé (test automatisé). - Revue multi-agents par tâche + revue finale whole-branch : chaîne de sécurité fermée sur les deux chemins d'ingress, aucun chemin de fuite de cycle de vie identifié. ## Notes de revue - Les follow-ups de la revue finale sont traités dans la branche : sémantique des limites par route verrouillée par tests (miroir exact du montage `OpenApiRouter` + layer-override), chemin abort-multipart prouvé sans objet visible (plafond et err$ - MinIO (builds 2025) rejette `AbortIncompleteMultipartUpload` (auto-expiration interne) : le compose l'applique en best-effort ; S3/RGW réels reçoivent la règle. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
AzSiAz self-assigned this 2026-07-12 18:52:55 +02:00
Spec validée en brainstorming : allowlist MIME + plafond streaming vers S3
pour le fetch, document_sources en table pointeur, nettoyage tombstones +
janitor, S3-compatible en dépendance obligatoire.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
La migration snapshot recrée stage_document_source avec la signature
pointeur ; le proacl par défaut d'une fonction fraîche accorde EXECUTE à
PUBLIC (même piège que tombstone_snapshot, corrigé pour elle seulement).
Le grant implicite atteint indara_control et le contrôle de posture du
serveur refuse alors de démarrer.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AzSiAz merged commit 917faa4e65 into main 2026-07-14 18:09:46 +02:00
AzSiAz deleted branch feat/snapshot-object-storage 2026-07-14 18:10:07 +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!5
No description provided.