- job_schema: geschlossene Job-Type-Allowlist (C5_SAVE_OBJECT/C5_DELETE_OBJECT), Pfad-/Längen-Validierung - job_state_machine: deterministische States (CREATED/READY/CLAIMED/EXECUTING/SUCCEEDED/FAILED) - job_claim: atomare Claim-/Lease-/Recovery-Logik (kein TOCTOU) - job_store: getrennte SQLite-Inbox-DBs (c5a_save.db/c5a_delete.db), delegiert an job_claim - save_executor_core: SAVE-only, Content-Rekonstruktion, Provenance-Validierung - delete_executor_core: DELETE-only, AUTH.3D-Composition, TOCTOU-Defense (Re-Read nach Claim) - test_job_channel: T1-T40 + adversarial (72 Tests) - test_job_channel_adversarial: adversarial + Substitution + DB-Manipulation - sensitivity_proof_auth3e: Mutationen A-L (12/12 Invarianten PRESENT) - AUTH3B/3C/3D/3E_DESIGN: autoritative Security-Dokumentation (e25 Reconciliation) COMMAND != AUTHORIZATION. Kein generischer Dispatcher. RQ credential-free. Keine produktive Mutation. Keine echten Credentials.
714 lines
32 KiB
Markdown
714 lines
32 KiB
Markdown
# 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:<workflow_commit_sha>`); `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.
|