- 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.
711 lines
40 KiB
Markdown
711 lines
40 KiB
Markdown
# AUTH.3B — SECRET / ENV / RUNTIME INJECTION DESIGN + DEPLOYMENT PRE-FLIGHT
|
||
|
||
**Status:** DESIGN + VERIFIED DEPLOYMENT PLAN (STRICT READ-ONLY, KEIN produktives Deployment)
|
||
**Datum:** 2026-08-27
|
||
**Mission Type:** STRICT DESIGN + READ-ONLY DISCOVERY + ISOLATED TESTS
|
||
**Baseline:** AUTH.1 (40bbc40) + AUTH.2 (edcb4902) + AUTH.3A (a11c1bb) = COMPLETE
|
||
|
||
> ⚠️ Dies ist ein **Design-/Plan-Dokument**. Es wird NICHTS produktiv deployt,
|
||
> kein Token erzeugt, keine ENV/Compose/Coolify geändert, kein Container
|
||
> restart/recreate, keine produktive Tolaria-Auth aktiviert.
|
||
|
||
---
|
||
|
||
## 1. RUNTIME DISCOVERY (Phase 1) — IST-Zustand
|
||
|
||
### A) Tolaria Server
|
||
| Attribut | Wert |
|
||
|---|---|
|
||
| Container | `tolaria` |
|
||
| Runtime | Node.js 20 / Vite (dev server, `vite.config.ts` middleware) |
|
||
| Host Source | `/opt/tolaria/app/tolaria` |
|
||
| Container Source | `/app/tolaria` |
|
||
| Vault | `/opt/data/tolaria-vault` → `/app/vault` (Bind-Mount) |
|
||
| Compose | `/opt/tolaria/docker-compose.yml` |
|
||
| Port | 5173 (produktiv); Repo-`vite.config.ts` deklariert 5202 (dev) |
|
||
| Produktiver Source-Basisstand | `b5a5e4bd` (VOR AUTH.2) |
|
||
| AUTH.2 deployed? | **NEIN** — Produktion läuft noch OHNE neue Auth (verifiziert: save ohne Token → 200) |
|
||
| ENV erwartet (Code) | `TOLARIA_SAVE_TOKEN`, `TOLARIA_DELETE_TOKEN`, `TOLARIA_VAULT_ROOT` (Default `/app/vault`) |
|
||
| Fail-closed | JA — `authorizeVaultWrite` Z.120-123: leeres `expected` → 401 "Write scope not configured" |
|
||
|
||
### B) C5C Writer (SAVE)
|
||
| Attribut | Wert |
|
||
|---|---|
|
||
| Klasse | `C5CPropagator` (`rq_c5c.py` Z.497) |
|
||
| Write-Methode | `TolariaClient.write()` (Z.315) → POST `/api/vault/save`, Bearer mit `save_token` |
|
||
| Credential-Quelle | NUR explizit injizierter Client (`client=...`); Default `TolariaClient()` credential-frei → fail-closed |
|
||
| ENV | `TOLARIA_SAVE_TOKEN` (Konstante Z.100); `rq_c5c.py` liest ENV NICHT selbst |
|
||
| CLI-Entrypoint | **KEINER** — kein CLI-Command ruft C5CPropagator mit save_token auf |
|
||
| Produktiver Aufrufer | `rq_c5f_resume.py` (untracked) — konstruiert `TolariaClient()` OHNE save_token → mit AUTH.3A fail-closed |
|
||
| Runtime | RQ-Container (`06a4a73573d0`), C5-DB `/opt/data/c5c_live/c5a.db` |
|
||
|
||
### C) C5 DeleteExecutor (DELETE)
|
||
| Attribut | Wert |
|
||
|---|---|
|
||
| Klasse | `DeleteExecutor` (`rq_c5_delete.py`) |
|
||
| Write-Methode | `TolariaClient.delete()` → POST `/api/vault/delete`, Bearer mit `delete_token` |
|
||
| Credential-Quelle | `_delete_executor` (`rq_c5_cli.py` Z.344): `delete_token=os.environ.get(ENV_TOLARIA_DELETE_TOKEN)` |
|
||
| ENV | `TOLARIA_DELETE_TOKEN` (Konstante Z.101) |
|
||
| CLI-Entrypoint | `c5-delete-execute`, `c5-delete-replay` (Human-Gated) |
|
||
| Runtime | RQ-Container (`06a4a73573d0`), C5-DB `/opt/data/c5c_live/c5a.db` |
|
||
|
||
### D) Red Queen / Hermes
|
||
| Attribut | Wert |
|
||
|---|---|
|
||
| Container | `06a4a73573d0` (RQ/Hermes) |
|
||
| C5-Caller | laufen IM RQ-Container (C5-DB `/opt/data/c5c_live/c5a.db`) |
|
||
| Docker-Daemon | NICHT verfügbar aus RQ-Container (kein `/var/run/docker.sock`) |
|
||
| VPS-Host-SSH | NICHT verfügbar (Key `red_queen_forgejo` nur für Forgejo-Git-Push) |
|
||
| Tolaria-API | read-only erreichbar (`187.124.31.123:5173`) |
|
||
|
||
### E) Rain / OpenClaw
|
||
| Attribut | Wert |
|
||
|---|---|
|
||
| Ports | 55163 (Alice), 54524 (Matt Addison) |
|
||
| Zugriff auf C5-DB | NICHT verifiziert (separate Container) |
|
||
| Tolaria-Write-Credential | KEIN (Architektur-Invariante) |
|
||
|
||
---
|
||
|
||
## 2. KRITISCHER BEFUND (Phase 7-Vorab)
|
||
|
||
**SAVE-Caller (C5CPropagator) und DELETE-Caller (DeleteExecutor) laufen im SELBEN
|
||
Prozess/Container (RQ-Container `06a4a73573d0`).** Es existiert KEINE getrennte
|
||
Runtime-Boundary zwischen SAVE und DELETE. Beide Credentials würden, wenn beide
|
||
injiziert würden, im selben Container landen.
|
||
|
||
**Konsequenz:** Die Credential-Separation (SAVE≠DELETE) ist heute NUR logisch
|
||
(verschiedene ENV-Namen, verschiedene Klassen), NICHT technisch als getrennte
|
||
Security Boundaries. Das muss in Phase 7 explizit dokumentiert und in Phase 3/4
|
||
bewertet werden.
|
||
|
||
---
|
||
|
||
## 2b. SECURITY BOUNDARY ANALYSIS (Phase 2) — IST-Zustand
|
||
|
||
### Runtime-Fakten (verifiziert)
|
||
- **RQ/Hermes-Container** = `06a4a73573d0`, User `hermes` (uid 10010, gid 10000).
|
||
- **Kein Docker-Socket** im RQ-Container (`/var/run/docker.sock` fehlt) → RQ kann
|
||
keine anderen Container inspizieren.
|
||
- **Kein VPS-Host-SSH** (Key `red_queen_forgejo` nur für Forgejo-Git-Push) → RQ kann
|
||
Host-Dateien (`/opt/tolaria`, Compose) NICHT lesen.
|
||
- **Kein `/opt/tolaria`** im RQ-Container-Dateisystem.
|
||
- **/proc** im RQ-Container zeigt nur eigene Prozesse (24 PIDs) → kein Cross-Container
|
||
/proc-Zugriff.
|
||
- **Netzwerk:** RQ erreicht Tolaria intern (`10.0.4.1:5173` → `{"ok":true}`) und
|
||
Forgejo (`10.0.4.1:3000`). Tolaria läuft in SEPARATEM Container auf VPS-Host.
|
||
- **C5-Caller:** KEIN Daemon, KEIN separater Container. `C5CPropagator` (SAVE) und
|
||
`DeleteExecutor` (DELETE) sind Library/CLI, die manuell im RQ-Container aufgerufen
|
||
werden (Hermes-Gateway-Prozess). C5-DB `/opt/data/c5c_live/c5a.db` liegt im RQ-Container.
|
||
- **ENV im RQ-Container:** `TOLARIA_SAVE_TOKEN` ABSENT, `TOLARIA_DELETE_TOKEN` ABSENT
|
||
(korrekt — RQ credential-free). Vorhandene Secrets: `FORGEJO_WRITE_TOKEN`,
|
||
`NOTION_API_KEY`, `TELEGRAM_BOT_TOKEN`, `HERMES_SESSION_KEY` (Namen, keine Werte).
|
||
|
||
### CAN_*_READ_SECRET — SAVE und DELETE (identisch, da gleiche Boundary)
|
||
|
||
| Akteur | SAVE | DELETE | Weg |
|
||
|---|---|---|---|
|
||
| **RQ/Hermes** (Container `06a4a73573d0`) | **NEIN** (tatsächlich) | **NEIN** (tatsächlich) | Kein Docker-Socket, kein Host-SSH, kein /proc-Cross, kein /opt/tolaria. ENV ABSENT. |
|
||
| **RQ/Hermes** (theoretisch via root) | JA (wenn Host-root) | JA (wenn Host-root) | Host-root kann Container-ENV via `docker inspect`/`docker exec` lesen. |
|
||
| **Rain/OpenClaw** (Alice 55163, Matt 54524) | **NEIN** (tatsächlich) | **NEIN** (tatsächlich) | Separate Container, kein Zugriff auf RQ-ENV oder Tolaria-Container. |
|
||
| **Anderer Container** (Tolaria selbst) | JA (eigener ENV) | JA (eigener ENV) | Tolaria-Container hält seine eigenen erwarteten Tokens. |
|
||
| **Docker-Admin** (VPS-Host) | JA | JA | `docker inspect`/`docker exec` auf Tolaria-Container. |
|
||
| **Host-Root** (VPS) | JA | JA | Liest Compose, .env, Container-ENV, Mounts. |
|
||
| **C5C Writer** (SAVE-Prozess) | JA (SAVE) | NEIN (kein DELETE) | Erhält nur SAVE-Token via injiziertem Client. |
|
||
| **DeleteExecutor** (DELETE-Prozess) | NEIN (kein SAVE) | JA (DELETE) | Erhält nur DELETE-Token via ENV. |
|
||
|
||
### Security-Boundary-Befund
|
||
- **Tolaria-Server** = eigene Boundary (separater Container, VPS-Host).
|
||
- **RQ/Hermes** = eigene Boundary (Container `06a4a73573d0`), aber **C5-Caller
|
||
(SAVE+DELETE) laufen INNERHALB dieser Boundary**.
|
||
- **CRITICAL:** SAVE- und DELETE-Credential würden, wenn beide injiziert würden,
|
||
im **selben Container** (`06a4a73573d0`) landen. Es gibt KEINE getrennte
|
||
Runtime-Boundary zwischen SAVE-Caller und DELETE-Caller.
|
||
- **Konsequenz für Phase 3:** Die Zielarchitektur "SAVE≠DELETE als getrennte
|
||
Boundaries" ist mit dem realen Deployment **NICHT vollständig erreichbar**, solange
|
||
beide Caller im selben RQ-Container laufen. Die Separation ist NUR logisch
|
||
(verschiedene ENV-Namen, verschiedene Klassen, verschiedene CLI-Commands), NICHT
|
||
technisch als getrennte Container/Prozesse.
|
||
|
||
---
|
||
|
||
## 3b. CREDENTIAL OWNERSHIP MODEL (Phase 3) — Zielarchitektur vs. Realität
|
||
|
||
### Zielarchitektur (verbindlich aus Freigabe)
|
||
- SAVE credential → Tolaria Server + C5C Writer
|
||
- DELETE credential → Tolaria Server + C5 DeleteExecutor
|
||
- RQ: KEIN SAVE, KEIN DELETE
|
||
- Hermes generisch: KEIN SAVE, KEIN DELETE
|
||
- Rain: KEIN SAVE, KEIN DELETE
|
||
- Kein Master-Token
|
||
- SAVE darf DELETE nicht autorisieren; DELETE darf SAVE nicht autorisieren
|
||
|
||
### Erreichbarkeit mit realem Deployment
|
||
|
||
**1) Server-seitige Auth (AUTH.2) — ERREICHBAR ✓**
|
||
- Tolaria-Server hält SAVE+DELETE-Tokens in seiner eigenen ENV (separater Container).
|
||
- RQ/Hermes/Rain haben KEINEN Zugriff auf den Tolaria-Container (kein Docker-Socket,
|
||
kein Host-SSH, kein /proc-Cross).
|
||
- Server-seitige Scope-Trennung (SAVE≠DELETE) ist im Code verifiziert (AUTH.2).
|
||
|
||
**2) Caller-seitige Credential-Separation — NICHT ERREICHBAR (HARD STOP-Kandidat) ✗**
|
||
- **Ursache:** C5C Writer (SAVE) und DeleteExecutor (DELETE) laufen im **selben
|
||
Container** wie RQ/Hermes (`06a4a73573d0`). Es gibt KEINE getrennte Runtime-Boundary.
|
||
- Wenn SAVE- und DELETE-Tokens in die ENV dieses Containers injiziert würden, hätte
|
||
RQ/Hermes (das im selben Container läuft, gleicher User `hermes`) **technischen
|
||
Zugriff auf BEIDE Tokens**.
|
||
- Das verletzt die Zielarchitektur "RQ: KEIN SAVE KEIN DELETE" und "Hermes: KEIN
|
||
SAVE KEIN DELETE" — unabhängig davon, ob RQ den Zugriff tatsächlich nutzt.
|
||
|
||
### HARD STOP — exakte Ursache
|
||
**Die Zielarchitektur "RQ/Hermes credential-free" ist mit dem realen Deployment
|
||
NICHT erreichbar, solange die C5-Caller (SAVE+DELETE) im selben Container wie
|
||
RQ/Hermes laufen und ihre Tokens in dessen ENV liegen.**
|
||
|
||
Notwendige Voraussetzung für AUTH.4 (Architektur-Änderung, NICHT in AUTH.3B):
|
||
- **Option A (empfohlen):** C5C Writer und DeleteExecutor in einen **separaten
|
||
Container/Prozess** mit eigener Boundary verschieben, der NICHT der RQ-Container
|
||
ist. RQ/Hermes ruft diesen Container nur über eine schmale Schnittstelle auf.
|
||
- **Option B:** Tokens über einen **separaten Secret-Store** injizieren, auf den nur
|
||
der C5-Caller-Prozess (nicht RQ/Hermes) Zugriff hat. Im selben Container mit
|
||
gleichem User ist das praktisch nicht durchsetzbar (RQ kann dieselbe Datei lesen).
|
||
|
||
**Ohne diese Änderung bleibt die Separation NUR logisch** (verschiedene ENV-Namen,
|
||
verschiedene Klassen, verschiedene CLI-Commands), NICHT technisch als getrennte
|
||
Security Boundaries. Das wird NICHT stillschweigend abgeschwächt — es wird als
|
||
HARD STOP für die volle Zielarchitektur dokumentiert.
|
||
|
||
### Was TROTZDEM sicher erreichbar ist (ohne Architektur-Änderung)
|
||
- **Server-seitige Auth (AUTH.2) aktivieren:** Tolaria-Server fail-closed, SAVE≠DELETE
|
||
Scope-Trennung. RQ bleibt credential-free (kein Zugriff auf Tolaria-Container).
|
||
- **SAVE-Aktivierung:** C5C Writer erhält SAVE-Token. ABER: da im RQ-Container,
|
||
hätte RQ technisch Zugriff → verletzt "RQ credential-free" für SAVE.
|
||
- **DELETE-Aktivierung:** DeleteExecutor erhält DELETE-Token. ABER: da im RQ-Container,
|
||
hätte RQ technisch Zugriff → verletzt "RQ credential-free" für DELETE.
|
||
|
||
### Fazit Phase 3
|
||
- **SERVER_AUTH_DEPLOYMENT:** ERREICHBAR (RQ bleibt credential-free ggü. Tolaria).
|
||
- **SAVE_ACTIVATION / DELETE_CREDENTIAL_ACTIVATION:** BLOCKED bis C5-Caller in
|
||
getrennte Boundary verschoben ODER Secret-Injection RQ-undurchdringbar ist.
|
||
- **Kein Master-Token:** ERREICHBAR (AUTH.2/AUTH.3A verifiziert, kein Master-Key).
|
||
- **SAVE≠DELETE Scope-Trennung:** Server-seitig ERREICHBAR; Caller-seitig nur logisch.
|
||
|
||
---
|
||
|
||
## 4b. SECRET STORAGE OPTIONS (Phase 4) — Bewertung
|
||
|
||
### Kontext: Was existiert im realen Stack?
|
||
- **Coolify-Suite** ist auf dem VPS installiert (infrastructure-handbook Z.52).
|
||
- **Exakte Tolaria-Provisionierung (Compose vs Coolify) ist aus RQ-Sicht UNKNOWN**
|
||
(kein Docker-Socket, kein Host-SSH). CURRENT_STATE Z.27: "UNKNOWN: Container-Name/
|
||
Image-Konfiguration, Env-Variablen, Mounts, Volumes — nur über Host-/Docker-Zugriff
|
||
ermittelbar, der nicht verfügbar ist."
|
||
- **Kein separater Secret-Store** (kein Vault/Consul/SOPS/age) im Stack nachweisbar.
|
||
- **Kein Docker-Secrets-Mechanismus** im Stack nachweisbar (kein Swarm).
|
||
- Tolaria-Server liest ENV via `process.env.TOLARIA_SAVE_TOKEN` (vite.config.ts).
|
||
|
||
### Optionen-Bewertung (SAVE und DELETE, identisch)
|
||
|
||
| Kriterium | A) Plain Compose ENV | B) .env-Datei | C) Docker Secrets | D) read-only Secret-Datei | E) Coolify Secret Mgmt | F) separater Secret-Store |
|
||
|---|---|---|---|---|---|---|
|
||
| SECURITY | Schwach (ENV in inspect) | Mittel (Datei, chmod) | Stark (nur im Container) | Mittel (Datei, chmod) | Stark (Coolify-UI) | Stark |
|
||
| VISIBILITY | `docker inspect` sichtbar | Datei, nicht inspect | Nicht in inspect | Datei, nicht inspect | Nicht in inspect | Nicht in inspect |
|
||
| DOCKER_INSPECT_EXPOSURE | **JA** | NEIN | NEIN | NEIN | NEIN | NEIN |
|
||
| HOST_EXPOSURE | JA (root liest) | JA (root liest) | JA (root liest) | JA (root liest) | JA (root liest) | JA (root liest) |
|
||
| RQ_EXPOSURE | JA (wenn im RQ-Container) | JA (wenn im RQ-Container) | NEIN (nur Ziel-Container) | JA (wenn im RQ-Container) | NEIN | NEIN |
|
||
| ROTATION | Restart nötig | Restart nötig | Restart nötig | Restart nötig | Restart nötig | Restart nötig |
|
||
| REVOCATION | ENV entfernen + Restart | Datei löschen + Restart | Secret entfernen + Restart | Datei löschen + Restart | ENV entfernen + Restart | Store-Update + Restart |
|
||
| RESTART_BEHAVIOR | ENV bleibt | Datei bleibt | Secret bleibt | Datei bleibt | ENV bleibt | Store bleibt |
|
||
| BACKUP_BEHAVIOR | ENV nicht in Backup | Datei evtl. in Backup | Secret nicht in Backup | Datei evtl. in Backup | ENV nicht in Backup | Store in Backup |
|
||
| OPERATIONAL_COMPLEXITY | Niedrig | Niedrig | Mittel (Swarm nötig) | Niedrig | Mittel | Hoch |
|
||
| COMPATIBILITY | Hoch (Vite liest ENV) | Hoch | Niedrig (kein Swarm) | Hoch (Datei lesen) | Mittel (Coolify vorhanden) | Niedrig (nicht vorhanden) |
|
||
| BLAST_RADIUS | Hoch (inspect) | Mittel | Niedrig | Mittel | Niedrig | Niedrig |
|
||
|
||
### Empfehlung für UNSEREN Stack
|
||
**Minimal sichere Variante: B) .env-Datei (oder D) read-only Secret-Datei) mit
|
||
strikten Permissions, injiziert in die Tolaria-Container-ENV.**
|
||
|
||
Begründung:
|
||
- **A (Plain Compose ENV) ist abzulehnen:** `docker inspect` würde die Tokenwerte
|
||
offenlegen (DOCKER_INSPECT_EXPOSURE=JA). Das verletzt die Anforderung "kein Secret
|
||
in docker inspect".
|
||
- **C (Docker Secrets) ist nicht kompatibel:** erfordert Docker Swarm, das im Stack
|
||
nicht nachweisbar ist.
|
||
- **F (separater Secret-Store) ist nicht vorhanden:** keine neue Infrastruktur
|
||
erfinden (Freigabe-Anforderung).
|
||
- **E (Coolify Secret Mgmt):** Coolify ist vorhanden, aber die exakte Tolaria-
|
||
Provisionierung ist UNKNOWN. Falls Tolaria über Coolify deployed wird, ist Coolify
|
||
Secret Management die natürliche Wahl (nicht in inspect, Rotation via UI).
|
||
- **B/D sind die robuste Default-Empfehlung:** `.env`-Datei mit `chmod 600`, vom
|
||
Compose/Coolify in die Container-ENV injiziert. Tokenwerte erscheinen NICHT in
|
||
`docker inspect` (nur der ENV-Name, nicht der Wert, wenn via `env_file` injiziert).
|
||
|
||
**WICHTIG (Phase 3-Kopplung):** Unabhängig von der Storage-Option bleibt der
|
||
HARD STOP bestehen: Solange die C5-Caller im RQ-Container laufen, würde jede
|
||
Injektion in dessen ENV RQ technischen Zugriff geben. Die Storage-Empfehlung gilt
|
||
daher primär für den **Tolaria-Server-Container** (eigene Boundary, RQ-fern).
|
||
|
||
---
|
||
|
||
## 5b. TOKEN GENERATION CONTRACT (Phase 5) — NUR DESIGN, KEINE echten Tokens
|
||
|
||
> ⚠️ Dies ist ein **Design-Vertrag**. Es wird KEIN echtes Token erzeugt, kein Wert
|
||
> angezeigt, nichts in ENV/Compose/Logs/Telegram/Repo geschrieben.
|
||
|
||
### Kryptographische Zufallsquelle
|
||
- **`/dev/urandom`** (Linux) oder `secrets.token_urlsafe()` (Python) — kryptographisch
|
||
sicher, nicht-deterministisch.
|
||
- NICHT: `random`-Modul, `time`-basiert, UUID (nicht krypto-sicher genug für Auth).
|
||
|
||
### Mindestentropie
|
||
- **≥ 256 Bit** (32 Bytes) pro Token. Empfehlung: 32 Bytes aus `/dev/urandom`,
|
||
base64url-kodiert → 43 Zeichen.
|
||
|
||
### Format / Länge
|
||
- **Format:** base64url (URL-sicher, kein `+`/`/`/`=`), 43 Zeichen bei 32 Bytes.
|
||
- **Länge:** 43 Zeichen (256 Bit Entropie).
|
||
- **Keine semantischen Token-Präfixe** (kein `save-`, `delete-`, `tolaria-` im Wert) —
|
||
Präfixe wären unnötige Information und könnten Scope-Leaks erzeugen. Der Scope wird
|
||
durch die ENV-Variable bestimmt, nicht durch den Tokenwert.
|
||
|
||
### Unabhängigkeit
|
||
- **SAVE und DELETE unabhängig generiert** (zwei separate `/dev/urandom`-Ziehungen).
|
||
- **Niemals gleicher Wert** (bei 256 Bit Entropie kollisionsfrei).
|
||
- **Keine Ableitung SAVE→DELETE** (kein Hash/Substring/Transformation).
|
||
- **Kein Master-Key** (kein gemeinsamer Seed, kein hierarchisches Schema).
|
||
|
||
### Nicht-Speicherung / Nicht-Ausgabe
|
||
- **Keine Shell-History** (kein `echo $TOKEN`, kein Token in interaktiven Befehlen).
|
||
- **Keine Ausgabe in Telegram / Chat / Logs / FINAL REPORT.**
|
||
- Token wird NUR in die Ziel-ENV/Secret-Datei geschrieben (Tolaria-Server-Container),
|
||
nie in Git, nie in Reports, nie in Test-Fixtures.
|
||
|
||
### Wer die Tokens bei AUTH.4 erzeugen darf
|
||
- **Nur Christian (Mensch)** — manuell auf dem VPS-Host, via `/dev/urandom` oder
|
||
`openssl rand -base64 32`, direkt in die Secret-Datei/Coolify-ENV geschrieben.
|
||
- **NICHT RQ/Hermes/Rain** — kein Agent erzeugt oder sieht die echten Tokens.
|
||
- Erzeugung erfolgt im Rahmen der AUTH.4-Deployment-Sequenz (Phase 9), nicht in
|
||
AUTH.3B.
|
||
|
||
---
|
||
|
||
## 6b. SERVER INJECTION CONTRACT (Phase 6) — AUTH.2 im Code verifiziert
|
||
|
||
### Exakte ENV-Namen (im Code verifiziert, `vite.config.ts` Z.35-39)
|
||
- `TOLARIA_VAULT_ROOT` → Default `/app/vault`
|
||
- `TOLARIA_SAVE_TOKEN` → `saveToken` (Scope SAVE)
|
||
- `TOLARIA_DELETE_TOKEN` → `deleteToken` (Scope DELETE)
|
||
|
||
### Verhalten (im Code verifiziert, `vault-write-guard.ts` Z.112-131)
|
||
| Feld | Wert |
|
||
|---|---|
|
||
| SERVER_SAVE_ENV | `TOLARIA_SAVE_TOKEN` |
|
||
| SERVER_DELETE_ENV | `TOLARIA_DELETE_TOKEN` |
|
||
| MISSING_SAVE_BEHAVIOR | `expected.length === 0` → 401 "Write scope not configured" (fail-closed) |
|
||
| MISSING_DELETE_BEHAVIOR | dito (fail-closed) |
|
||
| EMPTY_TOKEN_BEHAVIOR | `|| ''` → leeres expected → 401 (fail-closed) |
|
||
| WRONG_SCOPE_BEHAVIOR | `constantTimeEqual(extracted, expected)` → falscher Scope → 401 "Invalid credentials" |
|
||
|
||
### Fail-closed bestätigt
|
||
- **Kein "Auth disabled because ENV missing".** Fehlende/leere Token-Konfiguration
|
||
→ mutierende Endpoints bleiben DENIED (401).
|
||
- Scope-Trennung: SAVE-Token autorisiert NUR SAVE; DELETE-Token NUR DELETE.
|
||
- Const-time-Vergleich (`constantTimeEqual`) gegen Timing-Angriffe.
|
||
- Auth-before-Mutation: bei Auth-Fehler wird Response gesendet, KEINE Mutation.
|
||
|
||
### Fazit Phase 6
|
||
- **SERVER_FAIL_CLOSED_STATUS = ENFORCED** (im Code verifiziert).
|
||
- Server erwartet exakt `TOLARIA_SAVE_TOKEN` + `TOLARIA_DELETE_TOKEN`.
|
||
- Kein Master-Token, keine Scope-Abschwächung.
|
||
|
||
---
|
||
|
||
## 7b. CALLER INJECTION CONTRACT (Phase 7) — AUTH.3A im Code verifiziert
|
||
|
||
### ENV-Contract (im Code verifiziert, `rq_c5c.py` Z.100-101)
|
||
- `ENV_TOLARIA_SAVE_TOKEN = "TOLARIA_SAVE_TOKEN"`
|
||
- `ENV_TOLARIA_DELETE_TOKEN = "TOLARIA_DELETE_TOKEN"`
|
||
|
||
### Credential-Verwendung (im Code verifiziert)
|
||
| Caller | ENV | Verwendung | Scope |
|
||
|---|---|---|---|
|
||
| C5C Writer (`C5CPropagator`) | `TOLARIA_SAVE_TOKEN` | `write()` Z.322: `self._require_token(self.save_token, "SAVE")` | SAVE |
|
||
| DeleteExecutor | `TOLARIA_DELETE_TOKEN` | `delete()` Z.352: `self._require_token(self.delete_token, "DELETE")` | DELETE |
|
||
| read/list | — | KEIN Credential | READ |
|
||
|
||
### Verifizierte Invarianten
|
||
- **SAVE-Prozess bekommt DELETE nicht:** `write()` nutzt NUR `self.save_token`.
|
||
- **DELETE-Prozess bekommt SAVE nicht:** `delete()` nutzt NUR `self.delete_token`.
|
||
- **read/list brauchen nichts:** `read()`/`list()` senden kein Bearer.
|
||
- **fehlendes Token => kein HTTP Write:** `_require_token` (Z.300-313) bricht lokal ab
|
||
(RC_AUTH_FAILURE), HTTP wird NICHT aufgerufen (fail-closed, AUTH.1 §4).
|
||
- **Retry behält Credential:** Client-Instanz hält Token über Retries hinweg.
|
||
- **Replay behält Credential:** `c5-delete-replay` nutzt denselben `_delete_executor`.
|
||
- **Token wird nicht persistiert:** kein Token in DB/Logs/Reports (AUTH.3A verifiziert).
|
||
|
||
### KRITISCHER BEFUND (Runtime-Separation)
|
||
- **Sind C5C Writer und DeleteExecutor heute getrennte Runtime-Prozesse/Boundaries?
|
||
NEIN.**
|
||
- Beide laufen im **selben Container** (`06a4a73573d0`, RQ/Hermes) und werden als
|
||
Library/CLI im selben Hermes-Gateway-Prozess aufgerufen.
|
||
- **Konsequenz:** Die Credential-Separation ist NUR logisch (verschiedene ENV-Namen,
|
||
verschiedene Klassen, verschiedene CLI-Commands). Wenn beide Tokens in die ENV
|
||
dieses Containers injiziert würden, hätte RQ/Hermes technischen Zugriff auf BEIDE.
|
||
- **Das wird NICHT als technische Separation behauptet.** Es ist explizit dokumentiert:
|
||
CREDENTIAL_SEPARATION_STATUS = LOGICAL_ONLY (nicht technisch).
|
||
|
||
### Fazit Phase 7
|
||
- **CALLER_FAIL_CLOSED_STATUS = ENFORCED** (im Code verifiziert).
|
||
- **CREDENTIAL_SEPARATION_STATUS = LOGICAL_ONLY** (nicht technisch, da gleiche Boundary).
|
||
- Least Privilege im Code korrekt; Runtime-Separation fehlt (HARD STOP aus Phase 3).
|
||
|
||
---
|
||
|
||
## 8b. HUMAN APPROVAL INTERACTION (Phase 8) — AUTH4_WITH_OPEN_APPROVAL_GAP
|
||
|
||
### Kontext
|
||
- **HUMAN_APPROVAL_AUTHENTICITY_GAP = OPEN** (aus AUTH.3A, NICHT repariert).
|
||
Die DELETE-Approval erfüllt Invarianten A–N, aber die Provenance ist nicht
|
||
kryptographisch an Christian gebunden (frei wählbares CLI-Argument
|
||
`--approved-by`, `rq_c5_cli.py` Z.576).
|
||
- **DELETE-Execution (AUTH.3A, `rq_c5_delete.py` Z.192-251):** 4 Pre-Gates:
|
||
1. ObjectChange existiert + operation == DELETE
|
||
2. Commit-Status == ST_DELETE_APPROVED
|
||
3. Approval existiert, APPROVED, passt exakt (Commit/Objekt/Pfad) ← **Human Gate**
|
||
4. Pre-Delete-Drift-Check (Ziel existiert noch)
|
||
|
||
### Wie interagiert das DELETE-Token mit dem Human Gate?
|
||
- Das DELETE-Token (AUTH.2/AUTH.3A) ist eine **zusätzliche, unabhängige Bedingung**
|
||
auf Transport-Ebene: `delete()` sendet Bearer DELETE-Token; Server verweigert ohne
|
||
gültiges Token (401).
|
||
- Das Human Approval Gate (Gate 3) ist eine **separate Bedingung auf
|
||
Anwendungs-Ebene**: `execute()` verweigert ohne persistierte, exakt passende
|
||
Approval.
|
||
- **DELETE ist nur möglich, wenn BEIDES erfüllt ist:**
|
||
A) gültiges DELETE-Credential (Server-Auth) UND
|
||
B) bestehendes C5 Human Approval Gate (Gate 3).
|
||
|
||
### Verschärft AUTH.4 das Authenticity-Gap?
|
||
- **NEIN.** AUTH.4 fügt eine zusätzliche Hürde (DELETE-Token) hinzu, die das
|
||
bestehende Gap NICHT verschärft, sondern die DELETE-Ausführung zusätzlich
|
||
absichert.
|
||
- Das Gap (Provenance nicht kryptographisch an Christian gebunden) bleibt bestehen,
|
||
wird aber durch das DELETE-Token NICHT schwächer. Ein Angreifer, der das
|
||
Authenticity-Gap ausnutzt (frei wählbares `--approved-by`), bräuchte ZUSÄTZLICH
|
||
das DELETE-Token, um tatsächlich zu löschen.
|
||
- **Kein Risiko, dass durch das DELETE-Token ein schwächeres Human Gate akzeptiert
|
||
wird.** Das Human Gate (Gate 3) bleibt unverändert ENFORCED.
|
||
|
||
### Klassifikation
|
||
**AUTH4_WITH_OPEN_APPROVAL_GAP = SAFE_FOR_SERVER_AUTH_ONLY**
|
||
|
||
Begründung:
|
||
- Die Einführung des DELETE-Tokens (Server-Auth) verschärft das Authenticity-Gap
|
||
NICHT — es fügt eine unabhängige, zusätzliche Hürde hinzu.
|
||
- **ABER:** DELETE_OPERATION_ACTIVATION (tatsächliches Löschen) sollte erst nach
|
||
Schließung des Gaps freigegeben werden, um das Risiko eines kombinierten Angriffs
|
||
(Gap + gestohlenes Token) zu minimieren.
|
||
- **SAFE_FOR_SERVER_AUTH_ONLY** bedeutet: Server-seitige Auth (AUTH.2) kann sicher
|
||
aktiviert werden, ohne das Gap zu verschärfen. Die DELETE-Operation selbst bleibt
|
||
bis zur Gap-Schließung zurückhaltend zu behandeln (siehe Phase 14 GO/NO-GO).
|
||
|
||
---
|
||
|
||
## 9b. DEPLOYMENT SEQUENCE DESIGN (Phase 9) — AUTH.4 atomar, KEIN Big-Bang
|
||
|
||
> ⚠️ NUR DESIGN. KEINE AUSFÜHRUNG in AUTH.3B.
|
||
|
||
### Kernfrage: Caller zuerst vs Server zuerst?
|
||
**Server zuerst (AUTH.2 aktivieren), dann Caller (AUTH.3A).**
|
||
|
||
Begründung (kein Zeitfenster mit ungeschützten Writes):
|
||
- Wenn der **Caller zuerst** das SAVE-Token erhält, aber der **Server noch ohne
|
||
Auth** läuft, dann sendet der Caller zwar ein Bearer-Token, aber der Server
|
||
ignoriert es (Produktion läuft aktuell OHNE AUTH.2) → **Writes bleiben
|
||
ungeschützt** (ungültiges Zeitfenster).
|
||
- Wenn der **Server zuerst** AUTH.2 aktiviert (fail-closed), dann verweigert er
|
||
ALLE Writes ohne gültiges Token. In diesem Moment haben die Caller noch KEIN
|
||
Token → **legitime C5-Writes fallen kurzzeitig aus** (kontrolliertes, kurzes
|
||
Fenster). Das ist akzeptabel, weil es FAIL-CLOSED ist (kein ungeschützter Write).
|
||
- **Kein Zeitfenster, in dem Writes ungeschützt bleiben:** Server-Auth zuerst
|
||
schließt das Loch sofort; Caller-Token werden danach injiziert.
|
||
- **Kein Zeitfenster, in dem DELETE ohne Human Gate möglich wird:** DELETE-Token
|
||
wird erst injiziert, wenn das Human Gate (Gate 3) bereits ENFORCED ist (AUTH.3A
|
||
Code ist deployed). DELETE bleibt doppelt abgesichert.
|
||
|
||
### Atomare AUTH.4-Reihenfolge (jeder Schritt einzeln, mit Gate)
|
||
|
||
| # | Schritt | Aktion | Gate/Verifikation |
|
||
|---|---|---|---|
|
||
| 1 | **PRECHECK** | Produktiver Source == Forgejo `edcb4902`; AUTH.2/AUTH.3A Code deployed; kein Token in ENV | `git diff` leer, ENV-Namen ABSENT |
|
||
| 2 | **BACKUP** | Vault + Compose + aktuelle ENV-Namen sichern (KEINE Tokenwerte) | Backup-Datei existiert |
|
||
| 3 | **TOKEN_GENERATION** | Christian erzeugt SAVE+DELETE-Token (Phase 5 Contract), schreibt in Secret-Datei/Coolify | Token in Ziel-ENV, NICHT in Git/Logs |
|
||
| 4 | **SERVER_SECRET_INJECTION** | `TOLARIA_SAVE_TOKEN` + `TOLARIA_DELETE_TOKEN` in Tolaria-Container-ENV (via .env/Coolify) | ENV-Namen PRESENT (Werte nicht prüfen) |
|
||
| 5 | **CALLER_SECRET_INJECTION** | SAVE-Token an C5C Writer, DELETE-Token an DeleteExecutor (NUR nach Boundary-Fix, Phase 3) | Getrennte Boundaries ODER HARD STOP |
|
||
| 6 | **CODE_DEPLOY** | AUTH.2-Source (edcb4902) in Produktion deployen (Container-Restart) | Server startet, Read funktioniert |
|
||
| 7 | **SERVER_RESTART** | Tolaria-Container neu starten (liest neue ENV) | Healthcheck OK |
|
||
| 8 | **READ_SMOKE** | `GET /api/vault/ping`, `/list`, `/content` ohne Token → 200 | Read funktioniert |
|
||
| 9 | **UNAUTH_WRITE_NEGATIVE_TEST** | `POST /save` ohne Token → 401 (fail-closed) | Write DENIED |
|
||
| 10 | **WRONG_SCOPE_NEGATIVE_TEST** | `POST /save` mit DELETE-Token → 401; `POST /delete` mit SAVE-Token → 401 | Scope-Trennung |
|
||
| 11 | **AUTHORIZED_SAVE_TEST** | `POST /save` mit SAVE-Token → 200 (isolierte Fixture) | SAVE funktioniert |
|
||
| 12 | **DELETE_GATE_NEGATIVE_TEST** | `POST /delete` mit DELETE-Token aber OHNE Human Approval → DENIED | Human Gate ENFORCED |
|
||
| 13 | **AUTHORIZED_DELETE_TEST** | `POST /delete` mit DELETE-Token + Human Approval → 200 (NUR falls freigabefähig) | DELETE funktioniert |
|
||
| 14 | **POST_DEPLOY_CHECK** | Read + Write + Scope + kein Token in Logs/inspect | Alle Checks grün |
|
||
| 15 | **FRESH_CHECKER** | Unabhängiger Checker verifiziert alle Gates | PASS |
|
||
|
||
### Wichtige Design-Entscheidungen
|
||
- **Kein Big-Bang:** Jeder Schritt hat ein eigenes Gate; bei Fehler → Rollback
|
||
(Phase 10), nicht weiter.
|
||
- **Schritt 5 (CALLER_SECRET_INJECTION) ist BLOCKED** bis die Boundary-Frage aus
|
||
Phase 3 gelöst ist (C5-Caller in getrennten Container ODER RQ-undurchdringbare
|
||
Injektion). Ohne das bleibt RQ credential-free NICHT erreichbar.
|
||
- **Schritt 13 (AUTHORIZED_DELETE_TEST) ist CONDITIONALLY_READY** — nur falls
|
||
DELETE-Operation freigabefähig ist (siehe Phase 14, Gap-Berücksichtigung).
|
||
- **Server zuerst** minimiert das ungeschützte Write-Fenster auf null.
|
||
|
||
---
|
||
|
||
## 10b. ROLLBACK DESIGN (Phase 10) — FAIL CLOSED, Write-Degraded-Mode
|
||
|
||
> ⚠️ NUR DESIGN. KEINE AUSFÜHRUNG in AUTH.3B.
|
||
|
||
### Grundprinzip
|
||
- **FAIL CLOSED bevorzugen.** Bei Credential-/Auth-Problem NICHT automatisch auf
|
||
unauthentifizierte Writes zurückrollen.
|
||
- **Ein Rollback darf NICHT bedeuten:** "Auth ausschalten und unauthentifizierte
|
||
Writes wieder erlauben", wenn dadurch die bereits identifizierte CRITICAL Gap
|
||
(Produktion läuft aktuell OHNE Auth) wieder geöffnet wird.
|
||
- **Bevorzugter Security-Rollback = WRITE_DEGRADED_MODE:**
|
||
- **READ AVAILABLE** (Read-Endpunkte funktionieren weiter)
|
||
- **WRITE DISABLED** (mutierende Endpunkte bleiben DENIED)
|
||
|
||
### Exakte Rollback-Pfade
|
||
|
||
| Fall | Symptom | Rollback-Aktion | Security-Position |
|
||
|---|---|---|---|
|
||
| **A) Server startet nicht** | Container-Crash nach AUTH.2-Deploy | Source auf `b5a5e4bd` (Basisstand) zurück; ENV-Tokens entfernen; Restart | FAIL CLOSED (kein Write ohne Auth) |
|
||
| **B) Read-Endpunkte kaputt** | `/ping`, `/list`, `/content` 500 | Source auf `b5a5e4bd` zurück; ENV-Tokens entfernen; Restart | FAIL CLOSED |
|
||
| **C) C5C SAVE schlägt fehl** | SAVE-Writes 401/500 | SAVE-Token prüfen/erneuern; NICHT Auth deaktivieren | WRITE_DEGRADED (SAVE disabled, READ available) |
|
||
| **D) DELETE-Caller schlägt fehl** | DELETE-Writes 401/500 | DELETE-Token prüfen/erneuern; NICHT Human Gate deaktivieren | WRITE_DEGRADED (DELETE disabled, READ available) |
|
||
| **E) Token falsch injiziert** | Wrong-Scope 401 | Token korrekt injizieren; NICHT Auth deaktivieren | FAIL CLOSED |
|
||
| **F) Token-Leak vermutet** | Token in Logs/inspect/Repo | Token SOFORT rotieren (Phase 11); NICHT Auth deaktivieren | FAIL CLOSED |
|
||
| **G) Falscher Scope** | SAVE-Token autorisiert DELETE (o. umgekehrt) | Scope-Trennung prüfen; Token neu generieren | FAIL CLOSED |
|
||
| **H) Regression nach Restart** | Read/Write unerwartet | Source auf `b5a5e4bd` zurück; ENV-Tokens entfernen; Restart | FAIL CLOSED |
|
||
|
||
### WRITE_DEGRADED_MODE (bevorzugter Security-Rollback)
|
||
- **READ AVAILABLE:** `/api/vault/ping`, `/list`, `/content`, `/all-content`,
|
||
`/entry`, `/search` funktionieren (kein Token nötig).
|
||
- **WRITE DISABLED:** `/api/vault/save`, `/rename`, `/rename-filename`, `/delete`,
|
||
`/command` bleiben DENIED (401) — fail-closed, weil Token-Konfiguration entfernt
|
||
oder ungültig ist.
|
||
- **Wie erreicht:** ENV-Tokens aus Tolaria-Container entfernen (oder auf ungültig
|
||
setzen) + Restart. Server bleibt fail-closed (leeres expected → 401).
|
||
- **WICHTIG:** Das ist KEIN "Auth ausschalten". Es ist "Auth auf DENIED-ALL
|
||
setzen" — Writes sind blockiert, nicht ungeschützt.
|
||
|
||
### Warum NICHT auf unauthentifizierte Writes zurückrollen
|
||
- Die Produktion läuft aktuell OHNE Auth (CRITICAL Gap, verifiziert in Phase 1).
|
||
- Ein Rollback auf "Auth aus" würde dieses Gap wieder öffnen und Writes ungeschützt
|
||
lassen.
|
||
- WRITE_DEGRADED_MODE hält Writes geschützt (DENIED), während Read verfügbar bleibt
|
||
— das ist die sichere Zwischenposition bis zur Fehlerbehebung.
|
||
|
||
---
|
||
|
||
## 11b. TOKEN ROTATION / REVOCATION (Phase 11) — SAVE/DELETE getrennt
|
||
|
||
> ⚠️ NUR DESIGN. KEINE AUSFÜHRUNG in AUTH.3B.
|
||
|
||
### Unterstützt AUTH.2 nur einen Token pro Scope?
|
||
- **JA.** `VaultAuthConfig` (`vite.config.ts` Z.36-39) hält `saveToken` und
|
||
`deleteToken` als **einzelne Strings** (kein Array, keine Multi-Token-Unterstützung).
|
||
- `authorizeVaultWrite` (Z.120-126) vergleicht gegen genau EINEN erwarteten Token
|
||
pro Scope.
|
||
- **Konsequenz:** Zero-downtime-Rotation (zwei gültige Tokens gleichzeitig) ist
|
||
mit AUTH.2 NICHT möglich. Rotation erfordert ein kurzes Write-Downtime-Fenster.
|
||
|
||
### SAVE-Rotation (minimales Write-Downtime-Fenster)
|
||
1. Neues SAVE-Token erzeugen (Phase 5 Contract).
|
||
2. Neues Token in Tolaria-Container-ENV schreiben (ersetzt altes).
|
||
3. Tolaria-Container neu starten (liest neues Token).
|
||
4. C5C Writer erhält neues SAVE-Token (nach Boundary-Fix, Phase 3).
|
||
5. Altes SAVE-Token ist sofort ungültig (Server vergleicht nur gegen neues).
|
||
6. **Write-Downtime-Fenster:** zwischen Schritt 3 und 4 sendet C5C Writer noch
|
||
altes Token → 401. Fenster = Zeit bis C5C Writer aktualisiert ist.
|
||
7. **Verifikation:** `POST /save` mit neuem Token → 200; mit altem → 401.
|
||
|
||
### DELETE-Rotation (analog, getrennt)
|
||
1. Neues DELETE-Token erzeugen.
|
||
2. In Tolaria-Container-ENV schreiben (ersetzt altes).
|
||
3. Tolaria-Container neu starten.
|
||
4. DeleteExecutor erhält neues DELETE-Token.
|
||
5. Altes DELETE-Token sofort ungültig.
|
||
6. **Verifikation:** `POST /delete` mit neuem Token + Human Approval → 200; mit
|
||
altem → 401.
|
||
|
||
### Laufende Retries / queued Jobs
|
||
- **Retries:** Ein laufender Retry mit altem Token schlägt nach Rotation fehl (401).
|
||
Der Retry muss das neue Token verwenden (Client-Instanz neu konstruieren).
|
||
- **Queued Jobs:** C5-Queue-Einträge, die vor Rotation geplant wurden, verwenden
|
||
beim Ausführen das aktuelle Token (Client wird zur Laufzeit konstruiert). Nach
|
||
Rotation nutzen sie das neue Token.
|
||
- **Empfehlung:** Rotation in einem Wartungsfenster durchführen, wenn keine
|
||
kritischen Writes anstehen.
|
||
|
||
### DELETE-Revocation (unabhängig von SAVE)
|
||
- **DELETE-Revocation muss unabhängig von SAVE möglich sein:** Ja — DELETE-Token
|
||
ist eine separate ENV-Variable (`TOLARIA_DELETE_TOKEN`). Entfernen/ersetzen
|
||
dieser Variable + Restart widerruft DELETE, ohne SAVE zu beeinflussen.
|
||
- **SAVE-Revocation analog:** `TOLARIA_SAVE_TOKEN` entfernen/ersetzen + Restart
|
||
widerruft SAVE, ohne DELETE zu beeinflussen.
|
||
- **Revocation = sofort wirksam** nach Container-Restart (Server liest ENV beim
|
||
Start; kein Cache).
|
||
|
||
### Welche Komponenten müssen restart/reload?
|
||
- **Tolaria-Server-Container:** Restart nötig (liest ENV beim Start).
|
||
- **C5C Writer / DeleteExecutor:** Client-Instanz neu konstruieren (neues Token).
|
||
Kein Container-Restart nötig, wenn Token via ENV/Config injiziert wird und der
|
||
Prozess neu gestartet wird.
|
||
|
||
### Wie wird Rotation verifiziert?
|
||
- **Positiv:** Write mit neuem Token → 200.
|
||
- **Negativ:** Write mit altem Token → 401 (fail-closed).
|
||
- **Scope:** SAVE-Token autorisiert nicht DELETE und umgekehrt.
|
||
- **Kein Token-Leak:** neues Token nicht in Logs/inspect/Repo.
|
||
|
||
---
|
||
|
||
## 12b. BACKUP / RECOVERY (Phase 12) — Secret-Handling
|
||
|
||
> ⚠️ NUR DESIGN. KEINE Backup-Konfiguration ändern in AUTH.3B.
|
||
|
||
### Werden Secret-Werte in normalen Backups erfasst?
|
||
- **NEIN.** Das Tolaria-Backup-Skript (`/opt/data/bin/tolaria_backup.py`) sichert
|
||
NUR Vault-Content (`.md`-Dateien) und App-Source über die read-only API
|
||
(`/api/vault/list`, `/api/vault/content`). Es sichert KEINE ENV-Variablen,
|
||
KEINE Compose-Datei, KEINE Secret-Dateien.
|
||
- **Sollten sie enthalten sein?** NEIN. Secrets gehören NICHT in Backups. Sie
|
||
werden separat (außerhalb des Backup-Pfads) verwaltet und bei Restore neu
|
||
injiziert.
|
||
|
||
### Wie wird Disaster Recovery durchgeführt?
|
||
1. Vault-Content aus Backup wiederherstellen (via `tolaria_backup.py` oder
|
||
Vault-Git-Restore).
|
||
2. App-Source aus Forgejo (`edcb4902`) neu deployen (rebuildbar, Forgejo=SoT).
|
||
3. **Secrets NEU injizieren** (nicht aus Backup): Christian erzeugt frische
|
||
SAVE/DELETE-Tokens (Phase 5 Contract) und schreibt sie in die Container-ENV.
|
||
|
||
### Müssen Tokens nach Restore rotiert werden?
|
||
- **JA, zwingend.** Nach einem Disaster Recovery werden frische Tokens erzeugt
|
||
(nicht die alten aus dem Backup). Das verhindert, dass alte, möglicherweise
|
||
kompromittierte Tokens weiterleben.
|
||
|
||
### Können alte Backups alte gültige Tokens enthalten?
|
||
- **NEIN** (aktuell): Das Backup-Skript sichert keine Secrets. Aber falls in
|
||
Zukunft ein Backup die Compose-Datei oder ENV mit aufnimmt, könnten alte Tokens
|
||
enthalten sein.
|
||
|
||
### Wie verhindern wir Secret-Restore aus veraltetem Backup?
|
||
- **Backup-Skript sichert keine Secrets** (verifiziert, Z.17 "Keine Secrets im
|
||
Manifest/Log").
|
||
- **Restore-Prozedur injiziert IMMER frische Tokens** (nie aus Backup).
|
||
- **Empfehlung:** Falls ein Backup je Compose/ENV aufnimmt, Secrets vor dem
|
||
Backup strikt ausschließen (`.gitignore`-artige Exklusion) und nach Restore
|
||
rotieren.
|
||
|
||
### Empfehlung (dokumentiert, KEINE Änderung)
|
||
- Secrets NIE in Backups aufnehmen.
|
||
- Nach jedem Restore frische Tokens erzeugen.
|
||
- Backup-Manifest/Logs secret-frei halten (bereits der Fall).
|
||
|
||
---
|
||
|
||
## 13b. ISOLATED DEPLOYMENT TEST PLAN (Phase 13) — AUTH.4
|
||
|
||
> ⚠️ NUR TESTPLAN. KEINE produktiven Mutationsprobes in AUTH.3B.
|
||
> Alle Tests werden in AUTH.4 in isolierter Umgebung (Fixture-Vault, synthetische
|
||
> Tokens) ausgeführt, NICHT gegen Produktion.
|
||
|
||
| ID | Test | Erwartung | Typ |
|
||
|---|---|---|---|
|
||
| T1 | READ ohne Token | `/ping`, `/list`, `/content` → 200 | Positiv |
|
||
| T2 | SAVE ohne Token | `POST /save` → 401 (fail-closed) | Negativ |
|
||
| T3 | SAVE wrong token | `POST /save` mit falschem Token → 401 | Negativ |
|
||
| T4 | SAVE DELETE-token | `POST /save` mit DELETE-Token → 401 (Scope) | Negativ |
|
||
| T5 | SAVE korrektes SAVE-token | `POST /save` mit SAVE-Token → 200 | Positiv |
|
||
| T6 | DELETE ohne Token | `POST /delete` → 401 (fail-closed) | Negativ |
|
||
| T7 | DELETE wrong token | `POST /delete` mit falschem Token → 401 | Negativ |
|
||
| T8 | DELETE SAVE-token | `POST /delete` mit SAVE-Token → 401 (Scope) | Negativ |
|
||
| T9 | DELETE korrektes Token, ohne Human Approval | → DENIED (Gate 3) | Negativ |
|
||
| T10 | DELETE Approval, aber ohne Token | → 401 (fail-closed) | Negativ |
|
||
| T11 | DELETE Token + Approval | → 200 NUR falls ausdrücklich freigegeben | Positiv |
|
||
| T12 | missing server ENV | Server startet, Writes DENIED (fail-closed) | Negativ |
|
||
| T13 | missing caller ENV | Caller bricht vor HTTP ab (fail-closed) | Negativ |
|
||
| T14 | Restart erhält Security State | Nach Restart weiterhin fail-closed | Positiv |
|
||
| T15 | kein Token in Logs | Logs secret-frei | Negativ |
|
||
| T16 | kein Token in inspect | `docker inspect` secret-frei (soweit Architektur) | Negativ |
|
||
| T17 | kein Token in Repo | Git-History secret-frei | Negativ |
|
||
| T18 | kein Token im Vault | Vault-Content secret-frei | Negativ |
|
||
| T19 | kein Token in DB | C5-DB secret-frei | Negativ |
|
||
| T20 | Retry sicher | Retry behält Credential, kein Leak | Positiv |
|
||
| T21 | Replay sicher | Replay behält Credential, kein Leak | Positiv |
|
||
| T22 | READ während Write-Degraded-Mode | Read 200, Write DENIED | Positiv |
|
||
| T23 | SAVE/DELETE getrennt widerrufbar | Revocation eines Scopes beeinflusst anderen nicht | Positiv |
|
||
| T24 | Path traversal weiterhin denied | `../` → 401/400 | Negativ |
|
||
| T25 | Symlink escape weiterhin denied | Symlink → 401/400 | Negativ |
|
||
| T26 | `/api/vault/command` kann Auth nicht umgehen | Command ohne Token → 401 | Negativ |
|
||
|
||
### Test-Ausführungsregeln (AUTH.4)
|
||
- Alle Tests in isolierter Umgebung (Fixture-Vault, synthetische Tokens
|
||
`auth4-...`), NICHT gegen Produktion.
|
||
- T11 (DELETE Token + Approval) NUR wenn DELETE-Operation freigabefähig ist
|
||
(siehe Phase 14).
|
||
- Keine produktiven Mutationsprobes in AUTH.3B.
|
||
|
||
---
|
||
|
||
## 14b. AUTH.4 GO/NO-GO MATRIX (Phase 14)
|
||
|
||
> ⚠️ NUR BEWERTUNG. KEINE Aktivierung in AUTH.3B.
|
||
|
||
| Punkt | Status | Konkrete Blocker |
|
||
|---|---|---|
|
||
| **SERVER_AUTH_DEPLOYMENT** | **CONDITIONALLY_READY** | AUTH.2-Code (edcb4902) ist fertig + getestet (70/70, Fresh Checker 17/17). Blocker: produktiver Source muss == Forgejo `edcb4902` sein; ENV-Tokens müssen injiziert werden; Container-Restart nötig. Kein Code-Blocker. |
|
||
| **SAVE_ACTIVATION** | **CONDITIONALLY_READY** | AUTH.3A-Code (a11c1bb) fertig + getestet (19/19, 318/318). Blocker: SAVE-Token muss an C5C Writer injiziert werden; **Boundary-Frage (Phase 3) muss gelöst sein** — sonst hätte RQ Zugriff auf SAVE-Token. |
|
||
| **DELETE_CREDENTIAL_ACTIVATION** | **BLOCKED** | DELETE-Token-Injektion an DeleteExecutor erfordert getrennte Boundary (Phase 3). Aktuell laufen SAVE+DELETE im selben Container → RQ hätte Zugriff auf BEIDE Tokens. **HARD STOP bis Boundary-Fix.** |
|
||
| **DELETE_OPERATION_ACTIVATION** | **BLOCKED** | Zusätzlich zum Boundary-Fix: HUMAN_APPROVAL_AUTHENTICITY_GAP ist OPEN. DELETE-Operation erst nach Gap-Schließung freigeben (kombinierter Angriff Gap+Token). |
|
||
| **HERMES_READ_ONLY_AUTONOMY** | **CONDITIONALLY_READY** | Read-Endpunkte brauchen kein Token; RQ bleibt credential-free für Read. Blocker: keine (Read ist bereits möglich). |
|
||
| **HERMES_BOUNDED_AUTONOMY** | **BLOCKED** | Erfordert SAVE/DELETE-Aktivierung, die wiederum Boundary-Fix + Gap-Schließung voraussetzt. |
|
||
|
||
### Kern-Blocker (zusammenfassend)
|
||
1. **CREDENTIAL_SEPARATION (Phase 3):** SAVE+DELETE laufen im selben Container
|
||
(`06a4a73573d0`). RQ credential-free ist technisch NICHT erreichbar, solange
|
||
beide Tokens in die ENV dieses Containers injiziert würden. → **HARD STOP für
|
||
DELETE_CREDENTIAL_ACTIVATION.**
|
||
2. **HUMAN_APPROVAL_AUTHENTICITY_GAP (OPEN):** DELETE-Operation erst nach
|
||
Gap-Schließung. → **BLOCKED für DELETE_OPERATION_ACTIVATION.**
|
||
3. **SERVER_AUTH_DEPLOYMENT** und **SAVE_ACTIVATION** sind CONDITIONALLY_READY
|
||
(Code fertig), aber SAVE_ACTIVATION hängt an der Boundary-Frage.
|
||
|
||
### Empfohlene Reihenfolge (AUTH.4, nach Freigabe)
|
||
1. SERVER_AUTH_DEPLOYMENT (AUTH.2 aktivieren) — sicher, verschärft Gap nicht.
|
||
2. Boundary-Fix (C5-Caller in getrennten Container ODER RQ-undurchdringbare
|
||
Injektion) — VORAUSSETZUNG für SAVE/DELETE-Credential-Aktivierung.
|
||
3. SAVE_ACTIVATION (nach Boundary-Fix).
|
||
4. Gap-Schließung (separate Freigabe).
|
||
5. DELETE_CREDENTIAL_ACTIVATION + DELETE_OPERATION_ACTIVATION (nach 2+4).
|
||
|
||
---
|
||
|
||
## 15. INCIDENT (AUTH.3B, 2026-08-27)
|
||
|
||
**INCIDENT_OCCURRED = TRUE** (während Phase 1 Discovery)
|
||
- Versehentlicher mutierender POST `POST /api/vault/save` gegen Produktion
|
||
(`_auth3b_probe.md`, Inhalt "x") — Verstoß gegen die verbindliche Incident-Regel
|
||
(NO MUTATING HTTP METHOD PROBES).
|
||
- **INCIDENT_RECOVERED = TRUE** — Datei sofort via `POST /api/vault/delete` entfernt,
|
||
Read-Back bestätigt: Datei existiert nicht mehr.
|
||
- **PERMANENT_DAMAGE = NONE VERIFIED**
|
||
- **ROOT_CAUSE = unsafe mutating endpoint probe during read-only discovery**
|
||
- **BESTÄTIGT:** Produktion läuft OHNE AUTH.2 (save ohne Token → 200, kein 401).
|
||
- **LEHRE:** Keine weiteren mutierenden POSTs. Nur read-only GET/Code-Reading.
|