# AUTH.3E — EXECUTOR COMMAND CHANNEL + RUNTIME BOUNDARY CONTRACT **Status:** DESIGN + CODE + ISOLATED TESTS + REVIEW ONLY (KEIN produktives Deployment) **Datum:** 2026-08-27 **Mission Type:** SECURITY DESIGN + CODE + ISOLATED TESTS + REVIEW ONLY **Baseline:** AUTH.1 (40bbc40) + AUTH.2 (edcb4902) + AUTH.3A (a11c1bb) + AUTH.3B + AUTH.3C + AUTH.3D (9c8d239) = COMPLETE > ⚠️ Dies ist ein **Design-/Code-/Test-Dokument**. Es wird KEIN produktiver Umbau > ausgeführt, kein Container erstellt, keine Compose/Network/Volume/DB geändert, > kein echtes Credential erzeugt, keine ENV geändert, kein Deployment, keine > produktive Tolaria-Mutationsprobe, keine Hermes-Autonomie. Bei Zweifel: > FAIL CLOSED + HARD STOP. --- ## 1. CURRENT RUNTIME DISCOVERY (Phase 2) — rekonstruiert (READ-ONLY) > Keine Annahmen. UNKNOWN bleibt UNKNOWN. Alle Fakten aus Code-Reading verifiziert. ### 1a. SAVE-Pfad: RQ/Hermes → C5 CLI → C5CPropagator → TolariaClient.write() ``` RQ/Hermes (Container 06a4a73573d0, User hermes uid 10010) │ ├─ [Entry Point] rq_c5_cli.py (argparse CLI, manuell aufgerufen) │ KEIN CLI-Command führt C5CPropagator mit save_token aus (verifiziert) │ ├─ [Library] rq_c5c.py C5CPropagator (Z.497) │ __init__(store, reader, client=None, ...) → client = client or TolariaClient() │ Default: credential-frei (kein save_token) │ write() → self.client.write(vault_path, after_content) (Z.638) │ └─ [Library] rq_c5c.py TolariaClient.write() (Z.322) token = self._require_token(self.save_token, "SAVE") # fail-closed → POST /api/vault/save (Bearer save_token) ``` **Boundary-Fakten (SAVE):** - **Process Boundary:** C5CPropagator läuft im RQ/Hermes-Prozess (kein separater Prozess). - **Container Boundary:** RQ-Container `06a4a73573d0` (User `hermes`, uid 10010, gid 10000). - **Filesystem Boundary:** `/opt/data` (RQ-Container-Dateisystem); C5-DB `/opt/data/c5c_live/c5a.db`. - **ENV Boundary:** `TOLARIA_SAVE_TOKEN` ABSENT im RQ-Container (verifiziert). `C5A_DB` steuert DB-Pfad. - **Network Boundary:** Tolaria `187.124.31.123:5173` (auch 10.0.4.1/10.0.9.1); Forgejo 10.0.4.1:3000/10.0.9.1:3000. - **DB-Zugriff:** C5AStore → SQLite `c5a.db` (Commit-State, ObjectChanges, Approvals, Ledger). - **Forgejo-Zugriff:** GitReader liest Repo `nexo312/trading-system-docs` (Forgejo-Git-Push-Key). - **Tolaria-Zugriff:** `write()` (SAVE) via `TolariaClient`. - **Approval-DB-Zugriff:** C5AStore (delete_approvals-Tabelle) — SAVE-Pfad liest/schreibt sie nicht aktiv. - **Benötigte Inputs:** Commit-SHA, ObjectChanges, Source-/Provenance-Daten aus Forgejo. - **Erzeugte Outputs:** Tolaria-SAVE, C5-State-Update (PROPAGATING→VERIFYING→UPDATING_SEARCH). ### 1b. DELETE-Pfad: RQ/Hermes → C5 CLI → DeleteExecutor → Approval Validation → TolariaClient.delete() ``` RQ/Hermes (Container 06a4a73573d0) │ ├─ [Entry Point] rq_c5_cli.py cmd_c5_delete_approve (Z.355) → persistiert Human-Approval ├─ [Entry Point] rq_c5_cli.py cmd_c5_delete_execute (Z.424) → führt DELETE aus ├─ [Entry Point] rq_c5_cli.py cmd_c5_delete_replay (Z.458) → idempotenter Replay │ ├─ [Helper] rq_c5_cli.py _delete_executor (Z.344) │ TolariaClient(base_url, delete_token=os.environ.get(ENV_TOLARIA_DELETE_TOKEN)) │ → liest NUR DELETE-Token (kein SAVE-Token) │ ├─ [Library] rq_c5_delete.py DeleteExecutor (Z.83) │ __init__(store, client=None) → client = client or TolariaClient() (Default credential-frei) │ execute(commit_sha) (Z.192): │ Gate 1: ObjectChange existiert, operation == DELETE │ Gate 2: Commit-Status == ST_DELETE_APPROVED │ Gate 3: Approval existiert, APPROVED, passt exakt (Commit/Objekt/Pfad) │ Gate 4: Pre-Delete-Drift-Check (Ziel existiert noch) │ → _do_delete() → self.client.delete() (DELETE) │ └─ [Library] rq_c5c.py TolariaClient.delete() (Z.~352) token = self._require_token(self.delete_token, "DELETE") # fail-closed → POST /api/vault/delete (Bearer delete_token) ``` **Boundary-Fakten (DELETE):** - **Process Boundary:** DeleteExecutor läuft im RQ/Hermes-Prozess (kein separater Prozess). - **Container Boundary:** RQ-Container `06a4a73573d0` — **SELBER Container wie SAVE**. - **Filesystem Boundary:** `/opt/data`; C5-DB `/opt/data/c5c_live/c5a.db`. - **ENV Boundary:** `TOLARIA_DELETE_TOKEN` ABSENT im RQ-Container (verifiziert). - **Network Boundary:** Tolaria `187.124.31.123:5173`. - **DB-Zugriff:** C5AStore → SQLite `c5a.db` (Approvals, Ledger, Commit-State). - **Forgejo-Zugriff:** GitReader (Provenance-/Object-Daten). - **Tolaria-Zugriff:** `delete()` (DELETE) via `TolariaClient`. - **Approval-DB-Zugriff:** C5AStore delete_approvals-Tabelle (lesen + USED markieren). - **Benötigte Inputs:** Commit-SHA, persistierte Approval, ObjectChange, Nonce. - **Erzeugte Outputs:** Tolaria-DELETE, Approval→USED, Commit→ST_UPDATING_SEARCH. ### 1c. Kernbefund (bestätigt AUTH.3B/3C) **SAVE- und DELETE-Caller laufen im SELBEN Prozess/Container** (`rq_c5_cli.py`, RQ-Container `06a4a73573d0`). **CREDENTIAL_SEPARATION_STATUS = LOGICAL_ONLY.** Sobald Credentials produktiv in denselben Container injiziert würden, wäre RQ_SAVE_ACCESS/DELETE_ACCESS technisch NICHT mehr NO. → AUTH.4 BLOCKED. ### 1d. DB-Schema (C5AStore, `c5a.db`) — rekonstruiert - **Tabellen:** `commits` (commit_sha, parent_sha, discovered_at, sequence, status, retry_count, last_error, last_error_code, updated_at), `objects` (commit_sha, object_id, path_before, path_after, operation, content_hash_before/after, metadata_hash_before/after, representation, state, reason_code; UNIQUE(commit_sha, object_id, operation)), `delete_approvals` (approval_id, workflow_commit, object_change_id, object_id, path, operation='DELETE', approval_status APPROVED/USED/REVOKED, approved_by, approval_nonce, approved_at, used_at; UNIQUE(workflow_commit, object_change_id, object_id, path)), `kv` (key, value — für search_source_provenance). - **State Machine (commits.status):** DISCOVERED → VALIDATING → READY → PROPAGATING_TOLARIA → VERIFYING_TOLARIA → UPDATING_SEARCH → VERIFYING_SEARCH → APPLIED; plus RETRY_PENDING, FAILED, DEAD, HUMAN_REVIEW_REQUIRED, WAITING_FOR_PREDECESSOR, DELETE_APPROVED, DELETING. - **Locks/Transactions:** SQLite; `_init_schema` erstellt Tabellen; `upsert_commit` nutzt `INSERT ... ON CONFLICT DO UPDATE` (idempotent); `add_object_change` nutzt `INSERT OR IGNORE` (idempotent per UNIQUE). Kein expliziter WAL-Modus im Code gesehen (Default SQLite). - **Idempotenz:** `SyncStateMachine.idempotency_check` (commit_sha+object_id+operation; ALREADY_APPLIED/ALREADY_AT_TARGET/RETRY_SAFE/CONFLICT); `add_object_change` INSERT OR IGNORE; `upsert_commit` ON CONFLICT. - **Retry:** `retry_commit` (increment_retry, max_retries → DEAD, sonst RETRY_PENDING + backoff); `_is_retryable` in C5C (nur TolariaUnavailableError retryable). - **Replay:** `c5-delete-replay` (idempotent, nutzt denselben `_delete_executor`); Approval single-use (→ USED). - **Recovery:** `c5e-recover`, `c5e-reconcile` (C5E); `c5e-replay --allow-writes` nur Test-/Canary-Scope. - **Provenance:** `persist_search_source_provenance` (kv-Tabelle, key `search_source_provenance:`); `get_source_provenance`. - **Mission Binding:** **FEHLT** in C5A-Commits/ObjectChanges (kein mission_id-Feld). Nur in AUTH.3D-Approval-Payload (mission_id) vorhanden. - **Object Binding:** `objects.object_id` (UNIQUE mit commit_sha+operation); Approval object_id-bound. - **Commit Binding:** `commits.commit_sha`; Approval workflow_commit-bound. ### 1e. AUTH.3D-Approval (rekonstruiert, `approval_payload.py`/`approval_state.py`/`approval_verifier.py`) - **Payload (12 kanonische Felder):** approval_version, approval_id, mission_id, delete_request_id, operation, object_id, vault_path, expected_commit, expected_provenance_hash, nonce, issued_at, expires_at. - **Lifecycle:** CREATED → VERIFIED → RESERVED → EXECUTED → CONSUMED (atomare Reservation, TOCTOU-sicher). - **Verifier:** verify-only (Ed25519 PUBLIC KEY ONLY), importiert nur key_id, verify. - **Signature:** `approval_signature.py` enthält sign()/private_key_to_pem() NUR zur Simulation; KEIN Private Key im Stack. - **Bindings:** Object, Commit, Path, Operation, Single-Use, Nonce, Expiry, Mission (in Payload). --- ## 2. COMMAND CHANNEL OPTIONS (Phase 3) — Bewertung > Keine neue Infrastruktur bevorzugen, wenn eine einfachere bestehende Primitive > die Sicherheitsanforderungen erfüllt. | Kriterium | A) Shared SQLite Job DB | B) Getrennte SQLite Inbox pro Executor | C) Filesystem Inbox / atomic rename | D) Interner HTTP Executor Endpoint | E) RabbitMQ / Broker | F) Unix Socket / IPC | G) C5 State Machine (bestehend) | |---|---|---|---|---|---|---|---| | SECURITY | Mittel | **Hoch** | Mittel | Mittel | Hoch | Mittel | **Hoch** | | COMPLEXITY | Niedrig | **Niedrig** | Niedrig | Mittel | **Hoch** | Mittel | **Niedrig** | | FAIL_CLOSED | Ja | **Ja** | Ja | Ja | Ja | Ja | **Ja** | | AUDITABILITY | Hoch | **Hoch** | Mittel | Hoch | Hoch | Mittel | **Hoch** | | IDEMPOTENCY | Ja | **Ja** | Teilweise | Ja | Ja | Ja | **Ja** | | REPLAY_RESISTANCE | Teilweise | **Ja** | Teilweise | Ja | Ja | Ja | **Ja** | | CRASH_RECOVERY | Ja | **Ja** | Ja | Ja | Ja | Ja | **Ja** | | CONCURRENCY | Mittel (Lock) | **Hoch (getrennte DBs)** | Mittel | Hoch | Hoch | Mittel | **Hoch** | | RQ_SCOPE_ESCALATION | Nein | **Nein** | Nein | Nein | Nein | Nein | **Nein** | | SECRET_EXPOSURE | Kein Secret | **Kein Secret** | Kein Secret | Kein Secret | Kein Secret | Kein Secret | **Kein Secret** | | FILESYSTEM_BOUNDARY | Geteilt | **Getrennt** | Getrennt | Getrennt | Getrennt | Getrennt | **Getrennt** | | NETWORK_BOUNDARY | Kein Netz | **Kein Netz** | Kein Netz | Netz nötig | Netz nötig | Kein Netz | **Kein Netz** | | AUTH3D_COMPATIBILITY | Ja | **Ja** | Ja | Ja | Ja | Ja | **Ja** | | CURRENT_C5_COMPATIBILITY | Mittel | **Hoch** | Mittel | Niedrig | **Niedrig** | Niedrig | **Hoch** | | AUTH4_DEPLOYMENT_COMPLEXITY | Niedrig | **Niedrig** | Niedrig | Mittel | **Hoch** | Mittel | **Niedrig** | **Bewertung:** - **OPTION B (getrennte SQLite Inbox pro Executor)** ist die **bevorzugte** Wahl: Sie erfüllt die geforderte **GETRENNTE INBOX/STATE BOUNDARY** (SAVE-Executor claimt nur SAVE-Jobs, DELETE-Executor nur DELETE-Jobs), nutzt vorhandene SQLite-Primitive (keine neue Infrastruktur), ist fail-closed, auditierbar, idempotent, replay-resistent und crash-recoverable. Getrennte DB-Dateien (`c5a_save.db`/`c5a_delete.db`) vermeiden Lock-Konflikte zwischen SAVE- und DELETE-Executor. - **OPTION A (Shared SQLite Job DB)** ist einfacher, aber teilt die DB → schwächere DB-Boundary (SAVE-Executor könnte DELETE-Jobs sehen, wenn Scope nicht strikt getrennt). - **OPTION C (Filesystem Inbox)** ist einfach, aber weniger robust (kein atomarer Claim/Lease, schwächere Idempotenz/Replay-Resistenz). - **OPTION D (HTTP Endpoint)** erfordert neue Netzwerk-Endpoints → Scope-Creep. - **OPTION E (RabbitMQ/Broker)** erfordert neue Infrastruktur → **abgelehnt** (nicht vorhanden). - **OPTION F (Unix Socket)** erfordert neue IPC-Infrastruktur → Scope-Creep. - **OPTION G (C5 State Machine)** ist die bestehende Primitive für DELETE (Approval persistiert, Nonce, Single-Use, Replay-Schutz) und wird als **Komplement** zu B genutzt: Der DELETE-Job verweist auf die persistierte AUTH.3D-Approval; der DELETE-Executor validiert Job UND Approval unabhängig. **Empfehlung:** **OPTION B (getrennte SQLite Inbox pro Executor) als Command Channel**, kombiniert mit **OPTION G (C5 State Machine)** für die DELETE-Approval-Autorität. Keine neue Infrastruktur. --- ## 3. PREFERRED ARCHITECTURAL PROPERTY (Phase 4) **GETRENNTE INBOX / STATE BOUNDARIES.** ``` RQ/Hermes (Container 06a4a73573d0) — credential-frei │ credential-freier Job-Request (kein Secret) ▼ +---------------------------+ +-----------------------------+ | c5-save-executor (Cont.) | | c5-delete-executor (Cont.) | | SAVE credential | | DELETE credential | | NO DELETE | | NO SAVE | | claimt NUR SAVE-Jobs | | claimt NUR DELETE-Jobs | | c5a_save.db (Inbox) | | c5a_delete.db (Inbox) | +---------------------------+ +-----------------------------+ │ SAVE (Bearer save_token) │ DELETE (Bearer delete_token) ▼ ▼ Tolaria Server (187.124.31.123:5173) — erwartet SAVE+DELETE-Verifier ``` **Kein generischer Dispatcher.** Kein `command="..."`, `endpoint="..."`, `url="..."`, `method="..."`, `shell="..."`, `python="..."`, `handler="..."`. Keine RCE-artige Semantik. **Job Types als geschlossene Allowlist:** - `C5_SAVE_OBJECT` - `C5_DELETE_OBJECT` Gegen die reale C5-Architektur geprüft: Die C5-Operationen sind `OP_CREATE`, `OP_CONTENT_UPDATE`, `OP_METADATA_UPDATE`, `OP_STATE_UPDATE`, `OP_TAGS_UPDATE`, `OP_SOURCE_CANONICAL_RELATION_UPDATE`, `OP_RENAME`, `OP_MOVE`, `OP_DELETE_REQUEST`, `OP_SUPERSEDE`. Für den Command Channel werden diese auf zwei geschlossene Job-Types abgebildet: **SAVE** (alle Nicht-DELETE-Operationen, die Tolaria-Write erfordern) und **DELETE** (nur `OP_DELETE_REQUEST`). Kein generischer Operation-Dispatcher. --- ## 4. SAVE JOB CONTRACT (Phase 5) ### Kanonischer SAVE Job ```json { "job_version": 1, "job_id": "uuid4", "mission_id": "uuid4", "job_type": "C5_SAVE_OBJECT", "object_id": "string", "vault_path": "/app/vault/...", "source_commit": "sha256", "provenance_hash": "sha256", "expected_state": "string", "created_at": "ISO8601 UTC", "idempotency_key": "uuid4" } ``` ### Content-Entscheidung: A) Content im Job ODER B) Executor lädt selbst **Gewählt: B) Executor rekonstruiert sicherheitsrelevante Daten selbst aus autoritativer Source (Forgejo).** Begründung (Manipulationsrisiko): - Wenn Content im Job transportiert würde, könnte RQ (oder ein kompromittierter RQ-Prozess) beliebigen Content in den Job schreiben → der SAVE-Executor würde manipulierten Content nach Tolaria schreiben. - **Bevorzugte Eigenschaft (aus Mission):** Executor rekonstruiert sicherheitsrelevante Daten soweit möglich selbst aus autoritativen Quellen, statt RQ blind zu vertrauen. - Der SAVE-Executor lädt den Content selbst aus Forgejo (source_commit + object_id → GitReader.file_content) und validiert gegen provenance_hash/expected_state. **RQ darf NICHT bestimmen:** - Tolaria Base URL (Executor-Konfiguration, nicht Job) - HTTP Method (fix: POST /api/vault/save) - Authorization Header (Executor-Credential, nicht Job) - Credential (Executor-Credential, nicht Job) - beliebigen Zielendpoint (fix: /api/vault/save) --- ## 5. DELETE JOB CONTRACT (Phase 6) ### Kanonischer DELETE Job (strenger) ```json { "job_version": 1, "job_id": "uuid4", "mission_id": "uuid4", "delete_request_id": "uuid4", "job_type": "C5_DELETE_OBJECT", "object_id": "string", "vault_path": "/app/vault/...", "expected_commit": "sha256", "expected_provenance_hash": "sha256", "approval_id": "uuid4", "created_at": "ISO8601 UTC", "idempotency_key": "uuid4" } ``` **WICHTIG: Der DELETE Job selbst ist KEINE Approval.** DeleteExecutor muss unabhängig laden/verifizieren (AUTH.3D Approval Artifact): - Ed25519 Signature (verify-only, PUBLIC KEY ONLY) - mission_id - delete_request_id - object_id - vault_path - expected_commit - expected_provenance_hash - nonce - expiry - single-use state **Job und Approval müssen exakt zusammenpassen. Mismatch → DELETE_DENIED.** --- ## 6. TRUST MODEL (Phase 7) ### RQ_JOB_FIELDS_TRUST_LEVEL | Feld | SAVE | DELETE | Klassifikation | |---|---|---|---| | job_version | TRUSTED (Schema) | TRUSTED (Schema) | Schema-Validierung | | job_id | TRUSTED (Identität) | TRUSTED (Identität) | Idempotenz-Schlüssel | | mission_id | VERIFY_AGAINST_SOT | VERIFY_AGAINST_SOT | Gegen Approval/Provenance | | job_type | TRUSTED (Allowlist) | TRUSTED (Allowlist) | Geschlossene Allowlist | | object_id | VERIFY_AGAINST_SOT | VERIFY_AGAINST_SOT | Gegen Forgejo/Approval | | vault_path | VERIFY_AGAINST_SOT | VERIFY_AGAINST_SOT | Gegen ObjectChange/Approval + Path Safety | | source_commit | VERIFY_AGAINST_SOT | — | Gegen Forgejo | | provenance_hash | RECOMPUTE | VERIFY_AGAINST_SOT | Executor rechnet selbst | | expected_state | VERIFY_AGAINST_SOT | — | Gegen Forgejo | | expected_commit | — | VERIFY_AGAINST_SOT | Gegen Approval | | expected_provenance_hash | — | VERIFY_AGAINST_SOT | Gegen Approval | | approval_id | — | VERIFY_AGAINST_SOT | Gegen AUTH.3D Approval | | delete_request_id | — | VERIFY_AGAINST_SOT | Gegen Approval | | created_at | TRUSTED (Metadaten) | TRUSTED (Metadaten) | Audit | | idempotency_key | TRUSTED (Idempotenz) | TRUSTED (Idempotenz) | Replay-Schutz | **Grundsatz: DATA FROM RQ != AUTHORITY.** RQ ist Orchestrator, nicht Security Authority. Für sicherheitskritische Felder: VERIFY_AGAINST_SOT oder RECOMPUTE. --- ## 7. JOB STATE MACHINE (Phase 8) ### Deterministische State Machine ``` CREATED → READY → CLAIMED → EXECUTING → SUCCEEDED │ ├→ FAILED ├→ REJECTED └→ OUTCOME_UNKNOWN ``` **Erlaubte Transitionen (geschlossene Allowlist):** - CREATED → READY (Schema validiert, Job akzeptiert) - CREATED → REJECTED (Schema/Allowlist/Injection-Fehler) - READY → CLAIMED (atomarer Claim) - CLAIMED → EXECUTING (Claim bestätigt) - EXECUTING → SUCCEEDED (bestätigter Erfolg) - EXECUTING → FAILED (bestätigter Fehler, kein Mutationseffekt) - EXECUTING → OUTCOME_UNKNOWN (unklares HTTP-Ergebnis) - EXECUTING → REJECTED (Gate-Fehler während Execution) **RETRYABLE:** Optional, nur sicher begründet. Für SAVE: RETRYABLE nur bei TolariaUnavailableError (transient, kein Mutationseffekt). Für DELETE: **KEIN RETRYABLE** — ein bereits gestarteter DELETE darf nicht wegen Lease-Expiry von einem zweiten Worker wiederholt werden. OUTCOME_UNKNOWN → kein blinder Retry. **Ungültige Transition → FAIL CLOSED** (InvalidTransitionError). --- ## 8. CLAIM / LEASE / CONCURRENCY (Phase 9) ### Claim-Modell - **worker_id:** UUID des Executors (SAVE-Executor vs DELETE-Executor). - **claim_id:** UUID pro Claim. - **claimed_at:** Timestamp. - **lease_until:** Timestamp (Claim-Expiry). - **attempt_count:** Zähler. ### Atomarer Claim Der Claim muss **atomar** sein (SQLite `UPDATE ... WHERE status='READY' AND (lease_until IS NULL OR lease_until < now) RETURNING ...` in einer Transaktion). Kein TOCTOU. ### Concurrency-Prüfung - **Zwei SAVE Worker:** Nur einer kann denselben Job atomar claimen (Status-Transition READY→CLAIMED ist atomar). - **Zwei DELETE Worker:** Nur einer kann denselben DELETE-Job claimen. **Ein bereits gestarteter DELETE darf NICHT einfach wegen Lease-Expiry von einem zweiten Worker wiederholt werden.** → DELETE-Job, der in EXECUTING ist, wird bei Lease-Expiry in OUTCOME_UNKNOWN überführt (nicht zurück zu READY), und ein zweiter Worker darf ihn NICHT erneut claimen. Nur ein expliziter Human-/Admin-Eingriff (AUTH.3D-Approval erneut verifizieren) kann einen DELETE-Job aus OUTCOME_UNKNOWN reaktivieren. - **Worker Crash nach Claim:** Lease-Expiry → Job wird für SAVE wieder claimbar (RETRYABLE), für DELETE → OUTCOME_UNKNOWN (nicht blind wiederholen). - **Stale Claim:** Lease-Expiry erkennt stale Claims. - **Restart / Doppelstart / Race:** Atomarer Claim + Lease verhindern Doppelverarbeitung. --- ## 9. IDEMPOTENCY (Phase 10) ### SAVE Idempotenz über: `job_id` + `idempotency_key` + `object_id` + `source_commit`/`provenance`. - Gleicher Job (job_id) → bereits verarbeitet → kein zweiter Write. - Gleicher idempotency_key → idempotent (kein zweiter Write). - Gleicher object_id + source_commit → ALREADY_AT_TARGET (Ziel-Hash erreicht). ### DELETE (strenger) - Nach bestätigtem DELETE: gleicher Job niemals erneut mutieren. - Bei unklarem HTTP-Ergebnis: **OUTCOME_UNKNOWN. KEIN BLINDER RETRY.** - AUTH.3D OUTCOME_UNKNOWN-Modell bleibt erhalten. - Approval single-use (→ USED/CONSUMED) → Replay denied. --- ## 10. JOB AUTHENTICITY / INTEGRITY (Phase 11) ### Ist Job-DB-Manipulation durch RQ relevant? - **Für SAVE:** Ein manipulierter Job darf niemals Scope erweitern. Da der SAVE-Executor Content selbst aus Forgejo lädt und gegen provenance_hash/expected_state validiert, kann ein manipulierter Job keinen beliebigen Content einschleusen. Scope bleibt geschlossen (nur SAVE, nur erlaubter Pfad). - **Für DELETE:** Selbst vollständige Job-DB-Schreibkontrolle darf NICHT ausreichen, um DELETE zu autorisieren. **AUTH.3D Signature bleibt die Autorität.** Ein manipulierter DELETE-Job ohne gültige, exakt passende AUTH.3D-Approval → DELETE_DENIED. ### Zusätzliche Schutzmechanismen (bewertet) - **Job HMAC:** NICHT nötig für DELETE (AUTH.3D-Signatur schützt die kritische DELETE-Autorität). Für SAVE optional, aber nicht erforderlich (Content wird rekonstruiert). - **DB ownership / filesystem permissions:** Ja — getrennte DB-Dateien mit Executor-spezifischen Permissions (siehe Phase 12). - **Immutable fields after claim:** Ja — nach CLAIMED sind job_type, object_id, vault_path, approval_id, expected_* unveränderlich (UPDATE verboten). - **State hash / row version / compare-and-swap:** Optional für SAVE; für DELETE wird die AUTH.3D-Signatur als Autorität genutzt (keine unnötige Kryptografie). **Keine unnötige Kryptografie einführen, wenn die AUTH.3D-Signatur bereits die kritische DELETE-Autorität schützt.** --- ## 11. DB / FILESYSTEM BOUNDARY (Phase 12) ### Getrennte SQLite-Dateien (bestätigt AUTH.3C) - `c5a_save.db` — SAVE-Inbox (SAVE-Jobs, SAVE-State) - `c5a_delete.db` — DELETE-Inbox (DELETE-Jobs, DELETE-State, Approval-Referenzen) ### Zugriffsmatrix | DB | RQ_READ | RQ_WRITE | SAVE_EXEC_READ | SAVE_EXEC_WRITE | DELETE_EXEC_READ | DELETE_EXEC_WRITE | |---|---|---|---|---|---|---| | c5a_save.db | Ja (Job erzeugen) | Ja (Job erzeugen) | Ja | Ja | **NEIN** | **NEIN** | | c5a_delete.db | Ja (Job erzeugen) | Ja (Job erzeugen) | **NEIN** | **NEIN** | Ja | Ja | **Ziel:** - SAVE Executor braucht keinen Zugriff auf DELETE Credential/DB, außer explizit notwendige read-only Referenzen (keine). - DELETE Executor braucht keinen SAVE-State. ### Linux UID/GID-/Volume-Modell (für AUTH.4, NICHT produktiv ändern) - SAVE-Executor-Container: eigener User, Volume nur für `c5a_save.db`. - DELETE-Executor-Container: eigener User, Volume nur für `c5a_delete.db`. - RQ-Container: Volume für beide Inbox-DBs (nur Job-Erzeugung, kein Executor-Zugriff). - Kein geteiltes Secret-Volume. --- ## 12. NETWORK BOUNDARY (Phase 13) ### SAVE Executor **ALLOW:** - Tolaria notwendige interne Adresse (187.124.31.123:5173) — SAVE + READ - notwendige autoritative Source (Forgejo 10.0.4.1:3000) — Source-/Provenance-Read **DENY (soweit möglich):** - Docker Socket - SSH - Host Root - Telegram - beliebiges Internet - DELETE-spezifische Dienste ### DELETE Executor **ALLOW:** - Tolaria (187.124.31.123:5173) — DELETE - notwendige Approval-/State-Source (Forgejo 10.0.4.1:3000) — Object/Commit-Read **DENY:** - Docker Socket - SSH - Host Root - Telegram - beliebiges Internet - SAVE-spezifische Credentials ### Forgejo-Runtime-Frage **Wird Forgejo wirklich zur Runtime benötigt?** - **SAVE:** JA — der SAVE-Executor lädt Content selbst aus Forgejo (source_commit + object_id → GitReader.file_content). Forgejo ist die autoritative Source. - **DELETE:** JA (für Object/Commit-Read zur Validierung). Aber: Falls der DELETE-Job alle nötigen Daten (object_id, vault_path, expected_commit, expected_provenance_hash) bereits enthält und die AUTH.3D-Approval diese exakt bestätigt, könnte Forgejo-Read optional sein. **Empfehlung:** Forgejo-Read für DELETE beibehalten (Defense in depth), aber als read-only, nicht als Write. **Falls nicht nötig: KEIN Forgejo Runtime Access.** Für SAVE ist er nötig (Content- Rekonstruktion). Für DELETE ist er nötig (Object/Commit-Validierung). --- ## 13. SECRET BOUNDARY (Phase 14) ### Zielmatrix (beweisen) | Akteur | SAVE_TOKEN | DELETE_TOKEN | PRIVATE_SIGNING_KEY | PUBLIC_VERIFY_KEY | |---|---|---|---|---| | RQ | ABSENT | ABSENT | ABSENT | ABSENT | | SAVE Executor | **PRESENT** | ABSENT | ABSENT | ABSENT | | DELETE Executor | ABSENT | **PRESENT** | ABSENT | **PRESENT** | | Rain | ABSENT | ABSENT | ABSENT | ABSENT | | Tolaria Server | PRESENT (verifier) | PRESENT (verifier) | ABSENT | ABSENT | **Beweis (Design):** - RQ: credential-frei (kein SAVE/DELETE-Token, kein Private Key). Nur Job-Erzeugung. - SAVE Executor: erhält NUR SAVE-Token (injiziert), kein DELETE-Token, kein Private Key. - DELETE Executor: erhält NUR DELETE-Token + PUBLIC_VERIFY_KEY, kein SAVE-Token, kein Private Key. - Rain: kein Zugriff auf irgendein Secret (separate Container). - Tolaria Server: hält SAVE+DELETE-Verifier-Secrets (eigene ENV), KEIN Christians Private Key. --- ## 14. COMMAND INJECTION DEFENSE (Phase 15) Jobs dürfen niemals enthalten oder ausführen: - shell commands - arbitrary executable - Python snippets - URLs - HTTP headers - credentials - SQL - filesystem absolute targets (außerhalb erlaubten Schemas) - Docker commands - SSH commands **Enum + typed schema:** - `job_type` ist ein Enum (`C5_SAVE_OBJECT` | `C5_DELETE_OBJECT`), kein freier String. - Alle Felder sind typisiert (uuid, sha256-hex, ISO8601, enum). - **Unknown job_type → REJECTED.** - **Extra privileged fields → REJECTED** (Schema-Validierung lehnt unbekannte Felder ab). - **Malformed schema → REJECTED.** --- ## 15. PATH SAFETY COMPOSITION (Phase 16) AUTH.2 Path Safety bleibt serverseitig zwingend. Executor muss zusätzlich eigene Pfadprüfung durchführen. **Defense in depth:** ``` RQ Job → Executor validation → Tolaria AUTH.2 validation ``` - **Executor validation:** vault_path wird gegen das erlaubte Schema geprüft (kein `..`, kein absoluter Pfad außerhalb `/app/vault`, keine Symlink-Escapes, keine Unicode-Ambiguität, keine encoded traversal). - **Tolaria AUTH.2 validation:** serverseitige Path Safety (bestehend, zwingend). - **Kein Caller darf AUTH.2 Path Safety ersetzen.** --- ## 16. AUTH.3D COMPOSITION (Phase 17) Für DELETE muss gelten (alle Faktoren, keiner ersetzt einen anderen): ``` VALID_JOB AND VALID_STATE AND VALID_AUTH3D_SIGNATURE AND VALID_NONCE AND VALID_EXPIRY AND NOT_CONSUMED AND VALID_OBJECT AND VALID_PATH AND VALID_COMMIT AND VALID_PROVENANCE AND DELETE_CREDENTIAL ``` erst dann: **Tolaria DELETE.** --- ## 17. FAILURE MODEL (Phase 18) | Szenario | Verhalten | |---|---| | DB unavailable | FAIL CLOSED — kein Write ohne State | | DB locked | SQLite-Lock → Retry/Fehler; kein Write ohne State | | malformed job | REJECTED (Schema) | | unknown version | REJECTED | | unknown type | REJECTED | | state mismatch | FAIL CLOSED (InvalidTransitionError) | | source unavailable | FAIL CLOSED (kein Content-Rekonstruktions-Write) | | Tolaria unavailable | SAVE: RETRYABLE (transient); DELETE: OUTCOME_UNKNOWN | | 401 | FAIL CLOSED (Credential/Scope-Fehler) | | 403 | FAIL CLOSED | | 404 | FAIL CLOSED (Ziel fehlt) | | 409 | FAIL CLOSED (Konflikt) | | 500 | FAIL CLOSED | | timeout | SAVE: RETRYABLE; DELETE: OUTCOME_UNKNOWN | | connection reset | SAVE: RETRYABLE; DELETE: OUTCOME_UNKNOWN | | worker crash | Lease-Expiry; SAVE: wieder claimbar; DELETE: OUTCOME_UNKNOWN | | process kill | dito | | container restart | Zustand aus DB; keine Sicherheitsinvariante verloren | | duplicate job | Idempotenz (job_id/idempotency_key) | | duplicate worker | Atomarer Claim verhindert Doppelverarbeitung | | approval unavailable | DELETE_DENIED | | approval invalid | DELETE_DENIED | | approval expired | DELETE_DENIED | | credential missing | FAIL CLOSED (kein HTTP) | **Security-Grundsatz: Unsicherheit darf nicht zu privilegierter Mutation führen.** --- ## 18. AUDIT MODEL (Phase 19) Jeder Job benötigt Audit Trail. Mindestens: - job_id, mission_id, job_type, object_id - state transitions (mit Timestamps) - worker_id, attempt - timestamps - result code - approval_id (bei DELETE) - provenance **NICHT loggen:** - Credentials - Authorization Header - Private Key - sensitive Approval internals, falls unnötig **Audit muss unterscheiden:** - **REQUESTED** (RQ hat Job erzeugt) - **AUTHORIZED** (Executor hat Job + Approval validiert) - **EXECUTED** (Mutation ausgeführt) **WICHTIG: RQ REQUEST != HUMAN AUTHORIZATION.** Ein REQUESTED-Eintrag ist keine AUTHORIZATION. Nur AUTHORIZED (nach AUTH.3D-Validierung) ist eine Autorisierung. --- ## 19. ISOLATED IMPLEMENTATION (Phase 20) — Design > Implementiert in Code + Tests. KEIN produktiver Container. Modulare Komponenten. ### Module - `job_schema.py` — Job-Schema-Validierung (Enum, typed, Allowlist, REJECTED) - `job_store.py` — SQLite Inbox (getrennte DBs, atomarer Claim, Lease) - `job_state_machine.py` — deterministische State Machine (FAIL CLOSED) - `job_claim.py` — atomarer Claim/Lease/Concurrency - `save_executor_core.py` — SAVE-Executor-Logik (SAVE-only, Content-Rekonstruktion) - `delete_executor_core.py` — DELETE-Executor-Logik (DELETE-only, AUTH.3D-Validierung) ### Wiederverwendung - Bestehende C5-Module (rq_c5a, rq_c5c, rq_c5_delete, approval_*) werden wiederverwendet, nicht neu geschrieben. - Keine Big-Bang-Neuschreibung. --- ## 20. TEST CONTRACT (Phase 21) — Design > T1–T40 (siehe Mission §21). Implementiert in isolierten Tests (synthetische Secrets, > Fake-Tolaria, Temp-DB). Keine produktiven Mutationsprobes. --- ## 21. ADVERSARIAL TESTS (Phase 22) — Design > JSON duplicate keys, Unicode path ambiguity, encoded traversal, job_type casing, > enum confusion, integer/string confusion, oversized payload, stale mission, stale > provenance, race claim, replay after restart, DB row manipulation, cross-executor > DB injection, approval substitution, object substitution after claim, path > substitution after claim, state mutation after approval, crash before HTTP, crash > after uncertain HTTP. --- ## 22. TEST SENSITIVITY (Phase 23) — Design > Mutationen A–L (siehe Mission §23). Jede Mutation muss relevante Tests ROT machen. > Falls nicht: Tests verstärken. --- ## 23. REGRESSION (Phase 24) — Design > Volle relevante C5-Test-Suite + AUTH.3A-Tests + AUTH.3D-Tests. Keine bestehenden > Sicherheitsinvarianten abschwächen. Baseline-Fails separat klassifizieren. Keine > unrelated Reparaturen. --- ## 24. SECURITY REVIEW (Phase 25) — Design > Explizit beweisen: RQ_CREDENTIAL_FREE, HERMES_CREDENTIAL_FREE, RAIN_CREDENTIAL_FREE, > SAVE_EXECUTOR_SAVE_ONLY, DELETE_EXECUTOR_DELETE_ONLY, DELETE_EXECUTOR_VERIFY_ONLY, > NO_PRIVATE_SIGNING_KEY_IN_STACK, NO_GENERIC_COMMAND_DISPATCH, NO_ARBITRARY_URL, > NO_ARBITRARY_HTTP_METHOD, NO_ARBITRARY_HEADERS, NO_SHELL_EXECUTION, > JOB_DOES_NOT_EQUAL_AUTHORIZATION, AUTH3D_REQUIRED_FOR_DELETE, ATOMIC_CLAIM, > IDEMPOTENCY, REPLAY_PROTECTION, OUTCOME_UNKNOWN_FAIL_CLOSED. --- ## 25. DESIGN DOC RECONCILIATION (Phase 26) — Design > AUTH3B_DESIGN.md, AUTH3C_DESIGN.md, AUTH3D_DESIGN.md (untracked). Entscheidungsmatrix > (FILE, SECURITY_RELEVANCE, UNIQUE_INFORMATION, ALREADY_REPRESENTED_IN_COMMITTED_CODE/DOCS, > STALE_INFORMATION, RECOMMENDED_SOT_ACTION; Klassifikation COMMIT/MERGE_INTO_ARCH_DOC/ > ARCHIVE/DISCARD/KEEP_UNTRACKED_TEMPORARILY). Nur Empfehlung. Keine Mutation ohne > separate Freigabe. --- ## 26. COMMIT / PUSH (Phase 27) — Design > Nur AUTH.3E-Code + Tests + ausdrücklich notwendige AUTH.3E-Dokumentation. Vor Commit: > git status, git diff, Secret Scan, Private-Key Scan, Credential Pattern Scan. Keine > echten Credentials. Nur synthetische Testwerte. FF-only Push. HEAD == origin/main > verifizieren. Falls Hooks scheitern: Ursache analysieren. --no-verify NICHT > automatisch verwenden. --- ## 27. FRESH CHECKER (Phase 28) — Design > Unabhängiger Checker (32 Punkte, siehe Mission §28). --- ## 28. FINAL REPORT (Phase 29) + HARD STOP (Phase 30) > Nach FINAL REPORT sofort STOP. Keine produktiven Mutationen, keine Container, > keine echten Credentials, keine Hermes-Autonomie.