# CyberBrain26 — bezpečnostní list (one-pager)

| Položka | Hodnota |
|---|---|
| **Status** | Rámcový bezpečnostní list kódu. Pokud není výslovně uvedeno „hypotéza / plán“, je tvrzení **ověřeno v kódu**. |
| **Datum** | 2026-10-02 |
| **Autor** | CyberBrain26 project |
| **Rozsah** | Popisuje lokální prototyp z `src/cyberbrain26`. **Není** bezpečnostní certifikací a **není** zárukou ochrany proti libovolnému nepřátelskému kódu. |

> **Prohlášení o rozsahu.** Tento dokument popisuje **testovaný Python prototyp**. Není produkčně připravený, není bezpečnostně certifikovaný a neposkytuje záruku ochrany proti škodlivému nebo libovolně nepřátelskému kódu. Bubblewrap je zde použit jako omezený sandbox pro lokální prototyp, **nikoli** jako bezpečnostní záruka. Jakékoli bezpečnostní tvrzení níže je nutné před nasazením znovu posoudit nezávislou revizí.

Navazuje na `ROADMAP_V5.md` Etapa 3 krok 7; sandboxová rizika jsou podrobněji v `EXECUTION_SECURITY_REVIEW.md` (zde je shrnutí, nikoli kopie).

---

## 1. Podporovaná konfigurace

| Prvek | Podporováno | Stav tvrzení |
|---|---|---|
| OS | Linux (namespaces + `bwrap`) | **Ověřeno**: `execution_plane/bubblewrap_provider.py:100-123` skládá argv s `--unshare-all`; na tomto hostu `bwrap --version` = `bubblewrap 0.9.0` |
| Python | `>= 3.11` | **Ověřeno**: `pyproject.toml` `requires-python = ">=3.11"`, `target-version = "py311"` |
| Uživatelé | jeden lokální operátor | **Hypotéza / plán**: víceuživatelská izolace, SSO a centrální audit nejsou implementovány (§8, §9) |
| Rozhraní | lokální CLI `cyberbrain` | **Ověřeno**: `pyproject.toml` `cyberbrain = "cyberbrain26.cli:main"`; příkazy `version` (`cli.py:365`), `plan` (`cli.py:371`), `run` (`cli.py:446`), `approvals` (podřízená app `product/approvals.py:61`, registrovaná `cli.py:66`), `doctor` (podřízená app `product/doctor.py:969`, registrovaná `cli.py:67`) |
| Sandbox | Bubblewrap | **Ověřeno**: `PtahBridge.__init__` použije `execution_provider or BubblewrapExecutionProvider()` (`orchestration/ptah_bridge.py:188-190`) |
| Stav | lokální SQLite | **Ověřeno**: `data_plane/sqlite_store.py`, `SQLiteDatabase`, `PRAGMA journal_mode = WAL` (`:231-258`) |
| macOS / Windows | **nepodporováno** | **Ověřeno**: v `src/` není alternativní execution backend |
| cgroup limity, šifrování dat, omezení processu | **nepodporováno** | viz §4, §5 |

„Jiný OS / jiný sandbox režim“ není podporovaná konfigurace; rozšíření vyžaduje samostatnou bezpečnostní revizi, ne jen konfiguraci.

## 2. Kontrolní pořadí a chování při selhání

Koncepční pořadí podle `AGENTS.md`: **identita → politika → oprávnění → bezpečnostní kontroly → vyhodnocení → audit**. V kódu:

| Krok | Zdroj | Chování při selhání |
|---|---|---|
| 1. Identita (důvěra) | `PermissionGateway.evaluate` Step 0 (`security_plane/permission/__init__.py:130-145`) | `TrustLevel.UNTRUSTED` → `DENY`, audit `IDENTITY_CHECK` / `denied` s `error="untrusted_agent"`. Totéž kontroluje `PolicyEngine.evaluate` (`policy/__init__.py:284-292`) |
| 2. Identita (scope) | `PermissionGateway.evaluate` Step 1 + `ACTION_SCOPE_MAP` (`permission/__init__.py:82-95,147-165`) | Chybějící scope → `DENY` s `missing_scope:<scope>`. Akce mimo mapu scope se nekontroluje (§10) |
| 3. Politika | Step 2 → `PolicyEngine.evaluate` (`policy/__init__.py:263-316`) | Default deny: `_load_default_rules` vkládá `default_deny` jako poslední pravidlo. Podmínky se vyhodnocují přes allowlist AST (`evaluate_condition`, `policy/__init__.py:155-166`) |
| 4. Mapování | Step 3 (`permission/__init__.py:187-244`) | `DENY` / `REQUIRE_APPROVAL` / `ALLOW` |
| 5. Schválení | `AuthorizedExecutionService._wait_for_approval` (`execution_plane/secure_gateway.py:142-222`) | viz níže |
| 6. Sandbox | `execute` → `provider.execute` (`secure_gateway.py:63-140`; volání `provider.execute` `secure_gateway.py:113-119`, handling chyb `secure_gateway.py:120-129`) | Chybějící `bwrap` → `RuntimeError` **před spuštěním procesu**; žádný nesandboxovaný fallback |
| 7. Audit | `AuthorizedExecutionService._audit` (`secure_gateway.py:224-242`) | Audit se zapisuje **na každém kroku**, nejen na konci: `TOOL_CALLED/started`, `TOOL_RESULT/succeeded|failed` |

**Selhání v kroku schválení** (`secure_gateway.py`, `security_plane/approval/__init__.py`):

| Situace | Chování | Zdroj |
|---|---|---|
| Chybí `approval_manager` | audit `APPROVAL_DENIED` / `unavailable` + `ExecutionRefused(f"Approval unavailable for action '{action}'")` (podtřída `SecurityError`) | `secure_gateway.py:150-160` |
| Timeout | po `check_expired()` se zbývající `PENDING` zruší (`ApprovalStatus.CANCELLED`), audit `APPROVAL_DENIED`, `ExecutionRefused(f"Approval {status} for action '{action}'")` (podtřída `SecurityError`). Čekání = `min(approval_timeout_seconds, manager.timeout_seconds)`; knihovní default `min(30.0, 3600.0)` = 30 s, CLI default `min(300.0, 3600.0)` = 300 s (`cli.py:86`) | `secure_gateway.py:177-222`; `approval/__init__.py:83` |
| `asyncio.CancelledError` | `manager.cancel(...)` + audit `APPROVAL_DENIED` / `cancelled` + propagace výjimky | `secure_gateway.py:197-207` |
| Deny / změna stavu reviewerem | průlom smyčky → `ExecutionRefused` (podtřída `SecurityError`) s konkrétním stavem (`denied` / `expired` / `cancelled` / `missing`) | `secure_gateway.py:209-222` |

**Selhání sandboxu.** `BubblewrapExecutionProvider.execute` volá `shutil.which("bwrap")` a při `None` vyhodí `RuntimeError("Bubblewrap executable 'bwrap' is unavailable; refusing to run code without a sandbox")` (`bubblewrap_provider.py:73-78`). Výjimka propadne přes `AuthorizedExecutionService.execute`, zapíše audit `TOOL_RESULT` / `failed` s `type(exc).__name__` a je znovu vyhozena (`secure_gateway.py:120-129`). **Fail-closed: žádný pokus o běh na hostu.** Nepodporovaný jazyk vyhodí `ValueError` před spuštěním procesu (`bubblewrap_provider.py:63-71`).

**Přesné `bwrap` argumenty** (`bubblewrap_provider.py:100-123`): `--unshare-all`, `--die-with-parent`, `--new-session`, `--ro-bind /usr /usr`, dále `--ro-bind /lib /lib` a `--ro-bind /lib64 /lib64` jen pokud cesty existují, poté `--dev /dev`, `--proc /proc`, `--tmpfs /tmp`, `--bind <workspace> /workspace`, `--chdir /workspace`, `--clearenv`, `--setenv PATH /usr/bin:/bin`, `--setenv HOME /tmp`, `--`. Před `exec` se aplikují rlimity (`_limited_command`, `:168-192`): `RLIMIT_CPU` 10 s, `RLIMIT_AS` 512 MB, `RLIMIT_FSIZE` 16 MB. `stdout` / `stderr` je omezen na 1 000 000 bajtů (`_read_capped`, `bubblewrap_provider.py:194-206`).

## 3. Režimy policy

- `PolicyMode.SAFE` je **default** (`PolicyEngine.__init__(mode=PolicyMode.SAFE)`, `policy/__init__.py:197`) a je to co používá produkční call site: `PtahBridge` vytváří `PolicyEngine(audit_log=self.audit_log)` bez argumentu `mode` (`ptah_bridge.py:180`).
- **Režim nelze změnit zvenčí.** `src/cyberbrain26` nečte žádnou env proměnnou, konfigurační soubor ani CLI flag pro `PolicyMode`. `PolicyMode.FULL`, `RESEARCH` a `RED_TEAM_LAB` existují v kódu, ale z CLI nejsou dosažitelné.
- Pravidla se načítají v `PolicyEngine._load_default_rules()` volaném z `__init__` (`:203-244`). Přiřazení `engine.mode = ...` **znovu nenačte pravidla** — režim a pravidla se rozejdou. Režim měnit jen při konstrukci.
- `SAFE` vkládá před `default_deny` dvě pravidla: `safe_mode_sensitive_requires_approval` (`condition="is_sensitive(action)"` → `REQUIRE_APPROVAL`) a `safe_mode_non_sensitive_allowed` (`condition="not is_sensitive(action)"` → `ALLOW`). **Citlivé akce bez schválení neprojdou; necitlivé jsou povoleny implicitně.**
- Mimo `SAFE` se vkládá `default_allow` pro všechny akce (`:235-245`); v podporované cestě se tyto režimy nepoužívají.
- Audit policy: `_audit_decision` zapisuje jen `DENY` a `REQUIRE_APPROVAL`, v `RED_TEAM_LAB` vše (`:318-348`).

**`SENSITIVE_ACTIONS` — 11 položek** (`PolicyEngine.SENSITIVE_ACTIONS`, `policy/__init__.py:181-195`): `delete_file`, `write_file`, `execute_shell`, `execute_python`, `access_network`, `access_secrets`, `execute_docker`, `publish_content`, `send_external_message`, `credential_access`, `data_upload`.

`publish_content`, `send_external_message`, `credential_access` a `data_upload` **nemají scope mapu** v `PermissionGateway.ACTION_SCOPE_MAP`, takže u nich krok 1 (scope) projde bez kontroly a rozhoduje až krok 3 (policy → schválení).

## 4. Známá omezení

**Ověřeno v kódu** nebo přímo vyplývá z použitých mechanismů. Podrobnější seznam v `EXECUTION_SECURITY_REVIEW.md`.

| Omezení | Důsledek | Zdroj |
|---|---|---|
| Žádný cgroup limit, žádný limit počtu procesů | sandboxed workload může vytvořit libovolně mnoho procesů | `bubblewrap_provider.py:100-123` |
| `RLIMIT_AS` je virtuální adresní prostor, ne agregovaný RSS | neomezuje součet fyzické paměti více procesů | `bubblewrap_provider.py:168-192` |
| `RLIMIT_CPU` / `RLIMIT_FSIZE` lze sandboxed proces zvýšit | při host hard limitu `RLIM_INFINITY` nastaví `_limited_command` (soft, hard) = požadovanou hodnotu a proces si limity může zvýšit. **Nejde o vynucení proti nepřátelskému kódu**, jen o rozumné výchozí omezení proti nečestnému běhu | `bubblewrap_provider.py:170-181` |
| Žádný diskový limit ani kvóta inodeů na workspace | zaplnění host filesystemu není omezeno | `bubblewrap_provider.py:110-123` |
| Workspace je zapisovatelný bind-mount hostovské cesty | obsah je čitelný a změny přežívají běh. **Cesta je považována za důvěryhodnou z designu**; volající musí předat jednorázový workspace vázaný na task | `bubblewrap_provider.py:115` |
| `--ro-bind /usr` a další systémové stropy | předpokládá se standardní rozložení host filesystemu; obsah `/usr` a systémové binární soubory zůstávají součástí attack surface | `bubblewrap_provider.py:105-109` |
| Izolace závisí na host kernel namespaces a install mode Bubblewrap | podporované host konfigurace je třeba v deploymentu ověřit | `EXECUTION_SECURITY_REVIEW.md` |
| Bubblewrap má v historii vlastní CVE | podporovanou verzi `bwrap` je nutné evidovat a sledovat; verze se v kódu nepinuje ani neověřuje | `bubblewrap_provider.py:26` |
| Timeout / zrušení zabíjí jen `bwrap` proces | potomci se řeší přes `--die-with-parent`; cleanup nutno ověřit na každém podporovaném kernelu a buildu | `bubblewrap_provider.py:143-152` |
| SQLite není šifrovaná | data na disku čte vlastník souboru | `sqlite_store.py:139-144,260-264` |

## 5. Co není garantováno

**Audit log není kryptograficky neměnný.** Tvrzení „append-only“ v `security_plane/audit/__init__.py:103-114` je konvence: vlastník hosta může SQLite soubor i WAL přepsat, `AuditLog.record` pouze `INSERT`uje (`:169-204`) a žádné podepisování ani řetězení hashí není implementováno. `AuditLog.clear()` provádí `DELETE FROM audit_events` (`:264-269`). `SQLiteDatabase.prune_expired` maže logické řádky, ale **bezpečně neodstraňuje** staré stránky databáze ani fragmenty WAL (výslovně v docstringu, `sqlite_store.py:195-201`) — smazané údaje zůstávají fyzicky v souboru.

**Provenance hashe jsou pouze integritní kontrola.** `SQLiteMemoryProvider.attest` / `verify` vrací `integrity_only: True` a `signature_verified: False` (`data_plane/sqlite_memory.py:238-245,247-265`). SHA-256 detekuje nechtěnou změnu obsahu, ale vlastník databáze může přepsat obsah i hash. `MemoryTrustLevel` je popisek původu, **nikoli** tvrzení, že je obsah bezpečné spustit (`sqlite_memory.py:21-23`).

**`AIFirewall` je regex-based a není zapojena do cesty spuštění.** Implementace je v `security_plane/firewall/__init__.py` (`AIFirewall`, `PROMPT_INJECTION_PATTERNS`, `CREDENTIAL_PATTERNS`, …). `AuthorizedExecutionService`, `PermissionGateway` ani `PolicyEngine` ji **neinstantují ani nevolají**; `SecurityGate` ji bere jen jako volitelný parametr a bez ní dává `WARN` (`quality_gates/__init__.py:199-213`). Její detekce tedy v reálné cestě běhu dnes nic neblokuje.

**`PrivacyGateway` není DLP ani kontrola odchozích promptů.** `PrivacyGateway.redact` pokrývá 4 pevné regex pravidla (`secret_assignment`, `api_token`, `email`, `phone` — `privacy_gateway.py:35-59`) a je volán **pouze** z `SQLiteMemoryProvider.add_memory` při `redact_on_write=True` (`sqlite_memory.py:90-96`). Obsah promptu odesílaného providerovi privacy gateway **neprochází**.

**Quality gates jsou samohodnotící.** `SecurityGate` a `ArchitectureGate` neinspektují zdrojový kód ani stav systému; řada kontrol přebírá hodnoty z parametrů volajícího. `no_hardcoded_secrets` je vždy `PASS` bez jakékoli kontroly (`quality_gates/__init__.py:240-245`), stejně tak `default_deny_policy` (`:456-461`). Výsledek gate nelze použít jako bezpečnostní důkaz.

## 6. Data uložená lokálně

Výchozí umístění: `platformdirs.user_data_dir("CyberBrain26")` (`product/local_store.py:75`), soubor `cyberbrain.sqlite3` (`DEFAULT_DB_FILENAME`, `:44`). Přesun umožňují **ověřené** konstanty: `ENV_DATA_DIR = "CYBERBRAIN_DATA_DIR"`, `ENV_DB_PATH = "CYBERBRAIN_DB_PATH"`, `ENV_RETENTION_DAYS = "CYBERBRAIN_RETENTION_DAYS"` (`local_store.py:40-45`; precedence: explicitní argument → env → `user_data_dir`). Retention je fail-closed: nečíselná hodnota vyhodí `ValueError` a tiše se nahradit nedá (`retention_days_from_env`).

**Tabulky** (schéma `sqlite_store.py:11-117`, `CURRENT_SCHEMA_VERSION = 1`):

| Tabulka | Co uchovává | Retention |
|---|---|---|
| `tasks` | `task_id`, `handler`, `payload_json`, `status`, `result_json`, `error`, `attempts`, časová razítka | `prune_expired` maže jen `status IN ('completed','failed')` s `completed_at` před cutoffem |
| `approval_requests` | `request_id`, `agent_json` (identita agenta), `action`, `resource`, `context_json`, `risk_score`, `status`, `reviewer`, `reason`, `decision`, `metadata_json` | maže jen `status IN ('approved','denied','expired','cancelled')` starší než cutoff |
| `approval_records` | záznam rozhodnutí: `request_id`, `agent_id`, `action`, `resource`, `decision`, `reviewer`, `timestamp`, `reason` | **nemá vlastní prune větev**; mizí jen přes `ON DELETE CASCADE` po smazání rodiče v `approval_requests` |
| `audit_events` | události bezpečnostního a provozního auditu (`POLICY_DECISION`, `PERMISSION_CHECK`, `APPROVAL_*`, `TOOL_*`, `IDENTITY_CHECK`, `MEMORY_UPDATED`, …) | maže všechny `timestamp` před cutoffem |
| `project_memories` | `project_id`, `content`, `trust_level`, `provenance_id`, `metadata_json`, `expires_at` | maže jen řádky s `expires_at IS NOT NULL` a `expires_at <= now` |
| `provenance_records` | `content_json`, `sha256`, `metadata_json`, `labels_json`, `watermark_json` | **nikdy se nemaže** žádnou větví `prune_expired` |
| `schema_migrations` | `version`, `applied_at` | **nikdy se nemaže** |

**Co `SQLiteDatabase.prune_expired` nikdy nesmaže** (`sqlite_store.py:195-229`): pending schválení, nedokončené úlohy (`status` mimo `'completed'` / `'failed'`), paměti bez `expires_at`, `provenance_records`, `schema_migrations` a `approval_records`, jejichž rodič zůstává. `retention_days` default je 90 (`SQLiteDatabase.__init__(..., retention_days: int = 90)`, `local_store.py:45`). `prune_expired` je **explicitní** operace — nevolá se automaticky při startu, ale z plánovače či údržby.

**Oprávnění a režim SQLite:**

| Vlastnost | Hodnota | Zdroj |
|---|---|---|
| Adresář dat | `0o700` (mkdir `mode=0o700` + `chmod(0o700)` pro nově vytvořený) | `sqlite_store.py:139-144`; `local_store.ensure_data_dir` |
| Soubor DB | `0o600` (`_restrict_file_permissions` po `_initialize` i po každém commitu) | `sqlite_store.py:160-172,260-264` |
| Záloha | `0o600` | `sqlite_store.py:174-188` |
| Journál | `PRAGMA journal_mode = WAL`, `synchronous = FULL` | `sqlite_store.py:231-234` |
| Izolace transakce | `BEGIN IMMEDIATE` + rollback, `foreign_keys = ON`, `busy_timeout = 5000` | `sqlite_store.py:147-172` |
| WAL checkpoint | `PRAGMA wal_checkpoint(TRUNCATE)` — volitelně, `SQLiteDatabase.checkpoint()` | `sqlite_store.py:190-193` |

Oprávnění jsou **best-effort**: `chmod` je obalen v `try / except OSError` a chyba se nereskuje (`sqlite_store.py:260-264`).

## 7. Modelová vrstva a prompt egress

| Adaptér (`src/cyberbrain26/model_layer/`) | Volání | Kam data odcházejí |
|---|---|---|
| `LiteLLMClient` (`litellm_client.py`) | `litellm.completion` / `litellm.acompletion`, `litellm.embedding` / `litellm.aembedding` | **vzdáleně** k API cloud poskytovatele (`model` + volitelně `api_base` z `LiteLLMConfig.base_url`); `ModelGateway` registruje `litellm` jako `api_base="https://api.litellm.ai"`, `api_key_required=True` (`control_plane/model_gateway/__init__.py:93-121`) |
| `OllamaClient` (`ollama_client.py`) | `httpx` na `OllamaConfig.base_url`, default `http://127.0.0.1:11434` | **lokálně** na loopback |
| `LlamaCppClient` (`llama_cpp_client.py`) | `httpx` na `LlamaCppConfig.base_url`, default `http://127.0.0.1:8080` | **lokálně** na loopback |

### `cyberbrain run` a `cyberbrain plan` již model nepoužívají

Rozhodnutí D3 (vlastník, 2. října 2026): `cyberbrain run` **nevolá model vůbec**. Volání `ModelGateway.chat(...)` bylo z exekuční cesty odebráno; `cli.py` už nemá ani import `ModelGateway`. `cyberbrain plan` také nepoužívá model — `PlannerAgent.execute` (`agents/orchestrator.py:151-157`) generuje plán hardcodovaným logikou. Jako důsledek: roadmapy z `plan` obsahují kroky aniž by deklarovaly `action`, a `run` je odmítá místo aby je spustil.

**Ověřený stav:** `ModelGateway` a tři adaptéry stále existuj v `control_plane/model_gateway/__init__.py` a `model_layer/`, ale žádný CLI příkaz jej nikdy nevolá. Přímo přes `run` nebo `plan` tedy **neexistuje** prompt egress.

### OPEN GAP: model layer má LiteLLM-first výchozí, ale nikdo ho nepoužívá

Knihovní výchozí `ModelGatewayConfig` má `default_provider="litellm"`, `fallback_providers=["ollama", "llama.cpp"]` a `prefer_local=True` (`control_plane/model_gateway/__init__.py:43-45`). **Nikdo tuto výchozí konfiguraci nepoužívá** — `run` ani `plan` model nepoužívá. Proklíná se jen jako latentní riziko: kdokoli zavolá `ModelGateway().chat(...)` dostane LiteLLM jako výchozí a tichý fallback. To není chování `run`, ale vlastnost model layeru, která by se mohla projít i v budoucnu.

- `ModelGateway._resolve_route` zkouší model označený jako `local_available`; pro knihovní default `default_provider="litellm"` se žádný lokální provider do routingu nepdostane (`model_gateway/__init__.py:260-287`).
- `ModelGateway.chat` při výjimce z primárního provideru volá `_fallback_chat` (`:246-258,325-349`). **Přepnutí na jiný provider je tiché** — uživatel není dotázán ani informován; do logu jde jen varování.
- `AIFirewall.inspect_external_egress` existuje, ale není zavolaný ani z `ModelGateway.chat` ani z CLI.

**Co není pod kontrolou:** prompt egress není řízen `PrivacyGateway` (§5) a není evidován jako egress v model layeru. Dokud žádný CLI příkaz model nepoužívá, přímo přes `run` nebo `plan` žádný egress neexistuje; ale aplikace, která použije `ModelGateway`, má `default_provider="litellm"` s tichým fallbackem jako knihovní výchozí a nikým neověřený egress.

### Proměnné prostředí s klíči providerů

Hodnoty se **nikdy nelogují ani nevypisují**. V `src/cyberbrain26` **není** kód, který by tyto proměnné četl; `python-dotenv` je v `pyproject.toml` závislost, ale `load_dotenv` se nikde ve `src/` nevolá.

- **Ověřeno v kódu** (konstanty `local_store.py:40-42`): `CYBERBRAIN_DATA_DIR`, `CYBERBRAIN_DB_PATH`, `CYBERBRAIN_RETENTION_DAYS` — umístění lokálního stavu, nikoli klíče providerů.
- **Z `.env.example`** (soubor repozitáře, ne kód aplikace): `LITELLM_MASTER_KEY`, `GEMINI_API_KEY`, `GROQ_API_KEY`, `MISTRAL_API_KEY`, `OPENROUTER_API_KEY`, dále `CB26_DEFAULT_MODEL`, `CB26_PROXY_URL`, `CB26_API_PORT`, `CB26_LITELLM_PORT`. Tyto názvy jsou **plánováno / neověřeno v kódu**: `src/` je nečte a klíč pro konkrétního cloud poskytovatele si načítá až knihovna LiteLLM podle vlastní konvence. Před nasazením je nutné ověřit, kdo a odkud klíče čte, a zabezpečit jeho uložení.

## 8. Kdo spravuje data

| Role | Obor | Základ |
|---|---|---|
| **Lokální operátor** | úplný lokální stav: `tasks`, `approval_requests`, `approval_records`, `audit_events`, `project_memories`, `provenance_records` a `roadmaps.json` v `user_data_dir` | jediný uživatel na hostu; `reviewer` defaultuje na `getpass.getuser()` (`product/approvals.py:72-77`) |
| **Zvolený modelový provider** | samostatný správce obsahu promptu, který zařízení opustí (historie promptů, retenční a smazávací politika poskytovatele) | **Neexistuje** — žádný CLI příkaz model nepoužívá (`cli.py` nemá `ModelGateway` import); hrozí jen jako latentní riziko model layeru (viz §7) |
| **Vývojář / provozovatel instalace** | zálohy, konfigurace `CYBERBRAIN_*`, klíče providerů v prostředí | `SQLiteDatabase.backup`, `.env` |

**Nebezpečný detail:** `AuditLog` i `SQLiteDatabase` stojí na souborech vlastněných operátorem. Operátor je tedy zároveň správcem dat **i** subjektem, proti kterému by audit měl chránit. Bez `hash chain`, podpisu nebo odděleného úložiště nelze audit prokázat jako nepřepsatelný třetí stranou.

## 9. Podporované a mimo rozsah použití

**Podporováno:** jediný lokální uživatel na Linuxu přes lokální CLI; akce, které prošly explicitním schválením, spouštěné v omezeném sandboxu s jedním zapisovatelným workspace zvoleným volajícím; změny, které uživatel po běhu sám zkontroluje a převezme (např. diff); lokální audit a historie schválení v SQLite, dostupné pro revizi.

**Mimo rozsah — není podporováno a není garantováno:** záruka ochrany proti nepřátelskému kódu, kernel exploitům nebo obecné izolaci proti úniku z hostitelského systému; víceuživatelská izolace, správa týmů, SSO, centrální audit; compliance certifikace (ISO 27001, SOC 2, ochrana osobních údajů) a související procesní a technické záruky; macOS a Windows; šifrování uloženého stavu, bezpečné mazání na úrovni souborů, zálohy chráněné před přepsáním vlastníkem; automatické rozhodování, zda data smějí opustit zařízení.

## 10. Známé otevřené body

Flat seznam aktuálně platných mezer; každou lze ověřit v uvedeném souboru.

- [ ] **Schválení není kryptograficky svázáno s kódem, který se spustí.** `ApprovalManager` zaznamená `action`, `resource` a `context`, ale ne hash ani podpis payloadu (`security_plane/approval/__init__.py:98-147`); výslovně to přznává i `product/approvals.py:23-28`. Změna kódu mezi schválením a `provider.execute` není ničím zachycena. **Proti:** `ROADMAP_V5.md` §4.5.
- [x] **Writetelný workspace je omezen na sanitovaný kořen.** `workspace` ze step payloadu se resolvne a ověří proti `<data dir>/workspaces` *před* spuštěním procesu; cesta mimo kořen je odmítnuta `ValueError` (`execution_plane/bubblewrap_provider.py:120-127`, `product/local_store.py:274-301`). Read-write bind mount je jediný, který by mohl vrátit sandboxu celý host, takže je omezen. Kořen lze přesunout jen explicitně (`CYBERBRAIN_WORKSPACES_ROOT`, `bubblewrap_provider.py:90-95`); tichý návrat k permissivnímu chování neexistuje. Otestováno: `tests/test_cli_run_authorized.py:271-371`.
- [x] **`cyberbrain run` prochází `CoderAgentem` a `AuthorizedExecutionService`.** CLI směruje každý krok přes `PtahBridge.execute_step` → `InMemoryTaskDispatcher._authorize` (identity → scope → policy → approval) → `AuthorizedExecutionService` přes `CoderAgenta` → `BubblewrapExecutionProvider` (`cli.py:446-556`, `orchestration/ptah_bridge.py:331-504`, `extension_layer/registry_impl.py:141-241`, `execution_plane/secure_gateway.py:63-140`, `agents/orchestrator.py:187-277`). `PermissionGateway` i sandbox jsou v cestě zapojení. `run` navíc odmítne krok, jehož payload nedeklaruje `action`, a nedispatchuje krok, jehož `depends_on` neuspělo (`cli.py:498-520`); při jakémkoli neúspěchu skončí nenulovým exit kódem a řádek o úspěchu vypíše jen tehdy, když prošly všechny kroky (`cli.py:539-556`). Rozhodnutí D2, 2. října 2026.
- [x] **`cyberbrain run` už model nevolá (D3).** Volání `ModelGateway.chat(...)` je z exekuční cesty odebráno a `cli.py` nemá ani import `ModelGateway` (importy `cli.py:37-62`). Důvod: modelový turn nemá v policy systému žádnou `action` ani scope, takže ho nelze autorizovat přes `AuthorizedExecutionService`, a spuštění textu vygenerovaného modelem by znamenalo běh nedůvěryhodného vstupu v sandboxu. **Následkem je zlomová změna CLI:** odstraněny volby `--model` a `--lang`; `run` nabízí jen `--id`, `--data-dir`, `--db`, `--approval-timeout` (default 300.0) a `--scopes` (CSV, default prázdný) (`cli.py:446-456`). Důsledek pro `plan`: `PlannerAgent.execute` generuje kroky bez deklarované `action` (`agents/orchestrator.py:151-157`), takže je `run` odmítne, místo aby je spustil pod odvozeným defaultem. Viz §7.
- [x] **`--scopes` je bezpečnostní vlastnost, ne pohodlí.** Prázdný default **neuděluje nic**: kódový krok pak skončí uzavřený na scope bráně dřív, než se dostane na policy a schválení. Hodnoty se validují proti `PermissionScope` z security plane (`cli.py:90,201-227`), takže CLI nemůže nabídnout scope, které identitní vrstva nezná; neznámá hodnota je odmítnuta, ne tiše zahozena. Coder identita dostane jen to, co operátor výslovně vypíše (`cli.py:190`, `orchestration/ptah_bridge.py:277-283`). **Ověřeno experimentálně** na tomto hostu: běh s prázdnými scopes skončí `SecurityError: Agent ptah-coder denied for action 'execute_python'` (`extension_layer/registry_impl.py:177`) a exit kódem 1; s `--scopes python` se krok dostane až na schválení.
- [x] **Data-dir se resolveuje jednotně přes `product.local_store`.** Dříve `cli.py` počítal `_DATA_DIR` přímo a `CYBERBRAIN_DATA_DIR` ignoroval, takže `roadmaps.json` a SQLite databáze mohly skončit v různých adresářích. Teď obojí vychází ze stejného resolve (`cli.py:60,388,467-468`; `product/local_store.py:75,99-123,158-189`).
- [x] **CLI nyní konstruuje `PtahBridge` s databází.** `_build_bridge` (`cli.py:168-195`) otevírá `SQLiteDatabase` přes `open_database` (`product/local_store.py:158-189`) a předává ji do `PtahBridge(database=...)` (signatura `orchestration/ptah_bridge.py:140-150`). `ApprovalManager`, `AuditLog` i queue jsou persistentní; `PtahBridge.__init__` navíc vynucuje, že queue, `ApprovalManager` a databáze ukazují na stejný soubor (`orchestration/ptah_bridge.py:150-168`). `SecurityError("Approval unavailable ...")` už v podporované cestě nenastane — hází se jen tehdy, když je `ApprovalManager` `None` (`extension_layer/registry_impl.py:178-188`). Rozhodnutí D2, 2. října 2026.
- [x] **Operativní selhání schválení v `run` je timeout, ne chybějící manager.** Citlivá akce nejprve blokuje čekáním na rozhodnutí; po vypršení timeoutu (`min(approval_timeout, manager.timeout_seconds)`, CLI default 300 s proti knihovním 3600 s) se `check_expired()` zavolá, zbývající `PENDING` se zruší a požadavek se **zapíše do databáze jako `cancelled`** (`extension_layer/registry_impl.py:190-241`, `security_plane/approval/__init__.py:236-257`), takže zmizí z `approvals pending`. Rozhodnutí D2, 2. října 2026.
- [ ] **Text chyby při timeoutu schválení neodpovídá uloženému stavu.** V `_authorize` je `status` dopočten z objektu načteného **před** voláním `cancel()` (`extension_layer/registry_impl.py:229-232`), takže vyhozené `SecurityError` hlásí `Approval pending for action '...'`, zatímco řádek v `approval_requests` má `cancelled` — stejný vzor je i v knihovní cestě `AuthorizedExecutionService._wait_for_approval` (`execution_plane/secure_gateway.py:209-222`). Audit proto zapíše `APPROVAL_DENIED` / `deny` s `error="approval pending"` vedle skutečně zrušeného požadavku. Je to nepřesná hláška, **ne obcházení brány**: v obou případech je krok odmítnut a nic se nespustí. Opraveno nebylo.
- [x] **`approvals_app` je nyní registrován v CLI.** `cli.py:58,66` importuje `approvals_app` z `product/approvals.py:61` a registruje ho přes `app.add_typer(approvals_app, name="approvals")`. Příkazy `cyberbrain approvals pending | show | approve | deny | history` jsou z entry pointu `cyberbrain` nyní dosažitelné. Schválení zůstává **nekryptograficky svázáno** s kódem (viz výše). `--no-persist` existuje **jen na podpříkazech `approvals`**, nikoli na `run` ani `plan` (`product/approvals.py:242,292,443`): na `run`/`plan` je persistence **povinná**, protože schválení a audit musejí být čitelné z druhého terminálu, a proto na nich flag záměrně chybí (`cli.py:139-165`). Na `approvals` je `--no-persist` varování, že rozhodnutí nelze zapsat ani odemknout běh čekající v jiném procesu, a je **odmítnut**, pokud cílová databáze neexistuje (`product/approvals.py:126-145`).
- [x] **`cyberbrain doctor` existuje.** `cli.py:59,67` registruje `doctor_app` z `product/doctor.py:969` přes `app.add_typer(doctor_app, name="doctor")`; `cyberbrain doctor check` (alias `main`) provádí read-only diagnostiku `bwrap`, policy režimu, přítomnosti klíčů (nikdy hodnoty) a providerů. Doctor je **konfiguračním self-reportem**, ne ověřeným důkazem (viz `product/doctor.py:19-24`); `ModelGateway.health_check` stále není volán z CLI.
- [ ] **`AIFirewall` není zapojena do cesty spuštění ani do prompt egressu.** `AuthorizedExecutionService`, `PermissionGateway` a `PolicyEngine` ji nevolají; `QualityGateRunner` ji předává jen jako volitelný parametr.
- [ ] **Část členů `ThreatType` je nedosažitelná.** `MCP_POISONING`, `MALICIOUS_DOCUMENT`, `EXCESSIVE_AGENCY` a `AGENT_HIJACKING` se v kódu vytvářejí výhradně v metodách `_check_mcp_poisoning` (`firewall/__init__.py:529`), `_check_excessive_agency` (`:616`) a `_check_agent_hijacking` (`:638`), které žádná veřejná `inspect_*` metoda nevolá; `MALICIOUS_DOCUMENT` není konstruován nikde. **Přesně upřesněno:** metoda `_check_suspicious_agent_communication` (`:660`) je také mrtvá, ale samotná hodnota `SUSPICIOUS_AGENT_COMMUNICATION` je dosažitelná jinou cestou — z `_check_suspicious_destination` (`:607`) přes `inspect_external_egress`. Tvrzení „těchto pět nelze vzniknout“ by tedy nebylo přesné.
- [ ] **Quality gates samohodnotí** — `no_hardcoded_secrets` a `default_deny_policy` jsou vždy `PASS` bez inspekce; `ArchitectureGate` přebírá většinu vstupů jako parametry volajícího (`quality_gates/__init__.py:240-245,400-461`).
- [ ] **Prompt egress není evidován ani blokován pro model layeru** — viz §7. `cyberbrain run` a `cyberbrain plan` již model nepoužívají, takže přímo přes ně `neexistuje` egress. Model layer však stále obsahuje `ModelGatewayConfig` se `default_provider="litellm"` a tichým fallbackem jako knihovní výchozí; `PrivacyGateway.redact` není volán na egress ani `AIFirewall.inspect_external_egress`. Latentní riziko pro jakoukoli budoucí kódovou cestu, která model volá.
- [ ] **Akce bez scope mapy — deny-by-default je IN PROGRESS, ne hotovo.** `publish_content`, `send_external_message`, `credential_access` a `data_upload` chybí v `PermissionGateway.ACTION_SCOPE_MAP` (`permission/__init__.py:82-95`) a krok 1 je pro ně průchodný bez scope, protože podmínka zní `if scope_required and not agent.has_scope(...)` (`permission/__init__.py:148-149`): chybějící mapování se neodmítne, jen se přeskočí. Dokud to není opravené, je správné psát, že autorizace je deny-by-default **jen pro mapované akce**. Politika je deny-by-default nezávisle (`policy/__init__.py:203-245`), ale scope vrstva nikoli. **Probíhající oprava:** v pracovním stromu existuje `security_plane/actions.py` (kanonická slovníková akcí, `ACTION_SCOPE_REQUIREMENTS:64`, `SENSITIVE_ACTIONS:81`, `KNOWN_SAFE_ACTIONS:108`, `is_registered_action:118`), ale má **nula importérů** — `PermissionGateway` ani `PolicyEngine` ho zatím nevolají, takže oprava není v zapojené cestě.
- [ ] **Autorizace tasků z event busu — IN PROGRESS.** `TaskAuthorizer` protokol a `DispatcherAuthorizer` adaptér jsou v kódu a `BaseAgent` odmítne bus task bez injektovaného authorizeru nebo při jeho vyhození (`agents/orchestrator.py:64-140,195-260`) — bus už tedy není obcházka řetězce. **Nedokončeno:** `PtahBridge` žádný authorizer nepřiřazuje (`orchestration/ptah_bridge.py` neobsahuje `authorizer`), takže busem doručené `*_TASK_REQUEST` události jsou dnes odmítnuty, ne autorizovány. Samotné `AsyncEventBus.publish` (`control_plane/event_bus/__init__.py:85-105`) stále rozesílá všem subscriberům bez jakékoli kontroly oprávnění; oprava je na úrovni agentů, ne busu.
- [ ] **API skelet není fail-closed — a je mrtvý kód.** V `control_plane/api/__init__.py:175` se autentizace volá jen když je `route.requires_auth` **a** existuje auth provider; při chybějícím provideru požadavek projde neautentizovaný. Řešení v té době nebylo. Důležité: modul má **nula registrovaných routes a nula importérů**, takže dnes není vystaven žádný endpoint. Registrace tras je navíc na třídové úrovni (`_routes: dict = {}`, `control_plane/api/__init__.py:86`), tedy sdílená mezi instancemi.
- [ ] **Chybí cgroup / limit počtu procesů / kvóty disku a inodeů** — viz §4.
- [ ] **Oprávnění souborů jsou best-effort** — výjimky `chmod` se ignorují (`sqlite_store.py:139-144,260-264`).
- [ ] **`prune_expired` není bezpečné mazání** a neprohazuje s `provenance_records` (`sqlite_store.py:195-229`).
- [ ] **Regex pokrytí `AIFirewall` je neúplné** — pro část deklarovaných hrozeb v `ThreatType` neexistuje žádná dosažitelná detekce (viz výše).
- [x] **CI s Bubblewrap přítomným i chybějícím.** `no-bubblewrap-fail-closed` job v `.github/workflows/ci.yml` (build na `ubuntu-latest`) nejprve odinstaluje a smaže binárku `bwrap`, pak potvrzuje, že `command -v bwrap` selže, `bwrap --version` selže a `shutil.which("bwrap")` vrací `None`, a konečně spustí test suite s `pytest -q -rs` a explicitní kontrolu, že `BubblewrapExecutionProvider.execute` vyhodí `RuntimeError` s textem "refusing to run code without a sandbox" (`bubblewrap_provider.py:73-78`). Prokazuje fail-closed chování při chybějícím sandboxu. **Neprokazuje** ochranu hostitelského systému proti nepřátelskému kódu — namespace izolace a `RLIMIT_*` limity jsou kooperativní a neověřené v tomto jobu; samostatná bezpečnostní revize je vyžadována. Job navíc neprokazuje nic o `bwrap` přítomném: binary je v něm odstraněna. **Neověřeno zůstává, že tento job vůbec někdy proběhl** — GitHub-hosted runner zatím neodpálil, takže je jeho průchod známý jen ze zpracování souboru, ne ze zeleného buildu.
- [ ] **Verze `bwrap` není pinována ani ověřována** (`bubblewrap_binary: str = "bwrap"`).
- [x] **`ruff` prochází** — viz §11.

## 11. Jak ověřit tvrzení z tohoto dokumentu

```bash
PYTHONPATH=src python3 -m pytest -q
python3 -m ruff check .
PYTHONPATH=src python3 -m mypy --cache-dir=/tmp/cb26-mypy src/cyberbrain26
bwrap --version
# Bubblewrap chybějící: 360 passed, 9 skipped (viz §10) — hodnota z 2026-10-02, neopakována
```

Stav při sepsání (2026-10-02, tento host). Pracovní strom se mezitím měnil (necommitovaná práce paralelního agenta), takže počet testů je zaznamenaná hodnota, ne stabilní údaj. Bubblewrap-absent řádek měří `unshare -rm` s `/dev/null` bind-mounted přes `/usr/bin/bwrap`; 9 skipped jsou testy vyžadující skutečný sandbox runtime:

| Příkaz | Výsledek |
|---|---|
| `PYTHONPATH=src python3 -m pytest -q` | `413 passed` |
| `python3 -m ruff check .` | `All checks passed!` — 0 chyb |
| `PYTHONPATH=src python3 -m mypy …` | `Success: no issues found in 57 source files` |
| `bwrap --version` | `bubblewrap 0.9.0` |
| `python3 --version` | `Python 3.12.3` |
| `pytest -q -rs` s `bwrap` skrytým (`unshare -rm` + bind-mount `/dev/null` přes `/usr/bin/bwrap`) | `360 passed, 9 skipped` (9 skipped = testy vyžadující skutečný sandbox) |

**Před každým schválením znovu přečtěte citované zdroje**, protože pracovní strom se mění. Klíčové soubory: `execution_plane/secure_gateway.py` a `execution_plane/bubblewrap_provider.py` (pořadí kontrol a fail-closed); `security_plane/policy/__init__.py` (`SENSITIVE_ACTIONS`, `SAFE` pravidla, default deny); `security_plane/permission/__init__.py` (`ACTION_SCOPE_MAP`, pořadí `evaluate`); `security_plane/identity/__init__.py` (`TrustLevel`, `PermissionScope`); `security_plane/approval/__init__.py` (timeout a stavy); `security_plane/audit/__init__.py` a `data_plane/sqlite_memory.py` (`clear()`, `signature_verified: False`); `data_plane/sqlite_store.py` (tabulky, `prune_expired`, `0o700` / `0o600`, WAL); `product/local_store.py` (cesty, `ENV_*`); `product/approvals.py` (CLI schválení, fail-closed); `product/doctor.py` (read-only diagnostika); `control_plane/model_gateway/__init__.py` (knihovní `ModelGatewayConfig` defaulty — `run` již nepoužívá; viz §7); `cli.py` (fail-closed `run`, žádný import `ModelGateway`, seznam příkazů); `model_layer/litellm_client.py`, `ollama_client.py`, `llama_cpp_client.py` (remote vs. lokální); `security_plane/firewall/__init__.py` (nedosažitelné detektory); `security_plane/quality_gates/__init__.py` (samohodnotící kontroly); `orchestration/ptah_bridge.py` (SQLite-backed `PtahBridge`: `identity_scopes` `orchestration/ptah_bridge.py:277-283`, durable dispatch a `execution_attempted` `orchestration/ptah_bridge.py:363-504`); `control_plane/task_queue/__init__.py` (durable úlohy: `retry_failed` `control_plane/task_queue/__init__.py:335-354` je omezený na stav `FAILED` a **kontrolu `execution_attempted` vůbec nedělá**, zatímco `retry_blocked` `control_plane/task_queue/__init__.py:356-381` naopak otevírá výhradně záznam, jehož `execution_attempted` je `false`, a vše ostatní odmítne (`:381`); `agents/orchestrator.py` (pole `execution_attempted` v `AgentResult` `agents/orchestrator.py:35-52`, nastavované na `false`, když `CoderAgent` kód nespustil — `:200,210,232`); `extension_layer/registry_impl.py` (autorizační řetězec identity → scope → policy → approval, `:141-241`; zde se v `run` odehrává čekání na schválení); `.github/workflows/ci.yml` (`no-bubblewrap-fail-closed`, `:50-126`). Viz také `EXECUTION_SECURITY_REVIEW.md`.

---

*Odkaz na `AGENTS.md`: žádné tvrzení v tomto dokumentu nepopisuje Bubblewrap ani jinou prototypovou kontrolu jako ochranu proti libovolnému nepřátelskému kódu.*
