Navigácia
Kyberbezpečnosť

Zradné CDN a npm: ako web ochrániť pomocou SRI a CSP cez Cloudflare Worker

31 októbra, 2026 techy.sk

Načítať knižnicu cez <script src="…/latest/…"> je pohodlné. Znamená to však, že obsah tvojej stránky závisí od toho, čo sa v danej chvíli nachádza na cudzom serveri. Pri útokoch na dodávateľský reťazec, kde útočník ovládne účet správcu balíka, sa môže zmeniť bez toho, aby si čokoľvek upravil. Tento návod ukazuje dve vrstvy obrany pre jednoduché weby: Subresource Integrity (SRI) a Content Security Policy (CSP) pridanú cez Cloudflare Worker.

Čo je overené a čo nie

Zadanie opisovalo konkrétny útok z konca októbra 2026 na knižnice pre animácie a komponenty. Ten som v dostupných správach nenašiel. Rok 2026 bol však bohatý na útoky na npm cez ukradnuté účty správcov a tie sú dobre zdokumentované. Väčšina známych prípadov bola zameraná na vývojárske stroje a CI systémy a zneužívala inštalačné skripty (preinstall, postinstall) na krádež tajomstiev. Škodlivý kód v prehliadačoch návštevníkov je iné, hoci tiež reálne riziko. Konkrétne tvrdenia o obchádzaní blokátorov cez maskované subdomény som nepreberal, lebo som ich nepotvrdil.

Presvedčivým príkladom, že riziko sú aj CDN, je prípad zo začiatku leta 2024. Doména populárnej polyfill služby po zmene vlastníka začala servírovať škodlivý kód tisícom webov, ktoré ju načítavali priamo. Útok na prehliadač cez CDN je teda reálny a nepotrebuje vlastný balík v registri.

Prečo je latest problém

  • Odkaz bez pripnutej verzie ukazuje na obsah, ktorý sa môže zmeniť bez upozornenia.
  • SRI sa s takýmto odkazom nedá použiť. Hash musí presne zodpovedať súboru, takže odkaz musí smerovať na konkrétnu, nemennú verziu.
  • Súbor v repozitári a balík publikovaný v registri nemusia byť identické. Balík sa dá publikovať aj z lokálneho stroja so zmenami, ktoré v repozitári nikdy neboli.

Vrstva 1: Subresource Integrity

SRI pridá k značke skriptu hash očakávaného obsahu. Prehliadač súbor stiahne, porovná ho a pri nezhode ho nespustí.

<script
  src="https://cdn.example.com/[email protected]/dist/kniznica.min.js"
  integrity="sha384-VYPOCITANY_HASH"
  crossorigin="anonymous"></script>

Hash vypočítaš zo súboru, ktorý si skutočne overil:

#!/usr/bin/env bash
# Použitie: ./sri.sh URL_NA_PRIPNUTU_VERZIU
set -euo pipefail
url="${1:?zadaj URL}"
case "$url" in
  *latest*|*@latest*) echo "Odmietam nepripnutú verziu." >&2; exit 1 ;;
esac
hash="$(curl -fsSL "$url" | openssl dgst -sha384 -binary | openssl base64 -A)"
echo "integrity="sha384-${hash}""

Hash radšej porovnaj aj so súborom z balíka stiahnutého priamo z registra alebo z druhého CDN. Hash z toho istého zdroja, ktorý by mohol byť napadnutý, ťa nechráni pred jeho zmenou v čase prvého stiahnutia.

Limity SRI: nechráni inline skripty a nefunguje pre kód načítaný dynamickým import() alebo skriptom, ktorý sám pridá ďalší skript. Pri dôležitých projektoch je najbezpečnejšie knižnicu uložiť do vlastného repozitára a servírovať z vlastnej domény.

Vrstva 2: CSP cez Cloudflare Worker

CSP určuje, čo smie stránka načítať a kam smie posielať dáta. Pre jednosúborové weby s inline skriptom sa nedá spoľahnúť na 'unsafe-inline', lebo by to celú ochranu zrušilo. Worker preto pri každej odpovedi vygeneruje náhodný nonce a pridá ho ku všetkým značkám script a style v HTML, ktoré pochádza z tvojho originu. Skripty bez noncu sa nespustia. Nemusíš pritom povoliť celý CDN hostiteľ, takže útočník nemôže načítať inú knižnicu z rovnakého CDN.

export default {
  async fetch(request, env, ctx) {
    const response = await fetch(request);
    const type = response.headers.get("Content-Type") || "";

    const headers = new Headers(response.headers);
    headers.set("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
    headers.set("X-Content-Type-Options", "nosniff");
    headers.set("Referrer-Policy", "strict-origin-when-cross-origin");
    headers.set("Permissions-Policy", "camera=(), microphone=(), geolocation=()");
    headers.set("X-Frame-Options", "DENY");

    if (!type.includes("text/html")) {
      return new Response(response.body, {
        status: response.status,
        statusText: response.statusText,
        headers,
      });
    }

    const bytes = crypto.getRandomValues(new Uint8Array(16));
    const nonce = btoa(String.fromCharCode(...bytes));

    const csp = [
      "default-src 'none'",
      `script-src 'nonce-${nonce}'`,
      `style-src 'self' 'nonce-${nonce}'`,
      "img-src 'self' data:",
      "font-src 'self'",
      "connect-src 'self'",
      "base-uri 'none'",
      "form-action 'self'",
      "frame-ancestors 'none'",
      "object-src 'none'",
      "upgrade-insecure-requests",
    ].join("; ");

    const headerName = env.CSP_REPORT_ONLY === "1"
      ? "Content-Security-Policy-Report-Only"
      : "Content-Security-Policy";
    headers.set(headerName, csp);

    const rewritten = new HTMLRewriter()
      .on("script", { element(el) { el.setAttribute("nonce", nonce); } })
      .on("style", { element(el) { el.setAttribute("nonce", nonce); } })
      .transform(response);

    return new Response(rewritten.body, {
      status: response.status,
      statusText: response.statusText,
      headers,
    });
  },
};

Ako to funguje: connect-src 'self' zablokuje fetch, XHR, WebSocket a beacon na cudzie domény. Aj keby škodlivý kód v načítanej knižnici bežal, dáta by nemal kam poslať. Subdomény nie sú 'self', takže odoslanie na maskovanú subdoménu sa tiež zablokuje.

Ako to nasadiť:

  1. Najprv nastav premennú CSP_REPORT_ONLY=1. Prehliadač politiku nevynucuje, ale hlási porušenia v konzole. Otestuj stránku.
  2. Uprav politiku, ak niečo legitímne blokuje, napríklad povoľ konkrétny hostiteľ v img-src.
  3. Až potom premennú odstráň, aby sa politika vynucovala.

HSTS po zapnutí zruší možnosť otvoriť web cez HTTP. Hodnotu includeSubDomains zapínaj len ak všetky subdomény podporujú HTTPS.

Čo CSP nevyrieši

  • Dáta sa dajú vyniesť aj cez iné cesty, napríklad cez prvky, ktoré dovolíš (obrázky z cudzej domény), alebo cez DNS vyhľadávanie. Preto je politika postavená na default-src 'none' a povoľuje sa len nevyhnutné.
  • Ak dovolíš v connect-src cudziu doménu, môže ju zneužiť aj útočník.
  • Nonce chráni pred cudzími skriptami vloženými do stránky. Nechráni pred zraniteľným originom, kde by sa škodlivý skript dostal do HTML ešte pred Workerom, lebo ten by dostal nonce tiež. Origin musí zostať dôveryhodný.
  • Skript, ktorý už bežal v tvojej stránke so SRI hashom, ktorý si vypočítal zo škodlivého súboru, ťa nezachráni. Overuj hash nezávisle.

Systémový prompt pre AI kódovacieho agenta

Ak pri písaní stránok používaš AI agenta, tento prompt ho navedie, aby externé skripty bral opatrne. Dôležité je, že mu zakazuje vymýšľať hashe, lebo model hash bez stiahnutia súboru nespočíta.

Pri práci s externými skriptami a štýlmi dodržuj tieto pravidlá:

1. Nikdy nepoužívaj nepripnuté verzie. Zakázané sú „latest“, „@latest“, rozsahy verzií a URL bez presnej verzie. Vždy použi presnú verziu, napríklad „@1.2.3“.
2. Uprednostni knižnicu uložiť do repozitára a servírovať z vlastnej domény. Externý CDN použi len keď je to nevyhnutné a povedz mi prečo.
3. Ku každému externému skriptu a štýlu pridaj atribúty integrity="sha384-…" a crossorigin="anonymous".
4. Hash nikdy nevymýšľaj ani neodhaduj. Vypočítaj ho zo skutočne stiahnutého súboru príkazom:
   curl -fsSL URL | openssl dgst -sha384 -binary | openssl base64 -A
   Ak príkaz nemôžeš spustiť, nechaj v kóde viditeľný zástupný text „DOPLN_SRI_HASH“ a upozorni ma, že ho treba doplniť.
5. Nepoužívaj dynamické importy ani skripty, ktoré načítavajú ďalšie skripty z cudzích domén. Ak je to nutné, upozorni ma.
6. Nepridávaj inline obsluhu udalostí (onclick a podobne) ani eval. Skripty píš tak, aby fungovali s politikou CSP bez „unsafe-inline“ a „unsafe-eval“.
7. Po každej zmene vypíš zoznam všetkých externých zdrojov s verziou a hashom, aby som ich mohol skontrolovať.

Kontrolný zoznam

  • Žiadne latest v odkazoch. Verzie sú pripnuté.
  • Každý externý skript má SRI a crossorigin.
  • CSP s default-src 'none' a connect-src 'self' je nasadená, najprv v režime hlásenia.
  • Kľúčové knižnice sú uložené vo vlastnom repozitári, nie na cudzom CDN.
  • Prístup k účtom (npm, GitHub, Cloudflare) je chránený hardvérovými kľúčmi alebo prístupovými kľúčmi, nie len heslom a SMS.

Čo si z toho odniesť

Útok cez dodávateľský reťazec nevyžaduje prelomiť tvoj web. Stačí, že načítaš niečo, čo sa medzičasom zmenilo. SRI zabráni spusteniu zmeneného súboru a CSP zabráni úniku dát, ak sa niečo predsa spustí. Obe vrstvy dokážeš zapnúť zadarmo, ak tvoj web servíruješ cez Cloudflare. Žiadna z nich nenahrádza opatrnosť pri výbere závislostí.

Zdieľať článok

Mohlo by vás zaujímať