From bfa4017427852d56e7794fb3961cd558d818e146 Mon Sep 17 00:00:00 2001 From: Miccy Date: Sun, 23 Aug 2026 02:25:33 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=9D=20docs:=20add=20a=20Czech=20README?= =?UTF-8?q?=20and=20drop=20the=20article?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The article does not belong here. A repo carries its own documentation, not content written about it, and this one is a source text for publication with its own draft state and revision history. It moved to the private knowledge base under 10-Publications. README-cs.md is a full Czech version, linked from the English one. It is not a literal translation: the ecosystem table is folded into prose, and the manager-name caveat sits next to the local validation command where someone would hit it. --- README-cs.md | 178 ++++++++++++++++++++++++++ README.md | 2 + docs/clanek-supply-chain-cs.md | 225 --------------------------------- 3 files changed, 180 insertions(+), 225 deletions(-) create mode 100644 README-cs.md delete mode 100644 docs/clanek-supply-chain-cs.md diff --git a/README-cs.md b/README-cs.md new file mode 100644 index 0000000..2397ffb --- /dev/null +++ b/README-cs.md @@ -0,0 +1,178 @@ +# supply-chain + +**Sdílená supply-chain politika pro aktualizace závislostí** + +_Více ekosystémů • Více forgí • Ve výchozím stavu čeká na review_ + +> English version: [README.md](./README.md) + +--- + +## Co to je + +Renovate preset, který zapisuje jediné rozhodnutí: **které aktualizace +závislostí smějí přistát, aniž se na ně někdo podívá.** + +Odpověď je záměrně úzká. Důvěryhodné vývojářské nástroje a minor/patch +aktualizace CI akcí se sloučí samy. Produkční závislosti, obnova lockfilu +a holé změny digestu ne — to jsou cesty, kterými se šíří supply-chain útok. + +Není to konfigurace pro JavaScript. Vedle JS ekosystémů pokrývá Rust, Nix, Go, +Python, Docker, Terraform, Ansible i CI akce, protože hrozbu nezajímá, v čem +píšeš. + +### Proč vznikl + +Útok Shai-Hulud 2.0 na npm z listopadu 2025 je původní příběh, ne rozsah. +Vyvolal první verzi a seznam 428 balíčků, ale politika je obecná: počítej +s tím, že se libovolná závislost může mezi dvěma vydáními obrátit proti tobě, +a udělej z toho review místo merge. + +Seznam kompromitovaných balíčků se pořád dodává, s podmínkou schválení +v dashboardu. Detekci a nápravu řeší +[dont-be-shy-hulud](https://github.com/miccy/dont-be-shy-hulud). + +## Jak to použít + +Do nového repozitáře stačí `renovate.json`: + +```json +{ "extends": ["github>ownctrl/supply-chain"] } +``` + +To je celé nastavení. Preset nese schedule, grupování, automerge politiku +i pokrytí ekosystémů — per repo se nekonfiguruje nic. + +Pokud nechceš, aby se ti politika měnila pod rukama, připni si vydání: + +```json +{ "extends": ["github>ownctrl/supply-chain#v1.0.0"] } +``` + +### Předpoklady + +Dva, oba jednorázové: + +1. **Renovate na to repo dosáhne.** Na GitHub.com to znamená nainstalovanou + Mend Renovate App. Na každém jiném forge si Renovate hostuješ sám — viz níž. +2. **Auto-merge je povolený v nastavení repozitáře.** Preset nastavuje + `platformAutomerge`, který používá nativní auto-merge GitHubu. S vypnutým + nastavením Renovate tiše spadne na vlastního bota — funguje to, ale jinak, + než tenhle preset popisuje. + +### Kde Renovate běží + +Preset je jen JSON, funguje všude, kde funguje Renovate. Liší se to, jak +Renovate rozjet — a bez práce to je jen na GitHub.com: + +| Forge | Jak Renovate běží | +| --- | --- | +| GitHub.com | Mend Renovate App — hostovaná, zdarma, nic nespouštíš | +| GitLab | self-hosted; CI šablona [`renovate-runner`](https://gitlab.com/renovate-bot/renovate-runner) ho pouští jako scheduled pipeline | +| Codeberg / Forgejo / Gitea | self-hosted; Renovate CLI na plánovači | +| Bitbucket | self-hosted | + +Je to vlastnost Renovate ekosystému, ne tohoto presetu — narazíš na to +s jakýmkoli presetem i bez něj. + +### Co čekat na novém repu + +- **Do pondělí se nestane nic.** Schedule je `before 06:00 on monday` + (Europe/Prague). Není to chyba nastavení — když chceš první běh hned, nastav + si v repu `"schedule": ["at any time"]`. +- **Dependency Dashboard issue je ovládací panel.** Major verze a známé + kompromitované balíčky tam čekají na klik. +- **Co se sloučí samo:** důvěryhodné vývojářské nástroje (Biome, Oxlint, + TypeScript, Vitest, Jest, ESLint, Prettier a jejich scopes) a minor/patch + aktualizace GitHub akcí. Všechno ostatní otevře PR a počká na tebe. + +Poslední bod je záměrný kompromis: produkční závislosti, obnova lockfilu +a holé změny digestu jsou cesty supply-chain útoku, takže na ně review sedí +schválně. Počítej s několika kliky týdně, ne s nulou. + +## Presety + +Každý z nich je hotový preset, ne úryvek ke kopírování: + +| Preset | Reference | Co mění | +| --- | --- | --- | +| base | `github>ownctrl/supply-chain` | politika popsaná výš | +| lockdown | `github>ownctrl/supply-chain:lockdown` | nic se neslučuje samo, 14denní odstup u npm, všechno čeká na schválení | +| no-automerge | `github>ownctrl/supply-chain:no-automerge` | automerge vypnutý, zbytek beze změny | +| aggressive | `github>ownctrl/supply-chain:aggressive` | kdykoli, bez odstupu, vyšší limit PR | + +Po **lockdownu** sáhni při aktivním supply-chain incidentu, po **aggressive** +jen když si jsi jistý, že žádný neprobíhá. + +Sub-presety dědí base samy, takže se neuvádějí oba. + +## Souhrn politiky + +| Nastavení | Hodnota | Důvod | +| --- | --- | --- | +| `minimumReleaseAge` | 7 dní | vyhýbá se čerstvě vydaným balíčkům (jako packageRule pro npm, který přebíjí hodnotu z nejvyšší úrovně) | +| `security:minimumReleaseAgeNpm` | zapnuto | vyhýbá se čerstvě vydaným a odpublikovaným balíčkům | +| `rangeStrategy` | pin | přesné verze (npm, bun, deno) | +| `prConcurrentLimit` | 4 | proti záplavě PR | +| `schedule` | pondělí 06:00 | týdenní aktualizace | +| `timezone` | Europe/Prague | místní čas | +| `automerge` (produkční) | ❌ vypnuto | vyžaduje bezpečnostní review | +| `automerge` (důvěryhodné dev) | ✅ zapnuto | Biome, TypeScript, Vitest a spol. | +| `vulnerabilityAlerts` | ✅ zapnuto | s bezpečnostními štítky | +| `lockFileMaintenance` | ✅ týdně | review nutné — odstup se sem nevztahuje | + +## JS runtimy a package managery + +Renovate tu má tři managery a to rozdělení není to, co bys čekal: + +- **`npm`** pokrývá npm, pnpm **i** yarn. Samostatný `pnpm` ani `yarn` manager + neexistuje — jsou to tři lockfily jednoho manageru. +- **`bun`** je vlastní manager (`bun.lock`, `bun.lockb`). Lockfile commituj. +- **`deno`** je vlastní manager (`deno.json`, `deno.jsonc`, `deno.lock`) + a čerpá z datasources npm, jsr a deno. + +`.bun-version` obsluhuje manager `bun-version`. + +**Nub** nepotřebuje nic zvláštního. Čte a zapisuje ten lockfile, který projekt +už má (`package-lock.json`, `pnpm-lock.yaml`, `bun.lock`), takže Renovate dál +používá odpovídající manager a nub si výsledek přečte. Obnovu lockfilu ale +provádí ten původní package manager, ne nub. + +## Lokální validace + +```bash +./tooling/validate.sh +``` + +Stejný seznam presetů, jaký kontroluje CI. Jako pre-push hook: + +```bash +ln -s ../../tooling/validate.sh .git/hooks/pre-push +``` + +Lokální hook je pohodlí, ne hranice — dá se přeskočit. Skutečný gate zůstává +CI. + +Pozor na jedno omezení: **`renovate-config-validator` nevaliduje názvy +managerů.** `matchManagers: ["npm", "pnpm", "yarn"]` projde čistě a nematchuje +nic. Gate pokrývá schéma, ne význam. + +## Pod vlastním účtem + +Repozitář si zkopíruj, ale **neforkuj ho pro každou organizaci zvlášť.** +Dědění je levnější: + +```json +{ + "extends": ["github>ownctrl/supply-chain"], + "labels": ["dependencies", "tvoje-značka"] +} +``` + +Fork znamená opravit každou bezpečnostní vadu tolikrát, kolik máš kopií. + +## Odkazy + +- 🪱 [dont-be-shy-hulud](https://github.com/miccy/dont-be-shy-hulud) — detekce a náprava Shai-Hulud 2.0 +- 🔒 [Socket.dev](https://socket.dev) — skenování supply chain +- 📊 [Datadog IOC](https://github.com/DataDog/indicators-of-compromise/tree/main/shai-hulud-2.0) — oficiální IOC seznam diff --git a/README.md b/README.md index 2a53ab2..c15897a 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ _Multi-ecosystem • Multi-forge • Review-gated by default_ +[Česká verze](./README-cs.md) + --- diff --git a/docs/clanek-supply-chain-cs.md b/docs/clanek-supply-chain-cs.md deleted file mode 100644 index 322d048..0000000 --- a/docs/clanek-supply-chain-cs.md +++ /dev/null @@ -1,225 +0,0 @@ ---- -title: "Váš robot na aktualizace závislostí je taky cesta dovnitř" -perex: "Automatické aktualizace závislostí jsou dnes standard. Málokdo si přitom přečte, co přesně jeho konfigurace dovoluje sloučit bez lidského oka. Když jsme si to přečetli my, našli jsme čtyři díry — a tři z nich by nenašel nikdo, kdo jen následuje dokumentaci." -tags: [supply-chain, renovate, bezpečnost, open-source] -lang: cs -draft: false ---- - -## Útok, který nepotřebuje vaši chybu - -V listopadu 2025 se npm ekosystémem prohnal Shai-Hulud 2.0. Nešlo o díru -v něčím kódu. Šlo o to, že se útočník dostal k publikačním právům legitimních -balíčků a vydal novou verzi — a všechno, co ten balíček používalo v rozsahu -semver, si ji samo stáhlo. - -To je na tom to podstatné a je dobré si to říct nahlas: **vy jste neudělali -nic špatně.** Váš kód se nezměnil. Změnilo se něco, čemu jste se rozhodli -věřit, a ta důvěra byla vyjádřená stříškou ve `package.json`. - -Obrana proti tomuhle není lepší kód. Je to politika. Konkrétně odpověď na -jedinou otázku: - -> Které aktualizace závislostí smějí přistát, aniž se na ně někdo podívá? - -Většina týmů na tuhle otázku nikdy vědomě neodpověděla. Odpověděl za ně -výchozí config jejich bota. - -## Co je supply-chain - -[`ownctrl/supply-chain`](https://github.com/ownctrl/supply-chain) je sdílený -preset pro [Renovate](https://renovatebot.com), který tu odpověď zapisuje -explicitně. Vezmete si ho jedním řádkem: - -```json -{ "extends": ["github>ownctrl/supply-chain"] } -``` - -A to je celé nastavení. Žádný per-repo tuning. - -Odpověď, kterou ten preset dává, je záměrně úzká: - -**Sloučí se samo:** důvěryhodné vývojářské nástroje (Biome, Oxlint, -TypeScript, Vitest, Jest, ESLint, Prettier) a minor/patch aktualizace CI akcí. - -**Počká na vás:** produkční závislosti, obnova lockfilu, holé změny digestu -a všechny major verze. - -Ta druhá skupina není seznam nepohodlných výjimek. To jsou přesně cesty, -kterými se supply-chain útok šíří. - -## Čtyři věci, které jsme našli ve vlastním configu - -Preset jsme napsali jako reakci na Shai-Hulud. O devět měsíců později jsme si -ho pořádně přečetli. Tady je, co v něm bylo — a proč to jsou obecné pasti, ne -naše specifické hlouposti. - -### 1. Sedmidenní odstup nikdy neplatil - -V configu stálo `"minimumReleaseAge": "7 days"`. V README stálo, že čekáme -sedm dní, než balíček navrhneme. Obojí byla pravda o tom, co jsme napsali, -a lež o tom, co se dělo. - -Preset totiž dědil `security:minimumReleaseAgeNpm`, který nastavuje **tři dny -přes `packageRule`**. A `packageRule` přebíjí hodnotu z nejvyšší úrovně. Takže -npm — ekosystém, kvůli kterému ten preset vůbec vznikl — jel na třech dnech. - -To je past, do které spadne každý, kdo kombinuje vlastní hodnoty s děděnými -presety. **Nastavení na nejvyšší úrovni je slabší než pravidlo, ne silnější.** - -### 2. `^jest` je celý cizí namespace - -Měli jsme seznam „důvěryhodných" nástrojů, které se smějí slučovat samy. -Vypadal rozumně: `^jest`, `^vitest`, `^oxlint`. - -Jenže to jsou regulární výrazy bez ukotvení konce. `^jest` neodpovídá balíčku -`jest`. Odpovídá **každému balíčku, jehož název začíná na `jest`** — a prefix -`jest-` na npm nikdo nevlastní. Může do něj publikovat kdokoli. - -Kdokoli by tedy mohl vydat `jest-cokoli`, vy byste si to jednou přidali jako -vývojářskou závislost, a od té chvíle by se jeho aktualizace slučovaly bez -review. S důvěrou, kterou jste chtěli dát projektu Jest. - -**Ukotvujte celý název**, pokud není ve scope, který vlastníte. `^jest$` ano. -`^@testing-library/` taky ano, protože npm scopes vlastníka mají. `^jest` ne. - -### 3. Obnova lockfilu obchází všechny brzdy - -Renovate umí týdně přegenerovat lockfile, aby stáhl nejnovější verze v rámci -existujících rozsahů. Měli jsme to zapnuté a slučovalo se to samo. - -Ta funkce ale **nepodléhá `minimumReleaseAge`**. Renovate to říká ve vlastní -dokumentaci: kontrola stáří se nevztahuje na `pin`, `lockFileMaintenance`, -`lockfileUpdate`, `rollback`, `bump` ani `replacement`. - -Takže každé pondělí ráno se každá tranzitivní závislost posunula na nejnovější -vyhovující verzi, bez jakéhokoli odstupu a bez review. Tranzitivní šíření -uvnitř semver rozsahů je přesně to, jak Shai-Hulud cestoval. - -Vypnuli jsme to. Stojí to jeden klik týdně. - -### 4. Validátor nechytá to, co byste čekali - -Přidali jsme do CI `renovate-config-validator --strict`. Hned se to vyplatilo: -zachytil chybu v opravě, kterou jsme psali o commit dřív, a která by způsobila, -že Renovate celé pravidlo zahodí — díra by zůstala otevřená, ale vypadalo by to -opravené. - -Jenže má hranice, které stojí za to znát. Config obsahoval -`matchManagers: ["npm", "pnpm", "yarn"]`. **`pnpm` ani `yarn` nejsou managery** -— všechny tři lockfily obsluhuje manager `npm`. Ty dva zápisy neodpovídaly -ničemu. - -Validátor to pustil bez jediného varování. Ověřovali jsme to schválně. - -Horší bylo, co z toho plynulo: manager `bun` je samostatný a v tom pravidle -chyběl. Repozitáře, které jedou výhradně na bunu, nedostávaly pinování verzí, -které jsme v README inzerovali jako základní vlastnost. - -**Váš CI gate kontroluje schéma, ne význam.** Překlep v názvu manageru tiše -vypne celé pravidlo. - -## Co to usnadňuje - -Praktický zisk není „bezpečnost" jako abstraktní pocit. Je konkrétní: - -- **Nové repo je hotové jedním řádkem.** Žádné kopírování konfigurace, žádné - rozhodování, žádný per-projekt tuning. -- **Jedna oprava platí všude.** Když se objeví další Shai-Hulud, upravíte - jeden soubor, ne dvacet repozitářů. -- **Politika je čitelná.** Můžete ji někomu ukázat. Můžete se o ní hádat. - To o výchozím nastavení bota nejde. -- **Nezáleží na jazyce.** JavaScript, Rust, Go, Python, JVM, .NET, PHP, Ruby, - Nix, Terraform, Docker, Kubernetes. Hrozba se o váš jazyk nezajímá, tak proč - by se měla politika. - -## Jak si ho vzít a upravit - -### Základ - -Do nového repozitáře stačí `renovate.json`: - -```json -{ "extends": ["github>ownctrl/supply-chain"] } -``` - -Pokud nechcete, aby se vám politika měnila pod rukama, připněte si vydání: - -```json -{ "extends": ["github>ownctrl/supply-chain#v1.0.0"] } -``` - -Jedna věc, kterou je poctivé říct rovnou: **hostovanou Renovate aplikaci má -jen GitHub.com.** Na GitLabu, Codebergu, Forgejo, Gitea i Bitbucketu si Renovate -musíte spustit sami. Je to vlastnost Renovate ekosystému, ne tohoto presetu — -narazíte na to s jakýmkoli presetem i bez něj. - -### Když zrovna hoří - -Při aktivním incidentu si přepněte do lockdownu. Nic se neslučuje samo, -čtrnáctidenní odstup, všechno čeká na schválení: - -```json -{ "extends": ["github>ownctrl/supply-chain:lockdown"] } -``` - -### Když nehoří a spěcháte - -Pro nekritické projekty: - -```json -{ "extends": ["github>ownctrl/supply-chain:aggressive"] } -``` - -Žádný odstup, aktualizace kdykoli. Nepoužívejte to, když si nejste jistí, že -zrovna neprobíhá útok. - -### Přepsání jednotlivostí - -Cokoli z presetu jde přebít ve vlastním souboru. Ale pozor na past číslo 1 — -pokud přepisujete něco, co je v presetu nastavené jako `packageRule`, musíte -to taky napsat jako `packageRule`: - -```json -{ - "extends": ["github>ownctrl/supply-chain"], - "packageRules": [ - { "matchDatasources": ["npm"], "minimumReleaseAge": "14 days" } - ] -} -``` - -Nastavení `"minimumReleaseAge": "14 days"` na nejvyšší úrovni by tady -nefungovalo. Přesně tak jsme si tu chybu vyrobili my. - -### Pro vlastní organizaci - -Repozitář si zkopírujte, ale **neforkujte ho pro každou značku zvlášť.** -Dědění je levnější: - -```json -{ - "extends": ["github>ownctrl/supply-chain"], - "labels": ["dependencies", "vaše-značka"] -} -``` - -Fork znamená opravit každou bezpečnostní vadu tolikrát, kolik máte kopií. -Ty čtyři výše bychom opravovali čtyřikrát. - -## Co si z toho odnést, i když náš preset nepoužijete - -1. **Přečtěte si, co váš bot smí sloučit sám.** Ne co si myslíte, že smí. -2. **Ukotvujte vzory názvů**, pokud nejste vlastníkem prefixu nebo scope. -3. **Zjistěte, které typy aktualizací obcházejí vaše brzdy.** U Renovate to - jsou `lockFileMaintenance`, `pin`, `bump`, `rollback` a `replacement`. -4. **Nezaměňujte validaci schématu za validaci významu.** Zelené CI neznamená, - že vaše pravidla něčemu odpovídají. - -Nic z toho není v dokumentaci označené jako past. Všechno to jsou důsledky -dvou pravidel, která dávají samostatně smysl a dohromady vás překvapí. - ---- - -*Preset je pod MIT licencí. Chyby, nápady a nesouhlas vítáme — -[ownctrl/supply-chain](https://github.com/ownctrl/supply-chain).*