# Návrh: `pentester` agent — status: ČEKÁ NA SCHVÁLENÍ

> **Stav dokumentu:** návrh. Nic v `src/` ani `tests/` nebylo změněno.
> **Datum:** 2026-10-03
> **Rozsah:** read-only výzkum (explore, security-auditor, threat-modeler) + Gate 1 baseline.

---

## 1. Proč to v dnešním stavu nefunguje

Tři funkční blokery, ověřené v kódu:

| # | Blokující | Důkaz |
|---|---|---|
| B1 | Sandbox nemá síť. `--unshare-all` sdílí všechny namespace včetně sítě; `nmap`/`curl` na cíl neprojdou. | `execution_plane/bubblewrap_provider.py:154` |
| B2 | Limity jsou pro sken příliš těsné: `RLIMIT_CPU` 10 s, wall-clock 30 s. `/16` SYN scan se nevejde. | `bubblewrap_provider.py:49-50, 135, 195-199` |
| B3 | Chybí resolver a CA store. Sandbox nemá `/etc/resolv.conf` ani `/etc/ssl`, takže nelze ani přeložit jméno, ani ověřit TLS. | `bubblewrap_provider.py:152-175` (binduje jen `/usr`, `/lib`, `/lib64`, `/dev`, `/proc`, `/tmp`, workspace) |

Tři architektonické mezery, které pentest potřebuje a dnešní kód nemá:

| # | Mezera | Důkaz |
|---|---|---|
| A1 | `ExecutionProvider` protokol nemá parametr identity ani role → síť nelze zapnout per-role. | `extension_layer/execution_provider.py:22-29`, `bubblewrap_provider.py:97`, call site `secure_gateway.py:143-148` |
| A2 | `--scopes` je v CLI hard-coded na roli `coder`. Jiná role si scope nevezme. | `cli.py:190` |
| A3 | Per-role model routing neexistuje a v `run` se model nevolí vůbec. | `cli.py:9-24`, `model_gateway/__init__.py:233-243`, `router.py:154-197` |

**Důsledek A3, označený jako zásadní:** pentest bude **první místo v repu, kde nedůvěryhodný obsah ze sítě vstoupí do kontextu modelu**. Dosud žádná taková cesta neexistuje, tedy ani žádná existující kontrola. `AIFirewall` je napsaný, ale nikde zapojený — jediné importy jsou re-export v `security_plane/__init__.py:13-21`; `QualityGateRunner` ho bere jen jako volitelný parametr a bez něj dá `WARN` (`quality_gates/__init__.py:199-213`). Navíc `HIGH` severity mapuje na `QUARANTINE`, ne `BLOCK` (`firewall/__init__.py:339-349`), takže by regex firewall defaultně vůbec neblokoval.

---

## 2. Co už v repu funguje a nepokazíme

| Vlastnost | Důkaz |
|---|---|
| Agent běží out-of-process → DB si otevřít nemůže, `AuditLog.clear()` z agenta volat nejde | `bubblewrap_provider.py:178` |
| Policy je 100 % v kódu, mimo `PolicyEngine` ho není | `policy/__init__.py:198-240` |
| Repozitář, `~/.ssh`, `~/.aws`, `.env`, celý `/home`, `/etc/shadow` **nejsou namontované** | `bubblewrap_provider.py:152-175` |
| `--clearenv` + `HOME=/tmp` → žádný API klíč se dovnitř nedostane | `bubblewrap_provider.py:169-171` |
| Workspace confina je na FS úrovni a kontroluje se **před** spuštěním procesu | `bubblewrap_provider.py:116-127`, `product/local_store.py:274-301` |
| `PermissionGateway.evaluate` je skutečně deny-by-default pro neznámé akce | `permission/__init__.py:190-208` |
| `AuthorizedExecutionService` nemá žádnou fail-open cestu | `secure_gateway.py:82-169` |
| Digest binding odmítne payload změněný po schválení | `actions.py:118-147`, test `test_execution_provider.py:445-485` |

---

## 3. Návrh změn

### Fáze 1 — rekon בלבד (doporučeno jako první diff)

Nástroje: `nmap`, `ffuf`, `curl`. **Žádné využívání zranitelností, žádné credentials.**

| # | Soubor | Změna | Rozsah |
|---|---|---|---|
| 1 | `execution_plane/bubblewrap_provider.py` | `__init__` nový parametr `allow_network: bool = False`; v `_run` (`:152-158`) podmíněně vyměnit `--unshare-all` za `--share-net` | cílená |
| 2 | `extension_layer/execution_provider.py` | `allow_network` do protokolu | cílená |
| 3 | `execution_plane/secure_gateway.py` | předat flag z identity (`:143-148`) | cílená |
| 4 | `security_plane/actions.py` | nová akce `scan_network` → scope `network`, v `SENSITIVE_ACTIONS` | cílená |
| 5 | `security_plane/identity/__init__.py` | `AgentIdentity.metadata` už existuje (`:80`) — stačí použít jako zdroj pravdy pro profil | žádná |
| 6 | `extension_layer/registry_impl.py` | password gate v `_authorize_step` (`:176-286`) pro roli `pentester` | cílená |
| 7 | `agents/orchestrator.py` | `class PentestAgent(BaseAgent)` podle vzoru `ResearchAgent` (`:611-633`) + 2 konstanty událostí (`:20-31`) | přírůstková |
| 8 | `orchestration/ptah_bridge.py` | import, konstrukce s `AgentIdentity(agent_id="ptah-pentester", role="pentester")`, zápis do `self._agents` (`:268-275`) | přírůstková |
| 9 | `cli.py` | nový command `pentest` s `getpass`, `--targets` povinné; oddělený scope parser (dnes hard-coded na `coder`, `:190`) | přírůstková |
| 10 | `agents/__init__.py` | export konstant a třídy | přírůstková |

**Co se záměrně NEMĚNÍ:** `PolicyMode`, `PolicyEngine`, výchozí `--unshare-all` pro ostatní role, `KNOWN_SAFE_ACTIONS`, `--scopes` u `run`.

### Nové komponenty (nelze vyjádřit v současném policy DSL)

| Komponenta | Proč musí být nová |
|---|---|
| `TargetScope` | Whitelist cílů **nelze napsat jako policy pravidlo**. `ALLOWED_CONDITION_CALLABLES = frozenset({"is_sensitive"})` (`policy/__init__.py:97`) — podmínka musí být bool, max 256 znaků. Test „je to v povolených cílech" je funkční volání, které DSL odmítne. Zkusit to by znamenalo obejít allowlist AST, což je přesně to, před čím chrání `evaluate_condition` (`:162-173`). `PolicyContext.resource` je volný řetězec a `_rule_matches` ho zahazuje — cílová adresa dnes není v autorizaci nikde. |
| `EngagementAuthorization` | Artefakt: vlastník, seznam cílů (CIDR), povolené třídy nástrojů, časové okno, intenzita, rules of engagement, **hash artefaktu**. Hash se použije jako `execution_payload_digest` (`actions.py:118-147`), což je existující a správný mechanismus. |
| `OwnerPrincipalAuth` | Dnes v repu **žádná hesla nejsou**. `ApprovalManager.approve()` bere `reviewer` jako volný řetězec (`approval/__init__.py:149-163`) a `approvals` CLI defaultuje na `getpass.getuser()` (`product/approvals.py:72-77`). Heslo musí jít do identity vrstvy jako principal, ne jako CLI flag. |

### Kde se whitelist kontroluje

Při **každém volání nástroje**, ne jednou na začátku relace. Existující grant žije v `ContextVar` s životností jednoho volání (`actions.py:207-236`) — to je správná životnost i pro cílovou kontrolu.

Nástroj nesmí dostat cíl z promptu. `nmap -sS <target>` se **rekonstruuje** z deklarovaného záměru `{tool, target, ports}` a autorizuje se rekonstruovaná forma. Jinak je whitelist přepiselný jedním krokem navíc.

---

## 4. Známé pasti — pojmenované, ne obejité

| Past | Důkaz | Opatření |
|---|---|---|
| `RED_TEAM_LAB` tiše ruší celou approval bránu (`default_allow` pro `*`) | `policy/__init__.py:230-240` | Pentest **nesmí** být nový `PolicyMode`. Režim musí být samostatná identita + profil, ne přepínač policy. Kombinace „necenzurovaný model + `RED_TEAM_LAB`" je nejnebezpečnější možná konfigurace systému. |
| Workspace root je přípustný jako workspace (`candidate == allowed`) | `product/local_store.py:271`, test `test_execution_provider.py:200-209` | Pro pentest použít one-shot podadresář, nikdy root. Jinak cross-task leak. |
| Chybí `RLIMIT_NPROC`, `--size` na `/tmp`, seccomp, `--disable-userns` | `bubblewrap_provider.py:220-244, 166` | Do limit profilu pentestu. |
| Digest nepočítá obsah workspace — schválen je řetězec, ne bajty | `actions.py:118-147` | Viz T3 v threat modelu: `sh payload.sh` projde oběma branami. |
| `_check_suspicious_destination` označí `127.0.0.1` za podezřelý egress, ale Ollama tam legitimně běží | `firewall/__init__.py:599-614` | Loopback Ollama musí být explicitně allowlistovaná, jinak falešný poplach. |
| Workspace přežívá běh | `bubblewrap_provider.py:167` | One-shot workspace, zničit po běhu. |
| Výstup se zkracuje na 1 MB — `nmap` řádky jsou obrovské, důkaz se ztratí | `bubblewrap_provider.py:53, 246-258` | `stdout_truncated` zvednout na `SECURITY_ALERT`, ne na tichou položku. |

---

## 5. Co zůstává nedokončené i po fázi 1

Omezení, která **nejsou** chybou implementace, ale vlastností návrhu a je nutné vlastníkovi říct:

- **Audit není kryptořetězený.** 0 výskytů `entry_hash`/`chain_hash`/`prev_hash` v `src/` i `tests/`. `AuditLog` je append-only konvence, `clear()` je přístupný i v produkci (`audit/__init__.py:264`). `product/approvals.py:24-28` to přiznává. „Stopa pentestu" tedy není nedotknutelná.
- **Pentest nepodporuje klíčovou autentizaci.** Host klíč (`~/.ssh/id_*`) v sandboxu není a být nemusí — to je správně, ale znamená to, že klíčové přihlášení na cíl nefunguje.
- **`/usr` je čitelný celý** → pentest si může vybrat reveal-ované nástroje. Nízké, ale vědomé zřejmení.
- **Model je sekundární zdroj pravdy.** Shrnutí od dolphinu je neověřitelné a editovatelné; surový `stdout` je oříznutý. Obojí nesmí splývat v jeden „report".

---

## 6. Gate 1 baseline (2026-10-03)

```
PYTEST: 555 passed / 1 failed (556)
  FAIL: tests/test_kilo_agent_policy.py::test_every_agent_is_classified_for_cyberbrain26
        → 8 Kilo agentů nemá cyberbrain26_* roli
        → PRE-EXISTUJÍCÍ, v untracked souboru, ne regrese
  OLLAMA TEST: PASS (test_model_layer.py::TestOllamaClient::test_is_available)
RUFF: 2 chyby — obě v témže pre-existujícím untracked souboru; src/ čisté
MYPY: ok, 58 souborů
AUDIT_HASH: N/A — hash chain v repu neexistuje
SEMGREP: unavailable (chybí binárka i .semgrep.yml)
GIT: DIRTY — 28 modified + 24 untracked, HEAD 90a42e6, stash prázdný
ENV: python3 3.12.3, pytest 9.1.1, xdist chybí, pytest-cov chybí
```

**Regresní plocha pro fázi 1** (testy, které musejí zůstat zelené):
`test_execution_provider.py:64-74` (pin na `['lo']` — běžný provider se nesmí změnit),
`test_security_permission.py:375-430` (invarianty slovníku akcí),
`test_security_policy.py:315-613` (AST sandbox podmínek),
`test_task_dispatcher.py`, `test_ptah_bridge.py:58-66` (pinned hláška `"unknown role"`),
`test_cli_run_authorized.py:145-165` (help surface — `--model`/`--lang` tam být nesmějí),
`test_security_e2e.py`, `test_product_cli.py`.

---

## 7. Otevřené otázky pro vlastníka (před implementací)

1. **Třída nástrojů:** jen rekon (nmap/ffuf/curl), nebo i exploitační třída (sqlmap --dump, hydra, msfconsole)?
2. **Dosah sítě:** jen explicitní whitelist, jen lab prostředí, nebo interaktivní dotaz na každý cíl?
3. **Heslová brána:** v `registry_impl._authorize_step` (jediné místo, kde se neobejde), nebo jen v CLI?
4. **Autorizace k cíli:** musí být engagement artefakt s hashem zaznamenaný v `approval_requests` + `approval_records`?
5. **Model:** `dolphin-mistral` přes `OllamaClient` přímo v pentest command (obchází `ModelGateway`), nebo přes `ModelGateway` s `prefer_local=True`? Obě možnosti mají rozdílnou auditovatelnost.
6. **Hash audit chainu:** zavést jako součást této práce, nebo zapsat jako omezení a nechat na další fázi?
