# Security-Feed-Storage-Readiness

Security-Feed-Storage ist noch nicht schreibbereit: 0 Datenbank-Artefakt(e) fehlen oder die Serverfreigabe ist nicht aktiv.

> Readiness zeigt technische Schreibbereitschaft und Betreiberfreigabe. Es veröffentlicht keine Secrets, keine Feed-Rohdaten und keine personenbezogenen Logs.

## Freigabe
- Storage approved: nein
- Required env: SAFERPAGE_SECURITY_FEED_STORAGE_APPROVED=yes

## Tabellen
- security_feed_observations: ready
- security_feed_alert_links: ready
- security_feed_audit_log: ready

## Indizes
- security_feed_observations_domain_checked_idx: ready
- security_feed_observations_source_verdict_idx: ready
- security_feed_observations_expires_idx: ready
- security_feed_observations_result_meta_gin_idx: ready
- security_feed_alert_links_dedupe_idx: ready
- security_feed_audit_log_dedupe_idx: ready

## Trigger
- security_feed_observations_set_updated_at: ready
- security_feed_alert_links_set_updated_at: ready

## Preflight-Artefakte
- Evidence: https://saferpage.de/evidence/security-feed-storage-preflight.json
- Erforderlich: 11
- Bereit: 0
- Fehlend: 11
- **Feed-Observations-Tabelle**: missing - infra/postgres/migrations/security-feed-storage.sql mit DB-Admin-Verantwortung aus sicherer Shell anwenden.
- **Alert-Link-Tabelle**: missing - infra/postgres/migrations/security-feed-storage.sql mit DB-Admin-Verantwortung aus sicherer Shell anwenden.
- **Feed-Auditlog**: missing - infra/postgres/migrations/security-feed-storage.sql mit DB-Admin-Verantwortung aus sicherer Shell anwenden.
- **Domain/Checked-Index**: missing - infra/postgres/migrations/security-feed-storage.sql mit DB-Admin-Verantwortung aus sicherer Shell anwenden.
- **Source/Verdict-Index**: missing - infra/postgres/migrations/security-feed-storage.sql mit DB-Admin-Verantwortung aus sicherer Shell anwenden.
- **Retention-Index**: missing - infra/postgres/migrations/security-feed-storage.sql mit DB-Admin-Verantwortung aus sicherer Shell anwenden.
- **Result-Meta-GIN-Index**: missing - infra/postgres/migrations/security-feed-storage.sql mit DB-Admin-Verantwortung aus sicherer Shell anwenden.
- **Alert-Dedupe-Index**: missing - infra/postgres/migrations/security-feed-storage.sql mit DB-Admin-Verantwortung aus sicherer Shell anwenden.
- **Audit-Dedupe-Index**: missing - infra/postgres/migrations/security-feed-storage.sql mit DB-Admin-Verantwortung aus sicherer Shell anwenden.
- **Observations-updated_at-Trigger**: missing - infra/postgres/migrations/security-feed-storage.sql mit DB-Admin-Verantwortung aus sicherer Shell anwenden.
- **Alert-Link-updated_at-Trigger**: missing - infra/postgres/migrations/security-feed-storage.sql mit DB-Admin-Verantwortung aus sicherer Shell anwenden.

## Runbook
- Dedizierte Migration mit autorisierter DB-Verantwortung anwenden: psql -d saferpage -v ON_ERROR_STOP=1 -f infra/postgres/migrations/security-feed-storage.sql
- Danach Readiness erneut prüfen und Tabellen/Indizes/Trigger als ready bestätigen.
- SAFERPAGE_SECURITY_FEED_STORAGE_APPROVED=yes erst nach Betreiberfreigabe, Retention-Review und Alert-Routing setzen.
- Optionalen Storage-Canary mit --storage-canary ausführen, um eine synthetische private Observation ohne externe Feed-Abfrage zu testen.
- Separaten systemd-Canary-Timer prüfen: systemctl list-timers saferpage-security-feed-canary.timer --no-pager.
- Ersten Runner-Lauf kontrollieren: stored_observation_count muss zu den aktivierungsbereiten Domains passen.

## Migration Package
- Version: security-feed-storage-2026-06-09
- Rolle: PostgreSQL-Rolle mit Besitz- oder Adminrechten sowie CREATE TABLE, CREATE INDEX, CREATE TRIGGER im Schema public.
- SQL: https://saferpage.de/sicherheit/feed-storage-migration.sql
- SHA-256: bcaa177b61f0c9ea578e80abcdb8fa2af9c0615fd197f153b7367161603e9453
- Command: `psql -d saferpage -v ON_ERROR_STOP=1 -f infra/postgres/migrations/security-feed-storage.sql`
- Download + apply: `curl -fsS https://saferpage.de/sicherheit/feed-storage-migration.sql -o /tmp/saferpage-security-feed-storage.sql && psql -d saferpage -v ON_ERROR_STOP=1 -f /tmp/saferpage-security-feed-storage.sql`

### Preflight
- psql -d saferpage -Atq -c "select current_database(), current_user;"
- psql -d saferpage -Atq -c "select coalesce(to_regclass('public.security_feed_observations')::text,'missing');"

### Smoke Tests
- psql -d saferpage -Atq -c "select to_regclass('public.security_feed_observations') is not null, to_regclass('public.security_feed_alert_links') is not null, to_regclass('public.security_feed_audit_log') is not null;"
- psql -d saferpage -Atq -c "select count(*) from pg_trigger where tgname in ('security_feed_observations_set_updated_at','security_feed_alert_links_set_updated_at');"
- curl -fsS https://saferpage.de/sicherheit/feed-storage-readiness-json | python3 -m json.tool

### Aktivierung
- DDL-Migration anwenden.
- Readiness-Endpunkt prüfen: Tabellen, Indizes und Trigger müssen ready sein.
- Feed- und Delivery-Secrets serverseitig setzen und PHP/Runner neu laden.
- Canary-Timer installieren/aktivieren und zuerst ohne Storage-Freigabe blockiertes Verhalten prüfen.
- SAFERPAGE_SECURITY_FEED_STORAGE_APPROVED=yes erst nach Betreiberfreigabe setzen.
- Canary-Service manuell starten und storage_canary_stored_observation_count=1 prüfen.
- Runner einmal starten und stored_observation_count kontrollieren.

### Pause/Rollback
- Sofortige Pause ohne DDL-Rollback: SAFERPAGE_SECURITY_FEED_STORAGE_APPROVED entfernen und Runner neu laden.
- Bei fehlerhaften Observations: review_status=suppressed setzen statt Rohdaten zu löschen, sofern Auditpflicht besteht.
- DDL-Drop nur nach Retention-/Audit-Entscheidung und Backupfreigabe ausführen.

### Abnahme
- Readiness zeigt required_table_count=3 und ready_table_count=3.
- Readiness zeigt required_index_count=6 und ready_index_count=6.
- Readiness zeigt required_trigger_count=2 und ready_trigger_count=2.
- Storage-Freigabe ist nur nach dokumentierter Betreiberentscheidung aktiv.
- Runner-State zeigt keine Secret-Werte und keine Feed-Rohpayloads.

## Migration Evidence Receipt
- **DB-Admin-Verantwortung bestätigt** (ready_or_not_required): Preflight zeigt current_database/current_user nur redigiert; Admin-DSN bleibt in sicherer Shell. Verantwortlich: Platform/DBA
- **DDL-Hash festgehalten** (ready): bcaa177b61f0c9ea578e80abcdb8fa2af9c0615fd197f153b7367161603e9453 Verantwortlich: Platform/DBA
- **Preflight vor und nach Migration** (ready): https://saferpage.de/evidence/security-feed-storage-preflight.json Verantwortlich: Platform/Compliance
- **Retention- und Löschregel freigegeben** (required_before_approval): Clean 30 Tage, Treffer/Audit 365 Tage, inconclusive 14 Tage; keine Rohpayloads. Verantwortlich: Datenschutz/Legal
- **Storage-Approval bewusst gesetzt** (blocked_expected): SAFERPAGE_SECURITY_FEED_STORAGE_APPROVED=yes erst nach DDL, Retention, Canary und Routing. Verantwortlich: Betreiber/Trust Ops
- **Synthetischer Canary-Receipt erzeugt** (blocked_until_storage_ready): https://saferpage.de/sicherheit/feed-storage-canary-json Verantwortlich: Security Ops

## Rollback-Rehearsal
- 1. Approval entfernen: SAFERPAGE_SECURITY_FEED_STORAGE_APPROVED entfernen und PHP/Runner neu laden. Erwartung: Neue Storage-Schreibvorgänge blockieren ohne DDL zu ändern.
- 2. Timer pausieren: Feed-Runner- und Canary-Timer stoppen, bevor weitere Observations entstehen. Erwartung: Keine neuen Feed-Jobs starten.
- 3. Audit bewahren: Bestehende security_feed_audit_log-Zeilen nicht löschen; Entscheidungen weiter nachvollziehbar halten. Erwartung: Review- und Suppress-Entscheidungen bleiben prüfbar.
- 4. Suppress statt Hard Delete: Fehlerhafte Observations zunächst review_status=suppressed setzen. Erwartung: Öffentliche Anzeige bleibt aus, Audit bleibt erhalten.
- 5. DDL-Drop nur mit Freigabe: Tabellen nur nach Backup-, Retention- und Legal-Freigabe entfernen. Erwartung: Keine unbeabsichtigte Beweisvernichtung.

## Post-Migration-Canary
- 1. Preflight grün: `scripts/run-security-feed-storage-preflight.sh` Erwartung: missing_artifact_count=0.
- 2. Readiness prüfen: `curl -fsS https://saferpage.de/sicherheit/feed-storage-readiness-json` Erwartung: ready_for_write bleibt ohne Approval false.
- 3. Approval bewusst setzen: `SAFERPAGE_SECURITY_FEED_STORAGE_APPROVED=yes nur serverseitig setzen` Erwartung: Public Export zeigt nur Status, keinen Secret-Wert.
- 4. Canary blockiert testen: `python3 scripts/run-security-feed-schedule.py anrufer.info --storage-canary --dry-run` Erwartung: Keine externe Feed-Abfrage, keine öffentliche Trefferanzeige.
- 5. Canary schreiben: `python3 scripts/run-security-feed-schedule.py anrufer.info --storage-canary --execute` Erwartung: Genau eine synthetische private Observation und Audit-Zeile.
- 6. Runner nachziehen: `curl -fsS https://saferpage.de/sicherheit/feed-runner-json` Erwartung: stored_observation_count plausibel, keine Rohpayloads oder Secrets.

## Feed-Verantwortungs-Handoff
- Status: waiting_for_db_owner_and_operator_approval
- Zusammenfassung: Security-Feed-Handoff für DB-Verantwortung und Security Ops: Storage-Migration, Credential-Referenzen, Canary und Produktclaims werden no-secret getrennt.

### Übergabeschritte
- **Storage-SQL-Hash prüfen** (DB-Verantwortung): SHA-256 der öffentlichen Migration mit der lokal angewendeten Datei vergleichen.
  Abnahme: Hash stimmt und SQL enthält keine Credential-, Webhook- oder Rohpayload-Werte.
  Grenze: Hash beweist Dateigleichheit, nicht erfolgreiche Speicherung.
  Evidence: https://saferpage.de/sicherheit/feed-storage-readiness-json
- **Storage-Artefakte mit DB-Verantwortung anwenden** (DB-Verantwortung/Platform): Tabellen, Indizes und Trigger aus sicherer Shell anwenden und Preflight danach neu erzeugen.
  Abnahme: Tabellen 3/3, Indizes 6/6, Trigger 2/2 sind ready.
  Grenze: Admin-DSN und DB-Host bleiben privat.
  Evidence: https://saferpage.de/evidence/security-feed-storage-preflight.json
- **Retention/DSFA freigeben** (Datenschutz/Legal): Zweck, Datenminimierung, TTLs, Audit und Löschbarkeit vor Storage-Approval dokumentieren.
  Abnahme: Clean/Inconclusive/Hit-Retention und DPIA-Review sind freigegeben.
  Grenze: Öffentlich erscheinen nur Policy-Zusammenfassung und Status, keine internen Datenschutzakten.
  Evidence: https://saferpage.de/sicherheit/feed-storage-readiness-md
- **Synthetischen Storage-Canary ausführen** (Security Ops): Erst blockierten Canary testen, danach mit Storage-Approval genau eine private Observation schreiben.
  Abnahme: Canary speichert publish_decision=not_public und löst keinen Alert/Badge/öffentlichen Treffer aus.
  Grenze: Canary ersetzt keine echten Feed-Credentials und ist kein Malwaretreffer.
  Evidence: https://saferpage.de/sicherheit/feed-storage-canary-json
- **Feed-Credential-Referenzen privat setzen** (Security Ops): URLhaus- und Safe-Browsing-Keys nur als Server-Env oder Secret-Manager-Refs setzen; Rotation dokumentieren.
  Abnahme: Credential-Preflight zeigt Present-Status ohne Werte; Live-Probe bleibt gated.
  Grenze: Public Evidence zeigt nie Feed-Key, Token, Quota-Header oder Rohantwort.
  Evidence: https://saferpage.de/sicherheit/feed-credential-preflight-json
- **Externe Runs erst nach Go-live aktivieren** (Operator/Security): Execute-ready, Storage-Approval, Alert-Routing und Stop-Bedingungen im Go-live-Center abnehmen.
  Abnahme: Security-Feed-Smoke bleibt grün; offene Produktiv-Gates werden nicht als live behauptet.
  Grenze: Ohne Credentials/Freigabe keine Behauptung laufender täglicher Feeds.
  Evidence: https://saferpage.de/betreiber/go-live-json

### Private Inputs
- **Kurzlebiger Admin-DSN**: DDL für Feed-Storage braucht DB-Verantwortungsrechte. Public Handling: Nie öffentlich ausgeben; nur Status und SQL-Hash zeigen.
- **URLhaus-Key-Referenz**: Live-Connector braucht Credential und Quota-Verantwortung. Public Handling: Nur present/missing und Rotationsstatus exportieren.
- **Google-Safe-Browsing-Key-Referenz**: Threat-Match-Abfragen brauchen serverseitigen API-Key. Public Handling: Keine Key-Werte, Quota-Header oder API-Rohantworten exportieren.
- **Storage-Approval-Entscheidung**: Observations dürfen erst nach Retention-/DPIA-Review gespeichert werden. Public Handling: Nur Freigabestatus und Entscheidungsklasse zeigen.
- **Alert-Routing-Zielsysteme**: Feed-Hits können Betreiberalarme auslösen. Public Handling: Keine Webhook-URLs, Empfänger oder Kundenzielsysteme exportieren.
- **Rollback-/Pause-Verantwortlichkeit**: Runner, Storage und Alerts müssen bei Fehlverhalten sofort pausierbar sein. Public Handling: Nur Rollenname und Stop-Bedingungen öffentlich zeigen.

### Öffentlich erlaubte Outputs
- **Migration-SHA-256**: DB-Verantwortung kann die Datei vor Apply prüfen.
- **Tabellen-/Index-/Trigger-Zähler**: Storage-Fortschritt ist ohne Dateninhalt sichtbar.
- **Credential-Present/Missing-Flags**: Go-live-Status bleibt prüfbar, ohne Secrets zu zeigen.
- **Canary-Receipt-Status**: Synthetischer privater Schreibtest ist nachvollziehbar.
- **Review-/Retention-Policy**: Betreiber verstehen Grenzen und Löschregeln.
- **Smoke-Status und Zähler**: Produktreife ist maschinenlesbar, ohne private Details zu leaken.
- **SaferPage-Evidence-URLs**: Abnahme kann verlinkt werden.

### Verbotene Outputs
- Admin-DSN, DB-Host, Port, Passwort oder interner DB-Username
- URLhaus-, Safe-Browsing- oder sonstige Feed-Keys
- Feed-Rohantworten, Malware-Samples, Page Dumps oder DOM-Snapshots aus externen Feeds
- Webhook-, Slack-, Teams-, Jira- oder E-Mail-Zieladressen
- Authorization-Header, Bearer-Token, HMAC-Secrets oder echte Signaturen
- Besucherlogs, IP-Adressen, personenbezogene Requestdetails oder Kundendokumente
- Private Betreiberentscheidung mit Personennamen oder internen Ticket-URLs
- Öffentliche Behauptung eines echten Feed-Treffers ohne Reviewstatus und Betreiberkontext

### Validierungsqueries
- `select coalesce(to_regclass('public.security_feed_observations')::text, 'missing');`
- `select coalesce(to_regclass('public.security_feed_alert_links')::text, 'missing');`
- `select coalesce(to_regclass('public.security_feed_audit_log')::text, 'missing');`
- `select count(*) from pg_indexes where schemaname = 'public' and indexname like 'security_feed_%';`
- `select tgname from pg_trigger where tgname in ('security_feed_observations_set_updated_at', 'security_feed_alert_links_set_updated_at');`

### Handoff-Abnahme
- Storage-Artefakte sind mit DB-Verantwortung angewendet und Preflight zeigt keine fehlenden Pflichtartefakte.
- Storage-Approval ist erst nach Retention/DPIA und Canary-Freigabe aktiv.
- Credential-Preflight zeigt nur Referenzstatus, keine Feed-Key-Werte.
- Synthetischer Canary bleibt privat und erzeugt keine öffentliche Warnung.
- Dry-run ruft keine externen Feeds auf und speichert keine echten Observations.
- Go-live-Center nennt Stop-Bedingungen, Rollback-Verantwortlichkeit und Nachlaufbeobachtung.
- Öffentliche Claims sagen vorbereitet/no-secret belegbar, aber nicht produktiv live ohne Credentials und Freigabe.

## Threat Feed Operations
### Quellenqualität
- **URLhaus** (Malware URL): Credential present, HTTP 200, schema parseable, timestamp fresh. Publish: Nur nach Review als Risiko-Signal, nie als Rechts- oder Sperrlisten-Zertifikat.
- **Google Safe Browsing** (Threat Match): API-Key present, match response normalized, quota/backoff tracked. Publish: Nur Threat-Kategorie und Reviewstatus, keine API-Rohantwort.
- **Security Headers** (Webschutz): Header-Evidence aus passivem Scan und Retry bei Transportfehlern. Publish: Als technischer Befund mit Fix-Guide, nicht als Kompromittierungsbeweis.
- **TLS/HSTS** (Transport Security): Zertifikat, HSTS und Redirect-Ziel getrennt prüfen. Publish: Nur sichtbare Konfiguration, keine interne Infrastruktur behaupten.
- **DAST Passive** (Passive Browser Evidence): Nur passive Requests; keine Angriffs- oder Login-Tests. Publish: Als Prüfhinweis mit Betreiber-Review und klarer Scan-Grenze.
- **Synthetic Canary** (Storage Control): source_id=security_feed_storage_canary, publish_decision=not_public. Publish: Nie als echter Treffer veröffentlichen.

### Observation Review
- 1. **Observation normalisieren**: dedupe_key, source_id, severity, reference_id und publish_decision setzen. Verantwortlich: Security Ops
- 2. **Treffer kontextualisieren**: Domain, Ziel-URL-Hash, Zeitpunkt, Quelle und Wiederholung prüfen. Verantwortlich: Security Analyst
- 3. **False Positive prüfen**: Quelle, Redirect, CDN, historische URL und Betreiberfeedback abgleichen. Verantwortlich: Security Analyst/Betreiber
- 4. **Betreiberaktion ableiten**: Fix-Guide, Re-Scan, Monitoring oder Suppression wählen. Verantwortlich: Security Ops
- 5. **Publish-Grenze entscheiden**: Öffentlich nur Reviewstatus, Schweregrad und sanitisierte Evidence zeigen. Verantwortlich: Trust/Legal
- 6. **Audit und Retention setzen**: after_hash, reason, expires_at und review_status auditieren. Verantwortlich: Compliance

### False Positive Playbook
- **Quelle erneut prüfen**: Re-Check mit derselben Quelle und zweiter passiver Evidence durchführen. Trigger: Betreiber widerspricht oder Feed liefert alten Treffer.
- **Redirect-/CDN-Kontext prüfen**: Final URL, Host, Pfad-Hash und Zeitpunkt getrennt dokumentieren. Trigger: Matched URL zeigt auf CDN, Shortener oder Weiterleitung.
- **Temporär unterdrücken**: review_status=suppressed_pending_review, öffentlich keine Warnung anzeigen. Trigger: Treffer ist plausibel falsch, aber noch nicht belegt.
- **Betreiber-Receipt erzeugen**: Receipt mit Request-ID, Quelle, Zeitpunkt und Entscheidung speichern. Trigger: Betreiber liefert Entlastungsnachweis.
- **Reopen-Bedingung setzen**: Suppression aufheben und Review erneut starten. Trigger: Quelle meldet erneut oder andere Quelle bestätigt.

### Public Claim Policy
- **Kein Zertifikat "malwarefrei"**: Clean bedeutet nur: im passiven Prüfumfang kein aktiver Feed-Treffer.
- **Keine Rechts- oder Compliance-Freigabe**: Security-Feeds liefern technische Signale, keine abschließende rechtliche Bewertung.
- **Warnungen erst nach Review**: Öffentliche Badges/Reports zeigen kritische Feed-Treffer erst nach Betreiber- oder Security-Review.
- **Quelle und Zeitpunkt nennen**: Jeder veröffentlichte Feed-Befund braucht source_id, checked_at und Reviewstatus.
- **Keine Rohpayloads**: Keine Feed-Rohantworten, IPs, Secret-Werte, Besucherlogs oder personenbezogene Daten veröffentlichen.

### Retention/DPIA
- **Clean TTL**: Clean-Momentaufnahmen maximal 30 Tage speichern.
- **Inconclusive TTL**: Unklare oder synthetische Canary-Observations nach 14 Tagen erneuern oder löschen.
- **Treffer-Audit**: Review- und Entscheidungsnachweise bis 365 Tage halten, danach Retention-Review.
- **Datenminimierung**: Nur Hashes, Referenz-IDs und sanitisierte Metadaten speichern.
- **DPIA/DSFA-Review**: Neue Feed-Quellen vor produktiver Speicherung auf Zweck, Datenarten, Drittland und Löschbarkeit prüfen.

### Eskalations-SLO
- **critical_confirmed_feed_hit**: 30 Minuten, Verantwortlich: Security Ops, Aktion: Betreiber informieren, öffentliche Claim-Grenze prüfen, Re-Scan planen.
- **high_unreviewed_feed_hit**: 4 Stunden, Verantwortlich: Security Analyst, Aktion: Quelle, Referenz und False-Positive-Risiko prüfen.
- **medium_storage_or_parse_issue**: 1 Arbeitstag, Verantwortlich: Platform, Aktion: Parser, Storage, Backoff und Runner-State prüfen.
- **low_canary_or_freshness_issue**: 2 Arbeitstage, Verantwortlich: Security Ops, Aktion: Canary, Timer und Quellenfrische nachziehen.
