← ZPĚT NA CENÍK · HYDRA   ·  JAK TALOS FUNGUJE →   ·  ENGLISH VERSION →
TIA · TALOS · REDIGOVANÁ UKÁZKA

Bezpečnostní audit kódu — redigovaná ukázka

Co to je. Očištěný výřez z interního auditu vlastního kódu — vlastní kód jsme prohnali svojí útočně vedenou metodou („dogfooding"). Identifikátory cíle jsou redigované; metoda, nálezy, závažnosti a trasy dat jsou doslovné. Je to reprezentativní ukázka klientské dodávky, ne marketingová maketa.

Původ — čtěte nejdřív tohle
Tohle je snímek jednoho běhu ze dne 2026-07-19. Zveřejňujeme ho jako ukázku dodávky — jak vypadá zpráva z TALOSu, celá, včetně částí, které většina dodavatelů vynechá. Není to tvrzení o současném stavu jakéhokoli systému, našeho ani cizího. Cílem byl náš vlastní kód: metodu jsme pustili na sebe dřív, než jsme ji komukoli prodali.

Počty se dají zkontrolovat proti tabulkám níže: 13 sledovaných zvenku řízených vstupů → 10 kandidátů → falsifikace zabila 2 → 8 potvrzených (1 Medium, 7 Low). Když se některé číslo neshoduje s tabulkou, platí tabulka — napište nám a opravíme to.
Cíl interní API služba na loopbacku (název redigován) — lokální asynchronní brána na úkoly
Režim JEN PRO ČTENÍ — testy zneužití byly napsané, ale nespuštěné; každý nález nese statickou trasu vstup→místo užití s konkrétními payloady. Žádný zdrojový soubor nebyl změněn.
Metoda Recon → Paralelní lov → Adverzní falsifikace → Filtr proveditelnosti → nezávislé ověření jen pro čtení (kdo hodnotí ≠ kdo našel)
Nálezy 0 Critical, 0 High, 1 Medium, 7 Low — všechny Confirmed (další 2 kandidáti zabiti při falsifikaci)

Proč „útok napřed + falsifikace" (metoda)

Většina skenerů optimalizuje na úplnost — vynesou všechno, co se shoduje se vzorem slabiny, a třídění nechají na vás. My optimalizujeme na přesnost pod protivníkem: každý kandidát musí přežít krok, jehož jediným úkolem je vyvrátit ho.

Recon ─▶ Paralelní lov ─▶ Adverzní falsifikace ─▶ Filtr proveditelnosti ─▶ Nezávislé ověření
 mapuj    mnoho hypotéz     „dokaž, že to NENÍ      „dosáhne na to           jen pro čtení,
 vstupy   současně           skutečné" (výchozí      skutečný aktér?"        jiný kontext
 a místa                     stav: vyvráceno)                                (hodnotí ≠ našel)
 užití

Při tomhle běhu falsifikační krok zabil 2 z 10 kandidátů jako plané poplachy (viz Zabití kandidáti níže). Zpráva, ve které je jen ověřené, má větší cenu než zpráva dlouhá: rozpočet na opravy padne na skutečné vady, ne na jejich třídění.


Model důvěry (tohle je to, co stropuje závažnost)

Služba naslouchá jen na 127.0.0.1 (gateway.py:475) a každou cestu kromě /health ověřuje jedním sdíleným instalačním tokenem X-Api-Token (secrets.token_urlsafe(24), porovnání v konstantním čase :273). Existuje přesně jeden principál — kterýkoli lokální proces, který ten token drží — takže žádná hranice mezi nájemníky neexistuje. To stropuje každý nález: „číst/zapsat libovolný úkol" je ověřený základní stav a dosah je jeden loopbackový počítač. Nejcennější nález (F-001) je nezávislý na principálovi (podvržení autorství + destrukce), proto Medium; všechno ostatní je Low. Poctivé vymezení poráží nafukování závažnosti.


Souhrn

ID Nález CWE Severity Status
F-001 Podvržení odpovědi + vynucené smazání libovolného čekajícího úkolu CWE-290 / CWE-639 Medium Confirmed
F-002 next_id() souběh TOCTOU → duplicitní id / přepis kontextu CWE-367 / CWE-362 Low Confirmed
F-003 Neomezené čtení těla požadavku (/submit, /upload) CWE-400 Low Confirmed
F-004 Neomezené dekódování base64 přílohy + zápis na disk (/upload) CWE-400 Low Confirmed
F-005 Neomezené čtení těla požadavku (/answer) CWE-400 Low Confirmed
F-006 Sdílený rozpočet globálního omezovače → odepření služby CWE-770 / CWE-307 Low Confirmed
F-007 Neověřený /health spouští úklid souborů CWE-306 Low Confirmed
F-008 Ověřovací token v čitelném souboru a na výstupu; chmod je na Windows bez účinku CWE-312 Low Confirmed

Ke každému nálezu byl dodán statický důkaz a test zneužití (napsaný, nespuštěný).

Sloupce Severity a Status zůstávají v angličtině schválně — jsou to kanonické tokeny, ze kterých se počítá pečeť na konci dokumentu. Kdyby se přeložily, česká a anglická verze by daly jiný otisk a recept by nešel splnit.


Nálezy

F-001 — Podvržení odpovědi + vynucené smazání libovolného čekajícího úkolu

Pole Hodnota
CWE CWE-290 (obejití ověření podvržením) / CWE-639 (obejití autorizace přes klíč řízený uživatelem)
Severity Medium
Vstupní bod POST /answer
Místo gateway.py:448-451 (zápis); čtení při dotazování :302-308; smazání při úklidu :186-191
Trasa dat útočník nastaví reply_to na libovolné id úkolu → obsluha připojí {author:"worker", …} bez kontroly, že úkol existuje a je otevřený (:448-451) → (a) dotazující se rozhraní dostane podvrženou odpověď jako skutečný výstup pracovníka (:304-307); (b) úklid po „zodpovězení" smaže přechodný soubor s plným kontextem cílového úkolu dřív, než ho skutečný pracovník přečte (:186-191).
Oprava V obsluze odpovědi ověřit, že cílový úkol existuje a je otevřený, teprve pak připojovat; smazání po zodpovězení navázat na ověřenou odpověď; při čtení vynucovat původ odpovědi.
Kořenová příčina Klíč reply_to řízený uživatelem není navázaný na existenci a otevřený stav; pole autora je napevno.
Proč Medium (a ne Low) Nezávislé na principálovi: podvrhnout a zničit rozpracované úkoly je nad rámec ověřeného základního stavu „posílat odpovědi".

Oprava — nasazena a přeměřena týž den (identifikátory redigované, logika doslovná):

  # obsluha POST /answer
- # kdokoli mohl odpovědět na JAKÉKOLI id — existence nekontrolovaná, autor napevno
- append_reply(author="worker", reply_to=req["reply_to"], text=req["text"])
+ # odpověď musí odpovídat skutečnému, stále OTEVŘENÉMU požadavku — vše ostatní se odmítá
+ if read_request(reply_to) is None:      # takový otevřený požadavek není -> 404
+     return 404
+ if already_answered(reply_to):          # dvakrát odpovědět nelze     -> 409
+     return 409
+ append_reply(author="worker", reply_to=reply_to, text=text)

Přeměření po opravě: cesta podvržení teď vrací 404 a nepřipojí nic; čekající požadavek přežije úklidový průchod (útočník ho už nemůže vynuceně převést do množiny „zodpovězených"). Obě větve — podvržení autorství i vynucené smazání — se zavírají u téže pojistky. Doba od potvrzení k ověřené opravě: týž den.

F-002 — next_id() souběh TOCTOU

Pole Hodnota
CWE CWE-367 (TOCTOU) / CWE-362 (souběh)
Severity Low
Vstupní bod POST /submit, POST /upload (souběžnost)
Místo gateway.py:136-138 (next_id), volající :336, :390
Trasa dat next_id() odvozuje id z len(read_events()) (:137) → pod vláknovým HTTP serverem (:475) přečtou dva souběžné požadavky stejný počet dřív, než kterýkoli z nich připojí → oba přidělí totéž id → duplicitní záznam a přepsaný přechodný kontext jednoho z úkolů. Zámek omezovače hlídá jen počitadlo, ne přidělování id.
Oprava Serializovat next_id()→připojení→zápis pod jedním vyhrazeným zámkem, nebo odvozovat id z monotónního počitadla či UUID místo z délky záznamu.

F-003 — Neomezené čtení těla požadavku (/submit, /upload)

Pole Hodnota
CWE CWE-400 (neřízená spotřeba zdrojů)
Severity Low
Vstupní bod POST /submit, POST /upload
Místo gateway.py:323, :376
Trasa dat útočník nastaví velké Content-Lengthrfile.read(n) bez stropu MAX_BODY → jediná neomezená alokace. Omezovač stropuje počet požadavků, ne jejich velikost.
Oprava Zavést MAX_BODY na úrovni modulu; požadavky nad limit, bez hlavičky nebo se zápornou hodnotou odmítat kódem 413/400 v jedné sdílené pomocné funkci _read_json_body(), kterou použije každá POST cesta.

F-004 — Neomezené dekódování base64 přílohy + zápis na disk (/upload)

Pole Hodnota
CWE CWE-400
Severity Low
Vstupní bod POST /upload
Místo gateway.py:392-403
Trasa dat útočník pošle velkou base64 přílohu → b64decode (:394) → open(path,"wb").write(raw) (:402-403) bez kontroly délky → na disk se zapíšou bajty řízené útočníkem; úklid podle TTL je pomalejší než rychlost zápisu.
Oprava Vynutit MAX_ATTACHMENT_BYTES na dekódované délce ještě před zápisem na disk; nadměrné odmítnout.

F-005 — Neomezené čtení těla požadavku (/answer)

Pole Hodnota
CWE CWE-400
Severity Low
Vstupní bod POST /answer
Místo gateway.py:445
Trasa dat tentýž vzorec jako F-003 u třetí obsluhy: int(Content-Length)rfile.read(n) bez stropu.
Oprava Tentýž sdílený strop _read_json_body() jako u F-003 — opraví všechny tři cesty u kořene.

F-006 — Sdílený rozpočet globálního omezovače → odepření služby

Pole Hodnota
CWE CWE-770 (alokace bez limitu) / CWE-307 (nedostatečné omezení opakovaných pokusů)
Severity Low
Vstupní bod všechny cesty (brána běží před ověřením)
Místo gateway.py:261-269, kontrola :289, :312
Trasa dat omezovač drží jeden globální seznam zásahů bez klíče na volajícího a kontroluje se před ověřením → neověřený lokální volající, který vyčerpá okno, vynutí 429 pro oprávněného operátora (sdílený rozpočet).
Oprava Klíčovat omezovač podle volajícího (adresa klienta a/nebo token) s odděleným oknem pro každý klíč.

F-007 — Neověřený /health spouští úklid souborů

Pole Hodnota
CWE CWE-306 (chybějící ověření u kritické funkce)
Severity Low
Vstupní bod GET /health
Místo gateway.py:290-292 → úklid :161-192
Trasa dat větev /health spouští úklid souborů před kontrolou ověření (:293) → kterýkoli lokální neověřený volající vyvolá smazání přechodných souborů, které už jsou k úklidu způsobilé (zodpovězené / prošlé TTL). Jen příležitostné, ale skutečné — a řetězí se s F-001 (neověřené smazání úkolů označených útočníkem).
Oprava Udělat z /health čisté čtení — vypustit volání úklidu; spolehnout se na ověřený průchod a na úklid při startu.

F-008 — Ověřovací token v čitelném souboru a na výstupu

Pole Hodnota
CWE CWE-312 (ukládání citlivé informace v čitelné podobě)
Severity Low
Místo gateway.py:66-70 (soubor), :468 (výstup)
Trasa dat token_urlsafe(24) se zapisuje v čitelné podobě do .api-token; os.chmod(...,0o600) je na Windows bez účinku (:70) → soubor může zůstat čitelný pro všechny; celý token se navíc vypisuje na standardní výstup (:468) → lokální uživatel, který není operátor, si přečte soubor nebo konzoli a získá celý token (a ten odemyká F-001/006/007).
Oprava Šifrovat v klidu (klíčenka OS / DPAPI); nastavit skutečné ACL pro Windows místo spoléhání na POSIXový chmod; výpis na konzoli maskovat (otisk / poslední čtyři znaky).

Vyřešená inventura vstupů (doklad o úplnosti)

Seznam nálezů je důvěryhodný jen tehdy, když doloží i to, co bylo prověřeno a čisté. Každý zvenku řízený vstup má přiřazenou dispozici (názvy polí redigované):

Vstup Dispozice
hlavička Origin BEZPEČNÉ — řídí jen odraz CORS, ne přístup; zvláštnost s prefixem nejde využít (token je pořád vyžadován).
X-Api-Token BEZPEČNÉ (ověření) — porovnání v konstantním čase. Uložení tokenu je CWE-312 → F-008.
Content-Length KANDIDÁTF-003 (/submit,/upload), F-005 (/answer).
hlavní payload BEZPEČNÉ — před uložením redigováno a zbaveno tajemství; žádné místo užití pro injektáž.
objekt metadata (různá textová pole) BEZPEČNÉ — zabalený/redigovaný kontext; žádné místo užití.
pole url pro klasifikaci BEZPEČNÉ — slouží jen ke klasifikaci (povolení jako výchozí stav je zaznamenáno jako kód se zápachem → viz obrana do hloubky).
přepínač option BEZPEČNÉ — není místem užití.
base64 attachment KANDIDÁTF-004.
klíč reply_to KANDIDÁTF-001.
tělo odpovědi text BEZPEČNÉ — zneužití jde přes navázání reply_to, ne přes obsah textu.
id v cestě {id} BEZPEČNÉ — čistička re.sub(r"[^a-z0-9\-]","",id) blokuje průchod adresářem; IDOR mezi principály není vyjádřitelný (jeden sdílený token).
čtení záznamů druhého řádu BEZPEČNÉ — vlastní zápisy; přetržený řádek se při čtení sám ošetří.
přepínače příkazové řádky ZÁMĚR NÁVRHU — jen lokální operátor; není to místo užití dat.

Všechny vstupy vyřešeny. Nálezy, které nejdou navázat na jediný vstup (souběžnost, globální omezovač, /health bez vstupu), jsou zachyceny jako F-002 / F-006 / F-007.


Nenalezeno / vyloučeno (prověřené a čisté třídy)


Ověření záběru (chytili jsme každý výskyt každé kořenové příčiny?)

Kořenová příčina Nalezeno výskytů Ošetřeno (=nález) Zbývá
Neomezené rfile.read(Content-Length) 3 3 (F-003 ×2, F-005) 0
Neomezené b64decode → na disk 1 1 (F-004) 0
next_id() TOCTOU bez zámku 3 3 (F-002) 0
Rozpočet globálního omezovače 1 1 (F-006) 0
Cesta měnící stav před ověřením 6 cest 5 ověřených + 1 (F-007) 0
Tajemství v klidu / na výstupu 3 1 (F-008) + 1 vyloučeno (jen vývoj) 0
Místa pro injektáž 0 0 0
Průchod adresářem 2 2 (ošetřeno čističkou) 0

Každá kořenová příčina se mapuje 1:1 na nálezy — žádný osiřelý výskyt.


Kvalita kódu / obrana do hloubky (dnes nezneužitelné, ale stojí za zpevnění)

Vzorec Proč to dnes není nález Riziko, když se podmínky změní Doporučení
Klasifikátor obsahu používá blokační seznam s povolením jako výchozím stavem nedosáhne na žádné místo užití řízené útočníkem — je to mezera v úplnosti politiky, ne injektáž citlivý web, jehož URL neobsahuje uvedené klíčové slovo, pošle své tělo ven přejít na seznam povolených / klasifikaci podle obsahu; ukotvovat jednotně
Kontrola Origin přes shodu prefixu startswith(...) jen odráží hlavičku CORS; přístup pořád hlídá token kdyby se odraz někdy použil jako rozhodnutí o přístupu, podobný původ by prošel používat přesnou shodu původu / rozebrat hostitele
Externí modul redakce selhává povolením (při chybě vrátí text beze změny) mimo záběr skenu; není řízen útočníkem když modul chybí nebo je rozbitý, redakce tiše degraduje na průchod bez zásahu selhávat zákazem (zadržet), když se modul nepodaří načíst; degradaci zapsat do logu

Zabití kandidáti (co jsme sami vyvrátili)

Dvě hypotézy došly do adverzního kroku a byly vyvráceny — uvádíme je schválně, protože co skener zahodí je stejně vypovídající jako to, co si nechá:


Poctivé shrnutí jednou větou

Těsně vymezená lokální služba s jedinou skutečnou vadou návrhu (F-001) a hroznem běžných mezer ve zpevnění — 1 Medium, 7 Low, nulové nafukování a dva plané poplachy chycené dřív, než se dostaly do zprávy. Takhle vypadá ověřená dodávka.

Redigovaná ukázka · útočně vedený rozbor · režim jen pro čtení · kdo hodnotí ≠ kdo psal.


Náprava — co se stalo po téhle zprávě

Zpráva výše je snímek běhu z 2026-07-19 a nepřepisuje se. Tohle je dodatek: co se změnilo, kdy, a jak se ta změna přeměřila. Ověřoval ji průchod psaný proti každé opravě — kdo hodnotí ≠ kdo psal platí i na opravu.

IDZavřenoCo se změniloJak se to přeměřilo
F-0012026-07-19Klíč odpovědi navázán na existující, stále otevřený požadavek; dvojí odpověď se odmítá.Přeměřeno týž den: cesta podvržení vrací 404 a nepřipojí nic; čekající požadavek přežije úklidový průchod.
F-0022026-08-28Přidělování id serializováno pod zámkem s procesní značkou nejvyššího dosaženého čísla.40 souběžných přidělení dalo 40 různých id.
F-0032026-08-28Jedna sdílená čtečka těla se stropem pro všechny POST cesty — chybějící, záporné i nadměrné Content-Length se odmítá ještě před alokací.Deklarovaných 99 MB → 413; záporná hodnota → 400; chybějící hlavička → 400.
F-0042026-08-28Strop vynucen na DEKÓDOVANÉ délce přílohy, ještě před zápisem na disk.Nadměrný obrázek → 413 a na disk se nezapsal žádný soubor.
F-0052026-08-28Táž sdílená čtečka — oprava šla ke kořeni, ne třikrát do tří obsluh.Deklarovaných 99 MB na třetí cestě → 413.
F-0062026-08-28Omezovač klíčovaný podle třídy volajícího místo jednoho globálního seznamu.Neověřené zaplavení už nedusí ověřeného volajícího (200); po návratu k jedinému globálnímu kbelíku se 429 znovu objeví.
F-0072026-08-28Cesta na kontrolu života je čisté čtení; úklid běží při startu a jen na ověřené cestě.Přechodný soubor způsobilý k úklidu přežije neověřené volání a výslovný úklid ho pořád smaže.
F-0082026-08-28Přihlašovací údaj v konzoli maskován (vypsat ho jde jen na vyžádání); místo POSIXového volání, které na Windows nic nedělá, se nastavuje skutečné ACL.Kontrola zdroje plus volání ACL na souboru s tokenem.
Část, kterou většina zpráv vynechá
Než si přečtete opravy, přečtěte si data. Jediné Medium se zavřelo týž den. Sedm Low leželo 40 dní a zavřelo se naráz. Na službě na loopbacku s jediným principálem je to obhajitelné pořadí — ale je to skutečné zpoždění a tohle je dokument, který ho zaznamenává, ne místo, kde se ztratí. Nahlášeno není opraveno. Účetní kniha oprav má cenu jen tehdy, když unese i to čekání. Každou opravu výše přeměřil průchod psaný proti ní, ne ten, který ji napsal.
HYDRA · pečeť nad nálezy a nápravou · 8 × F · run 2026-07-19
b2d5597ac24a8d2050506c2904c692c9b48c61132d88d7e09aa02bac22925a61

Jeden řádek na každý nález z tabulky SouhrnID|CWE|Severity|Status, přesně jak je vytištěno. Jeden řádek na každý řádek tabulky NápravaID|fixed|datum. Jeden řádek run|2026-07-19. Všechny řádky seřadit abecedně dohromady, spojit znakem nového řádku (bez koncového), SHA-256. Všechny vstupy jsou vytištěné na této stránce.

Nahrazuje 414329d8ff0b6e68a173a6d4d1383fba… — dřívější pečeť jen nad nálezovou sadou, než dodatek o nápravě existoval. Dokument, který se změnil, to má říct řetězem, ne potichu.

Co tahle pečeť NEDOKAZUJE. Nedosvědčuje prózu, opravy ani to, kdy stránka vyšla, a není časovou autoritou. Zamyká nálezovou sadu — identifikátory, CWE, závažnosti a dispozice — aby pozdější verze téhle stránky nemohla potichu nález přidat, ztratit nebo mu snížit závažnost, aniž se změní otisk. To je celé tvrzení. Otisk je shodný s anglickou verzí — pečetí se nálezová sada, ne text.