Zpět na blog
28. července 2026

Naše vlastní aplikace ukládala auth tokeny do localStorage. Co jsme s tím udělali.

Viktor Bulanek
Founder & CTO, Penetrify
MSc IT Security · 20+ years in security · 4x Ex-CTO

V červnu jsme Penetrify pustili na naši vlastní aplikaci, jako to děláme před větším releasem. Scan se vrátil s problémem v našem přihlašování přes Cognito, a když jsem za tu nitku zatáhl, vyšlo z toho něco, co jsem nečetl rád: dashboard Penetrify držel access a refresh tokeny z Cognita v localStorage, v plaintextu, čitelné jakýmkoli JavaScriptem na stránce.

Nesedli jsme si a takhle se nerozhodli. Je to default auth knihovny, kterou jsme použili, a nikdy jsme ho nezpochybnili. Právě to je na tom to zajímavé.

Co přesně byl ten nález

Náš dashboard je single-page aplikace. Po přihlášení dostala tokeny z AWS Cognita a SDK je uložilo do browser storage, protože tam je SDK ukládají, když v session není zapojený server. Přečíst je mohl jakýkoli skript s přístupem na stránku.

Znamená to, že jedna cross-site scripting chyba kdekoli v aplikaci, jedno škodlivé rozšíření prohlížeče nebo jedna kompromitovaná npm závislost, a útočník odchází s oběma tokeny. Access token ho dostane do účtu. Refresh token ho tam udrží, protože zůstává platný dlouho po tom, co session vypadá zavřená, dokud ho něco výslovně nezneplatní.

U našich uživatelů byl v dosahu fakturační údaj, výsledky scanů jejich aplikací a data o affiliate výplatách. Nakolik to umíme posoudit, nikdo to nezneužil. O to ale nejde. Bylo to dosažitelné a byl to náš vlastní default k opravě.

Proč šifrování localStorage není řešení

První instinkt je tokeny před uložením zašifrovat. Nepomůže to. Dešifrovací klíč musí žít ve stejném JavaScriptu, který útočník už spouští. Přidal jsi krok, ne hranici.

Druhý instinkt je přesunout tokeny do cookie na straně klienta. To taky nepomůže. Cookie nastavená JavaScriptem je JavaScriptem čitelná. Vlastnost, na které záleží, je HttpOnly, a nastavit ji umí jen server, hlavičkou Set-Cookie. Pokud tvoje oprava běží v prohlížeči, není to oprava.

Co jsme nasadili místo toho

Celou session jsme přesunuli do cookies nastavovaných serverem, vzorem token handler uvnitř backendu, který jsme už měli:

  • FastAPI backend provádí volání na Cognito a vrací session jako cookies s HttpOnly, Secure a SameSite. Prohlížeč token nikdy nevidí.
  • Auth middleware čte access token z cookie místo z hlavičky Authorization.
  • Protože se cookies posílají automaticky, mutující požadavky teď nesou CSRF token v hlavičce, který backend ověřuje.
  • Odhlášení provede global sign-out na straně Cognita, takže token, který někdy unikl, umře se session, místo aby ji přežil.
  • Auth SDK je z prohlížeče úplně venku.

Nepotřebovali jsme na to novou službu. Naše statická aplikace a naše API jsou už teď na stejném originu: CloudFront servíruje aplikaci a route /api/* posílá na API Gateway. First-party cookie se tak posílá s každým voláním API bez jakékoli další infrastruktury.

Nejdřív jsme se dívali na cestu doporučenou vendorem, což by znamenalo převzít hostované přihlašovací UI, a zamítli jsme ji. Zahodila by naše vlastní obrazovky pro login, MFA a reset hesla kvůli problému, který jsme umíme vyřešit v existujícím backendu.

Co to stálo

Asi týden práce a žádnou viditelnou změnu pro uživatele. To je upřímný souhrn většiny bezpečnostní práce a přesně proto se tyhle věci ve startupu odkládají do nekonečna. Na roadmapě se nic nepohne. Žádný zákazník o to neřekne. Změní se jen to, co s tvými uživateli dokáže udělat jediný injektovaný skript.

Jeden praktický detail, který nasazení zbezpečnil: během rolloutu backend pořád přijímal starou cestu přes hlavičku, takže revert frontend deploye byl rollback na jeden krok, kdyby se cokoli pokazilo.

Zkontroluj si vlastní aplikaci. Zabere to asi 30 sekund.

Otevři svoji aplikaci, přihlas se a otevři DevTools:

  • Záložka Application, Local Storage. Hledej dlouhé hodnoty se dvěma tečkami. Tenhle tvar je JWT.
  • Ve stejném panelu zkontroluj Session Storage. Stejný problém, kratší životnost.
  • V konzoli napiš document.cookie. Cokoli se tam objeví, je z definice čitelné JavaScriptem, takže session cookie, kterou v tom výpisu vidíš, tě nechrání.

Pokud token najdeš, je to nález. Ne teoretický, a ne takový, na který by tě upozornil zelený audit závislostí nebo čistý běh statické analýzy.

Proč to publikujeme

Dva důvody, a ten druhý je o našem vlastním produktu.

První: defaulty jsou rozhodnutí, která za tebe udělal někdo jiný. Přijdou s knihovnou, jsou pohodlné a skoro nikdy nejsou threat model. Ten náš byl rozumný default pro tutoriál a špatný pro aplikaci, která drží platební data.

Druhý: scan je ukazatel, ne verdikt. Ten náš označil přihlašovací flow a dokázal, že je dosažitelné. Nepodal nám větu „tvoje auth knihovna má default localStorage a právě to je potřeba změnit". Cesta od nálezu k opravě znamenala otevřít si vlastní auth cestu a přečíst ji. Přesně tak očekáváme, že budeš používat i naše reporty: nález ti řekne, kam se podívat, a ukáže request, který to dokazuje, a ty se pak rozhodneš, co je správná oprava ve tvojí architektuře.

Takže: pusť scan na vlastní aplikaci a pak si přečti kód, na který ukazuje. Udělali jsme obojí a ani jedna polovina by sama nestačila.

Pokud chceš pohled na svoji aplikaci zvenku hned teď, náš bezplatný security check zabere asi 60 sekund a nechce registraci. Pokud chceš plný report s důkazy a opravami, první scan je za $29 a započítá se do plánu. A jestli chceš radši tu věc zdarma, kterou jsme popsali výš, otevři DevTools a podívej se do svého local storage. Ta nestojí nic.

Frequently Asked Questions

Jaké typy zranitelností Penetrify detekuje?

Penetrify detekuje všechny kategorie zranitelností OWASP Top 10, včetně SQL injection, XSS, CSRF, IDOR, broken authentication, bezpečnostních misconfigurations a expozice citlivých dat. Testuje také bezpečnost API, správu relací a běžné misconfiguration v Supabase, Firebase a Bubble.

Jak dlouho trvá AI penetrační test?

Rychlý sken je dokončen za 15–30 minut. Standardní sken trvá 1–2 hodiny s širším pokrytím. Hloubkový sken může pro komplexní aplikace trvat několik hodin.

Co obsahuje zpráva Penetrify?

Každá zpráva obsahuje executive summary, celkové bezpečnostní skóre, nálezy klasifikované dle závažnosti (Kritická, Vysoká, Střední, Nízká), kroky pro reprodukci a konkrétní doporučení pro nápravu napsaná pro vývojáře – ne pro compliance manažery.

Related articles

DAST Alternativy v roce 2026: Když dynamické skenování nestačí (a co použít místo toho)
DAST skenery přehlížejí autentizační toky, obchodní logiku a moderní API. Zde je upřímné srovnání DAST, SAST, IAST, PTaaS a autonomního Penetration Testingu s umělou inteligencí – a kdy který z nich použít.
What an autonomous pentest agent found in 3,847 apps — and what your scanner didn't
A data breakdown of 47,291 exploitation-validated findings, with methodology and limitations. 91% of the SQL injection we found shipped despite a SAST gate in CI; 78% of critical findings needed no login.
Indirect Prompt Injection to Data Exfiltration: When the Model Has Tools
Prompt injection on its own is a curiosity. Prompt injection reaching a tool that holds real credentials is a breach — and the instruction does not have to come from your user. It can arrive inside the document your app was asked to summarise.

Explore more