{
    "schema": "https://saferpage.de/schemas/operator-api-runtime-gate-probe.v1",
    "generated_at": "2026-06-09T21:00:23+00:00",
    "available": true,
    "health": "blocked_by_api_server_gates",
    "summary": "API Runtime Gate Probe: Public-Read-Flächen und Runtime-Kontrollen sind dokumentiert; produktive Operator-/Write-/Admin-Flows bleiben blockiert, bis 7/7 API-Key-Servergates freigegeben sind.",
    "metrics": {
        "probe_count": 24,
        "passed_probe_count": 11,
        "warning_probe_count": 0,
        "blocked_probe_count": 9,
        "public_route_count": 4,
        "protected_route_count": 7,
        "denied_fixture_count": 4,
        "live_denied_fixture_count": 1,
        "write_hmac_fixture_count": 1,
        "write_hmac_fixture_negative_test_count": 4,
        "api_key_gate_count": 7,
        "api_key_gate_passed_count": 0,
        "api_key_gate_blocked_count": 7,
        "runtime_implementation_gate_count": 7,
        "runtime_implementation_gate_passed_count": 7,
        "runtime_implementation_gate_missing_count": 0,
        "api_systemd_service_contract": 1,
        "api_access_storage_ready": 0,
        "api_access_audit_events_24h": 0,
        "api_access_fallback_audit_events_24h": 11
    },
    "api_access_storage_status": {
        "available": true,
        "api_keys_table": false,
        "api_access_audit_log_table": false,
        "audit_events_24h": 0
    },
    "api_access_fallback_audit_status": {
        "available": true,
        "path": "/var/www/saferpage.de/runtime/api_access_audit.jsonl",
        "event_count": 11,
        "events_24h": 11,
        "last_event_at": "2026-06-09T12:34:14+00:00",
        "export_policy": "Fallback-Audit enthält nur Request-ID, Key-Prefix falls vorhanden, Scope, Endpoint, Entscheidung und IP-/UA-Hashes."
    },
    "runtime_implementation_gates": [
        {
            "id": "hashed_key_store_runtime",
            "label": "Key-Store/Hash-Vertrag implementiert",
            "status": "passed",
            "passed": true,
            "evidence": "Migration und Storage-Funktionen deklarieren Prefix, Hash, Scopes, Status und Ablauf.",
            "operator_action": "Mit Migration, Env-Freigaben und echten Test-Keys weiter verifizieren."
        },
        {
            "id": "scope_enforcement_runtime",
            "label": "Scope-Enforcement implementiert",
            "status": "passed",
            "passed": true,
            "evidence": "Operator-Probe weist Scope-Mismatches mit 403 ab und auditiert sie.",
            "operator_action": "Mit Migration, Env-Freigaben und echten Test-Keys weiter verifizieren."
        },
        {
            "id": "access_audit_runtime",
            "label": "Access-Audit/Fallback implementiert",
            "status": "passed",
            "passed": true,
            "evidence": "Postgres-Audit und File-Fallback sind implementiert; Fallback-Events 24h: 11.",
            "operator_action": "Mit Migration, Env-Freigaben und echten Test-Keys weiter verifizieren."
        },
        {
            "id": "rate_limit_runtime",
            "label": "Rate-Limit implementiert",
            "status": "passed",
            "passed": true,
            "evidence": "Operator-Probe prueft Rate-Limits und auditiert 429-Entscheidungen.",
            "operator_action": "Mit Migration, Env-Freigaben und echten Test-Keys weiter verifizieren."
        },
        {
            "id": "revocation_runtime",
            "label": "Revocation/Expiry implementiert",
            "status": "passed",
            "passed": true,
            "evidence": "Revoked/expired Keys werden vor Allow-Entscheidung abgewiesen.",
            "operator_action": "Mit Migration, Env-Freigaben und echten Test-Keys weiter verifizieren."
        },
        {
            "id": "domain_claim_runtime",
            "label": "Domain-Claim implementiert",
            "status": "passed",
            "passed": true,
            "evidence": "Operator-Scopes brauchen Domain-Scope; Mismatches werden nur mit Hash-Evidence auditiert.",
            "operator_action": "Mit Migration, Env-Freigaben und echten Test-Keys weiter verifizieren."
        },
        {
            "id": "write_hmac_runtime",
            "label": "Write-HMAC implementiert",
            "status": "passed",
            "passed": true,
            "evidence": "Write-Scopes verlangen HMAC-Signatur und Idempotency-Key.",
            "operator_action": "Mit Migration, Env-Freigaben und echten Test-Keys weiter verifizieren."
        }
    ],
    "runtime_gates": [
        {
            "id": "public_exports_declared",
            "label": "Public-Read-Exports deklariert",
            "status": "passed",
            "evidence": "Schema, Matrix, Report-Share-Card und Badge sind als Public-Read-Flächen im Router sichtbar.",
            "operator_action": "Public-Exports weiter cachebar und ohne Secret-Felder halten."
        },
        {
            "id": "api_runtime_controls_implemented",
            "label": "API-Runtime-Kontrollen implementiert",
            "status": "passed",
            "evidence": "7/7 Kontrollen im Backend nachweisbar: Key-Store-Vertrag, Scope, Audit, Rate-Limit, Revocation, Domain-Claim, Write-HMAC.",
            "operator_action": "Produktive Env-Gates, Migration und echte Test-Keys separat freigeben."
        },
        {
            "id": "api_access_storage_migrated",
            "label": "API-Key-Store und Access-Auditlog migriert",
            "status": "blocked",
            "evidence": "Tabellen api_keys und api_access_audit_log sind noch nicht beide vorhanden.",
            "operator_action": "scripts/run-api-access-migration-preflight.sh ausfuehren, dann infra/postgres/migrations/api-access.sql mit Admin-DSN anwenden und API-Server neu starten."
        },
        {
            "id": "operator_probe_endpoint_available",
            "label": "Geschuetzter Operator-Probe-Endpunkt implementiert",
            "status": "passed",
            "evidence": "/api/operator/probe prueft Bearer-Key, Scope, Revocation/Expiry und schreibt sanitisierte Audit-Entscheidungen.",
            "operator_action": "Nach Secret-/Key-Freigabe echte 401/403/200-Smoke-Tests mit Test-Key ausfuehren."
        },
        {
            "id": "operator_probe_fallback_audit",
            "label": "Fallback-Audit fuer Deny-Smokes aktiv",
            "status": "passed",
            "evidence": "Fallback-Audit hat 11 sanitisierte Event(s) in den letzten 24h geschrieben.",
            "operator_action": "DB-Auditlog migrieren und Fallback danach nur noch als Notfallpfad nutzen."
        },
        {
            "id": "api_systemd_service_contract",
            "label": "API systemd-Service-Vertrag vorhanden",
            "status": "passed",
            "evidence": "saferpage-api.service und scripts/run-api-service-smoke.sh sind als Runtime-Evidence dokumentiert.",
            "operator_action": "systemctl status saferpage-api.service und scripts/run-api-service-smoke.sh nach jedem Deploy pruefen."
        },
        {
            "id": "no_key_issuance_in_public_probe",
            "label": "Keine Key-Ausstellung im oeffentlichen Probe",
            "status": "passed",
            "evidence": "Diese Seite erzeugt keine Keys, speichert keine Hashes und akzeptiert keine Authorization-Header.",
            "operator_action": "Produktive Key-Ausstellung separat hinter Admin-Auth, Domain-Claim und Auditlog bauen."
        },
        {
            "id": "synthetic_denied_fixtures",
            "label": "Deny-Fixtures dokumentiert",
            "status": "passed",
            "evidence": "4 erwartete Deny-Entscheidungen sind ohne Live-Secret ausformuliert; 1 ist bereits live auditierbar.",
            "operator_action": "Nach Aktivierung der Server-Gates dieselben Faelle als echte Smoke-Tests ausfuehren."
        },
        {
            "id": "runtime_middleware_active",
            "label": "Runtime-Middleware fuer Operator/API aktiv",
            "status": "blocked",
            "evidence": "0/7 API-Key-Servergates aktiv.",
            "operator_action": "Key-Store, Scope-Middleware, Auditlog, Rate-Limit, Revocation, Domain-Claim und Write-HMAC aktivieren."
        }
    ],
    "api_key_server_gates": [
        {
            "id": "hashed_key_store",
            "label": "Gehashter Key-Store aktiv",
            "env_ref": "SAFERPAGE_API_KEY_STORE_READY",
            "status": "blocked",
            "passed": false,
            "owner": "IT/Security",
            "evidence": "SAFERPAGE_API_KEY_STORE_READY ist im Server-Environment nicht aktiv.",
            "operator_action": "Key-Store mit Prefix, Hash, Status, Ablauf und Revocation bereitstellen.",
            "secret_policy": "Nur Env-Referenz und Status anzeigen; keine Keys, Hashes, Salts, Tokens, IPs oder Personenrohdaten."
        },
        {
            "id": "scope_enforcement",
            "label": "Scope-Enforcement aktiv",
            "env_ref": "SAFERPAGE_API_SCOPE_ENFORCEMENT_READY",
            "status": "blocked",
            "passed": false,
            "owner": "Backend",
            "evidence": "SAFERPAGE_API_SCOPE_ENFORCEMENT_READY ist im Server-Environment nicht aktiv.",
            "operator_action": "Middleware fuer Scope, Domain-Claim, Methode und Route aktivieren.",
            "secret_policy": "Nur Env-Referenz und Status anzeigen; keine Keys, Hashes, Salts, Tokens, IPs oder Personenrohdaten."
        },
        {
            "id": "access_audit",
            "label": "Access-Auditlog aktiv",
            "env_ref": "SAFERPAGE_API_ACCESS_AUDIT_READY",
            "status": "blocked",
            "passed": false,
            "owner": "Compliance/IT",
            "evidence": "SAFERPAGE_API_ACCESS_AUDIT_READY ist im Server-Environment nicht aktiv.",
            "operator_action": "Append-only Auditlog fuer Key-Prefix, Scope, Route, Entscheidung und Statuscode anbinden.",
            "secret_policy": "Nur Env-Referenz und Status anzeigen; keine Keys, Hashes, Salts, Tokens, IPs oder Personenrohdaten."
        },
        {
            "id": "rate_limit",
            "label": "Rate-Limits aktiv",
            "env_ref": "SAFERPAGE_API_RATE_LIMIT_READY",
            "status": "blocked",
            "passed": false,
            "owner": "Platform",
            "evidence": "SAFERPAGE_API_RATE_LIMIT_READY ist im Server-Environment nicht aktiv.",
            "operator_action": "Read-, Write- und Admin-Limits je Key und Scope erzwingen.",
            "secret_policy": "Nur Env-Referenz und Status anzeigen; keine Keys, Hashes, Salts, Tokens, IPs oder Personenrohdaten."
        },
        {
            "id": "revocation",
            "label": "Revocation aktiv",
            "env_ref": "SAFERPAGE_API_REVOCATION_READY",
            "status": "blocked",
            "passed": false,
            "owner": "IT/Security",
            "evidence": "SAFERPAGE_API_REVOCATION_READY ist im Server-Environment nicht aktiv.",
            "operator_action": "Sperrentscheidung vor Scope-Entscheidung pruefen und Rotation dokumentieren.",
            "secret_policy": "Nur Env-Referenz und Status anzeigen; keine Keys, Hashes, Salts, Tokens, IPs oder Personenrohdaten."
        },
        {
            "id": "domain_claim",
            "label": "Domain-Claim vor Operator-Zugriff aktiv",
            "env_ref": "SAFERPAGE_API_DOMAIN_CLAIM_READY",
            "status": "blocked",
            "passed": false,
            "owner": "Programm-Owner",
            "evidence": "SAFERPAGE_API_DOMAIN_CLAIM_READY ist im Server-Environment nicht aktiv.",
            "operator_action": "Operator-Keys nur fuer verifizierte Domains oder Portfolios ausstellen.",
            "secret_policy": "Nur Env-Referenz und Status anzeigen; keine Keys, Hashes, Salts, Tokens, IPs oder Personenrohdaten."
        },
        {
            "id": "write_hmac",
            "label": "Write-HMAC aktiv",
            "env_ref": "SAFERPAGE_API_WRITE_HMAC_READY",
            "status": "blocked",
            "passed": false,
            "owner": "IT/Security",
            "evidence": "SAFERPAGE_API_WRITE_HMAC_READY ist im Server-Environment nicht aktiv.",
            "operator_action": "Schreibpfade nur mit Signatur, Idempotency-Key und Audit-Event erlauben.",
            "secret_policy": "Nur Env-Referenz und Status anzeigen; keine Keys, Hashes, Salts, Tokens, IPs oder Personenrohdaten."
        }
    ],
    "public_read_probes": [
        {
            "id": "schema_registry",
            "label": "Schema Registry",
            "method": "GET",
            "path": "/schemas",
            "scope": "schemas:read",
            "expected_runtime_decision": "allow_public_cacheable",
            "status": "passed",
            "route_declared": true,
            "evidence": "Router und Schema Registry deklarieren einen oeffentlichen, cachebaren Schema-Katalog.",
            "url": "https://saferpage.de/schemas"
        },
        {
            "id": "feature_matrix",
            "label": "Feature-Matrix Export",
            "method": "GET",
            "path": "/vergleich/features-json",
            "scope": "schemas:read",
            "expected_runtime_decision": "allow_public_cacheable",
            "status": "passed",
            "route_declared": true,
            "evidence": "Konkurrenzmatrix ist als oeffentlicher JSON-Export deklariert.",
            "url": "https://saferpage.de/vergleich/features-json"
        },
        {
            "id": "public_report_share_card",
            "label": "Public Report Share Card",
            "method": "GET",
            "path": "/anrufer.info/share-card-json",
            "scope": "reports.public:read",
            "expected_runtime_decision": "allow_public_cacheable",
            "status": "passed",
            "route_declared": true,
            "evidence": "Kanonische Domain-Reports und Share-Cards bleiben oeffentlich, ohne Betreiber-Secret.",
            "url": "https://saferpage.de/anrufer.info/share-card-json"
        },
        {
            "id": "public_badge_embed",
            "label": "Public Badge Embed",
            "method": "GET",
            "path": "/badge/anrufer.info/embed-json",
            "scope": "badges.public:read",
            "expected_runtime_decision": "allow_public_cacheable",
            "status": "passed",
            "route_declared": true,
            "evidence": "Status-Badges sind oeffentliche Trust-Links ohne interne Rohdaten.",
            "url": "https://saferpage.de/badge/anrufer.info/embed-json"
        }
    ],
    "protected_route_contract": [
        {
            "scope": "reports:read",
            "tier": "operator_read",
            "methods": [
                "GET"
            ],
            "example_path": "/api/operator/probe?scope=reports:read",
            "runtime_decision": "blocked_until_api_access_tables_migrated",
            "required_gates": [
                "api_keys_table",
                "api_access_audit_log_table",
                "hashed_key_store",
                "scope_enforcement",
                "access_audit"
            ]
        },
        {
            "scope": "reports:read",
            "tier": "operator_read",
            "methods": [
                "GET"
            ],
            "example_path": "/report-pack/anrufer.info/export",
            "runtime_decision": "blocked_until_domain_claim_scope_audit_ready",
            "required_gates": [
                "hashed_key_store",
                "scope_enforcement",
                "access_audit",
                "rate_limit",
                "revocation",
                "domain_claim"
            ]
        },
        {
            "scope": "portfolio:read",
            "tier": "operator_read",
            "methods": [
                "GET"
            ],
            "example_path": "/portfolio/audit-json",
            "runtime_decision": "blocked_until_domain_claim_scope_audit_ready",
            "required_gates": [
                "hashed_key_store",
                "scope_enforcement",
                "access_audit",
                "rate_limit",
                "revocation",
                "domain_claim"
            ]
        },
        {
            "scope": "evidence:read",
            "tier": "operator_read",
            "methods": [
                "GET"
            ],
            "example_path": "/nachweise/anrufer.info/export",
            "runtime_decision": "blocked_until_sanitization_and_domain_claim_ready",
            "required_gates": [
                "hashed_key_store",
                "scope_enforcement",
                "access_audit",
                "rate_limit",
                "revocation",
                "domain_claim"
            ]
        },
        {
            "scope": "tickets:write",
            "tier": "operator_write",
            "methods": [
                "POST"
            ],
            "example_path": "/fix-guides/anrufer.info/tickets-delivery-json",
            "runtime_decision": "blocked_until_write_hmac_and_audit_ready",
            "required_gates": [
                "hashed_key_store",
                "scope_enforcement",
                "access_audit",
                "rate_limit",
                "revocation",
                "domain_claim",
                "write_hmac"
            ]
        },
        {
            "scope": "dispatch:write",
            "tier": "operator_write",
            "methods": [
                "POST"
            ],
            "example_path": "/portfolio/dispatch-json",
            "runtime_decision": "blocked_until_write_hmac_rate_limit_and_approval_ready",
            "required_gates": [
                "hashed_key_store",
                "scope_enforcement",
                "access_audit",
                "rate_limit",
                "revocation",
                "domain_claim",
                "write_hmac"
            ]
        },
        {
            "scope": "keys:rotate",
            "tier": "admin",
            "methods": [
                "POST"
            ],
            "example_path": "/api-zugriff/key-readiness-json",
            "runtime_decision": "blocked_until_admin_auth_audit_and_revocation_ready",
            "required_gates": [
                "hashed_key_store",
                "scope_enforcement",
                "access_audit",
                "rate_limit",
                "revocation"
            ]
        }
    ],
    "denied_fixtures": [
        {
            "id": "missing_authorization",
            "request": "GET /api/operator/probe?scope=reports:read ohne Authorization",
            "expected_status_code": 401,
            "expected_decision": "deny_missing_authorization",
            "implemented_as_public_fixture": true,
            "live_denial_endpoint_active": true,
            "audit_evidence": "file_fallback_events_24h=11",
            "evidence": "Der Live-Operator-Probe verweigert fehlende Bearer-Authorization mit 401 und schreibt sanitisiertes Audit."
        },
        {
            "id": "invalid_scope",
            "request": "GET /nachweise/anrufer.info/export mit Scope reports.public:read",
            "expected_status_code": 403,
            "expected_decision": "deny_scope_mismatch",
            "implemented_as_public_fixture": true,
            "live_denial_endpoint_active": false,
            "evidence": "Evidence-Exports brauchen evidence:read, Domain-Claim und Auditlog."
        },
        {
            "id": "unsigned_write",
            "request": "POST Ticket-Delivery ohne X-SaferPage-Signature und Idempotency-Key",
            "expected_status_code": 403,
            "expected_decision": "deny_missing_hmac_or_idempotency",
            "implemented_as_public_fixture": true,
            "live_denial_endpoint_active": false,
            "evidence": "Schreibpfade bleiben bis Write-HMAC und Rate-Limit blockiert."
        },
        {
            "id": "revoked_key",
            "request": "GET Portfolio-Export mit revokiertem Key-Prefix",
            "expected_status_code": 401,
            "expected_decision": "deny_revoked_key",
            "implemented_as_public_fixture": true,
            "live_denial_endpoint_active": false,
            "evidence": "Revocation muss vor Scope-Entscheidung wirken."
        }
    ],
    "write_hmac_test_fixture": {
        "purpose": "Öffentlicher Testfall fuer Operator-API-Write-Clients. Nicht als produktives Secret verwenden.",
        "algorithm": "HMAC-SHA256",
        "signature_header": "X-SaferPage-Signature",
        "idempotency_header": "X-SaferPage-Idempotency-Key",
        "secret_ref": "SAFERPAGE_API_WRITE_HMAC_SECRET",
        "test_secret": "saferpage_api_write_test_secret_do_not_use_in_production",
        "method": "POST",
        "endpoint": "/api/operator/probe",
        "scope": "tickets:write",
        "idempotency_key": "sp-api-write-test-fixture",
        "canonical_string": "POST\n/api/operator/probe\ntickets:write\nsp-api-write-test-fixture",
        "canonical_string_sha256": "d88fdc61e9afd95ceb9d17054bb86405bff1463547b9a6d7f7cd845bbe6bddc7",
        "expected_signature": "sha256=f67e8e5281ec206981536f06eb55011b7537dbda2cfc28b1397c73464ff6f314",
        "expected_signature_header": "X-SaferPage-Signature: sha256=f67e8e5281ec206981536f06eb55011b7537dbda2cfc28b1397c73464ff6f314",
        "positive_test": "Client berechnet ueber exakt diesen kanonischen String dieselbe Signatur und sendet sie mit Idempotency-Key.",
        "negative_tests": [
            "Methode von POST auf GET aendern: Signatur muss abgelehnt werden.",
            "Scope von tickets:write auf reports:read aendern: Signatur muss abgelehnt werden.",
            "Idempotency-Key aendern oder wiederverwenden: Payload muss abgelehnt oder als Duplikat behandelt werden.",
            "Signature-Prefix entfernen oder falschen Algorithmus nutzen: Request muss abgelehnt werden."
        ],
        "sample_commands": {
            "canonical_string_sha256": "printf %s \"$CANONICAL\" | sha256sum",
            "expected_signature": "printf %s \"$CANONICAL\" | openssl dgst -sha256 -hmac \"saferpage_api_write_test_secret_do_not_use_in_production\" -binary | xxd -p -c 256",
            "curl_dry_run": "curl -X POST \"https://saferpage.de/api/operator/probe?scope=tickets%3Awrite&domain=anrufer.info\" -H \"Authorization: Bearer sp_live_testfixture.invalid\" -H \"X-SaferPage-Idempotency-Key: sp-api-write-test-fixture\" -H \"X-SaferPage-Signature: sha256=f67e8e5281ec206981536f06eb55011b7537dbda2cfc28b1397c73464ff6f314\""
        }
    },
    "operator_sequence": [
        "Key-Store mit Prefix, Hash, Ablauf und Revocation-Feldern produktiv bereitstellen.",
        "Vor Key-Ausgabe scripts/run-api-runtime-deny-smoke.sh ausfuehren und 401 plus sanitisiertes Audit pruefen.",
        "Scope- und Domain-Claim-Middleware fuer alle Operator-Read-, Write- und Admin-Pfade aktivieren.",
        "Access-Auditlog, Rate-Limit und Revocation vor Key-Ausgabe testen.",
        "Write-HMAC und Idempotency-Key fuer Tickets, Dispatches und Webhooks erzwingen.",
        "Erst danach echte Test-Keys ausstellen und 401/403/429/Revocation-Smoke-Tests ausfuehren."
    ],
    "links": {
        "html": "https://saferpage.de/api-zugriff/runtime-gate-probe",
        "json": "https://saferpage.de/api-zugriff/runtime-gate-probe-json",
        "csv": "https://saferpage.de/api-zugriff/runtime-gate-probe-csv",
        "markdown": "https://saferpage.de/api-zugriff/runtime-gate-probe-md",
        "api_access": "https://saferpage.de/api-zugriff/export",
        "key_readiness": "https://saferpage.de/api-zugriff/key-readiness-json",
        "parity_readiness": "https://saferpage.de/vergleich/parity-readiness-json",
        "integration_setup": "https://saferpage.de/integrationen/setup-json",
        "schema": "https://saferpage.de/schemas/operator-api-runtime-gate-probe.v1"
    },
    "disclaimer": "Dieser Probe ist ein oeffentlicher No-Secret-Nachweis. Er erstellt keine API-Keys, prueft keine echten Authorization-Header, speichert keine Hashes und fuehrt keine Schreibaktion aus. Echte Live-Deny-Smoke-Tests sind erst nach aktivierten Server-Gates sinnvoll."
}
