nexus-stack: tighten 3 review findings (audit-on-block caveat, arch-test pattern scope, fail-closed only for nullable settings)
This commit is contained in:
@@ -35,7 +35,7 @@ Regeln: `configure_logging` vor allem anderen. Neue Router über `include_router
|
||||
|
||||
## Settings (`src/nexus/config.py`)
|
||||
|
||||
Eine `Settings`-Klasse (pydantic-settings, Prefix `NEXUS_`), Zugriff via `get_settings()` (lru_cache). Tests leeren den Cache. `Settings.__repr__` redacted Secrets — bei neuen Secret-Feldern (`*_secret`, `*_password`, DSN) beibehalten. Werte kommen prod aus `/etc/nexus/env`. Reale Felder u.a.: `host` (Default `127.0.0.1`), `public_url`, `database_url`, `oidc_*`, `session_secret`, `llm_policy_path`, `anthropic_api_key`. **Fail-closed by default:** fehlende Konfiguration ⇒ Feature aus (None), nie ein unsicherer Default.
|
||||
Eine `Settings`-Klasse (pydantic-settings, Prefix `NEXUS_`), Zugriff via `get_settings()` (lru_cache). Tests leeren den Cache. `Settings.__repr__` redacted Secrets — bei neuen Secret-Feldern (`*_secret`, `*_password`, DSN) beibehalten. Werte kommen prod aus `/etc/nexus/env`. Reale Felder u.a.: `host` (Default `127.0.0.1`), `public_url`, `database_url`, `oidc_*`, `session_secret`, `llm_policy_path`, `anthropic_api_key`. **Fail-closed gilt für die nullable Secrets/DSN:** ohne `database_url`/`oidc_discovery_url`/`oidc_client_secret`/`session_secret`/`anthropic_api_key` ist das jeweilige Feature aus. Achtung: NICHT alle Felder sind so — `oidc_client_id` hat den Default `nexus-web`, `public_url` einen http-localhost-Default, und ohne `session_secret` läuft die SessionMiddleware mit einem EPHEMEREN Secret. Den OIDC-Login schaltet erst `build_oauth` fail-closed ab (None ⇒ `/auth/login` 503), wenn Discovery/Secret/Session-Secret fehlen oder in Prod `public_url` nicht https ist.
|
||||
|
||||
## Lese-Pfad: `nexus/visibility.py` ist der EINZIGE Query-Weg (SICHT-1/2/3)
|
||||
|
||||
@@ -52,7 +52,7 @@ if entry is None: raise HTTPException(404, "not found") # nicht-sicht
|
||||
- Sichtbarkeit wird IN der Query durchgesetzt (kein Python-Nachfilter); `viewer` ist Pflichtparameter. Share-Zeilen wirken nur bei `visibility=='geteilt'`.
|
||||
- SICHT-2: nicht-sichtbar liefert `None` ⇒ überall dieselbe 404 (gleicher Text), nie ein abweichender Code/Zählwert.
|
||||
- Kein Admin-Bypass auf Inhalte.
|
||||
- **Erzwungen:** `tests/test_architecture.py` scannt `src/` und verbietet `select(Entry…)`, `.get(Entry…)`, `aliased(…)`, `models.X` und Raw-SQL auf die geschützten Tabellen außerhalb von `visibility.py`/`models.py`. Wer eine neue sichtbarkeitsrelevante Query braucht: Helper IN `visibility.py` ergänzen, nicht den Test aufweichen. Jede neue Lese-Route kommt zusätzlich in die **SICHT-4-Suite** (`tests/test_sicht4.py`).
|
||||
- **Erzwungen:** `tests/test_architecture.py` scannt `src/` und verbietet die geschützten Modelle (`Entry`, auch als `models.Entry`) INNERHALB der Query-Konstrukte `select(…)`, `.get(…)`, `.query(…)`, `aliased(…)` sowie Raw-SQL (`text(…)`) auf die geschützten Tabellen — alles außerhalb von `visibility.py`/`models.py`. Ein bloßer `models.Entry`-Bezug (z. B. Typannotation) ist erlaubt; der verbotene Pfad ist die Query. Wer eine neue sichtbarkeitsrelevante Query braucht: Helper IN `visibility.py` ergänzen, nicht den Test aufweichen. Jede neue Lese-Route kommt zusätzlich in die **SICHT-4-Suite** (`tests/test_sicht4.py`).
|
||||
|
||||
## LLM: `router.run(...)` ist die EINZIGE Modell-Schnittstelle (PRIV-1…6)
|
||||
|
||||
@@ -64,7 +64,7 @@ with LLMRouter() as r:
|
||||
```
|
||||
|
||||
Hartverdrahtete Garantien (nicht umbauen ohne ADR):
|
||||
- **Reihenfolge in `run`:** PRIV-6-Sensitivitäts-Pre-Check → Policy-Decision → forbidden? → Endpoint-Auflösung → **Hard Guard** → Health-Check (lokal) → Call. Jeder Fehlerpfad endet in `blocked` + Audit; **kein** externer Fallback, auch nicht bei Timeout.
|
||||
- **Reihenfolge in `run`:** PRIV-6-Sensitivitäts-Pre-Check → Policy-Decision → forbidden? → Endpoint-Auflösung → **Hard Guard** → Health-Check (lokal) → Call. Jeder Fehlerpfad endet in `blocked`; **kein** externer Fallback, auch nicht bei Timeout. Das begleitende Audit hängt am `audit_sink`: per Default schreibt der Router via Session, und ohne erreichbare/ konfigurierte DB (`audit_session`/`NEXUS_DATABASE_URL`) kann der Audit-Write fehlschlagen — der Router gibt dann zwar `blocked` zurück, das Event ist aber nicht garantiert persistiert. Wer das Audit verlässlich braucht (K-108 Queue), injiziert eine Session/Sink.
|
||||
- **Hard Guard:** `privat`/`familie` an `kind=="external"` ⇒ `blocked` — zweite Verteidigungslinie vor der Policy.
|
||||
- **Allowlist:** `EndpointConfig` validiert `kind=="local"` gegen `LOCAL_ENDPOINT_ALLOWLIST` (gepinnte luki-ai host:port) — eine als „local" getarnte externe URL wird beim Policy-Load abgewiesen. Endpoints sind Konfiguration (`config/llm_policy.default.json`), nie Code.
|
||||
- Policy-Änderung nur über `write_policy_as_admin` (admin-only, auditiert).
|
||||
|
||||
Reference in New Issue
Block a user