Er draait niets als je om een pagina vraagt
Deze week bracht het PHP-project fixes uit voor PHP2Shell, en volgens de advisory kan de aanval afgaan “before any application code runs”1. Wat dat voor PHP betekent, schreef ik in PHP: negenentwintig jaar de deur open laten staan. Deze post gaat over de andere helft van het antwoord. Wat als er helemaal niets draait?
Zo werkt deze blog. Toen je om deze pagina vroeg, startte er geen programma van mij op, werd er geen database gelezen, geen login gecontroleerd en geen HTML gebouwd. Er werd een bestand teruggestuurd dat al bestond. Meer is het niet.
Zo ziet dat verschil eruit, op dezelfde dag gemeten, tegen een standaard-WordPress die vers in een lab is geïnstalleerd en tegen deze site zoals hij nu live staat:
| Standaard-WordPress 7.1.3 | Deze site | |
|---|---|---|
| REST API-routes | 133 | 0 |
| REST-routes die schrijfacties accepteren | 62 | 0 |
| XML-RPC-methoden | 80 | 0 |
| Bestanden die op de server worden uitgevoerd | 1.514 PHP | 0 |
| Bestanden schrijfbaar voor de gebruiker die de site serveert | 3.785 van 3.785 | geen: er draait niets dat kan schrijven |
| Database | 12 tabellen, app-account met elk privilege | geen |
| Loginpagina | wp-login.php, live | geen; het pad geeft 404 |
| Een POST, PUT of DELETE naar de homepage | PHP draait en rendert de pagina toch | aan de rand geweigerd met 405; er draait niets |
Geen van die nullen is een instelling die ik heb uitgezet. Het zijn nullen omdat het ding dat ze tellen hier niet bestaat. Je kunt geen loginpagina aanvallen die nooit is gebouwd.
Wat een standaard-WordPress op internet zet
Ik wilde dit niet uit mijn geheugen beargumenteren, dus heb ik het gemeten. De officiële containerimages wordpress en mariadb, in podman op mijn eigen machine gedraaid en aan localhost gebonden, geïnstalleerd zonder iets toe te voegen: geen plugins buiten wat er standaard bij zit, het standaardthema, één beheerder. WordPress 7.1.3 op PHP 8.3.36. Daarna heb ik geteld waar het allemaal op antwoordt.
De REST API is het grootste losse oppervlak. Vijf namespaces, 133 routes, 257 combinaties van route en methode, en 62 routes die een of andere schrijfactie accepteren. Hij kwam in core mee met WordPress 4.7, “with REST API endpoints for posts, comments, terms, users, meta, and settings”2. En je krijgt te horen dat je hem niet moet uitzetten: “You should not disable the REST API; doing so will break WordPress Admin functionality that depends on the API being active.”3 Dus hij blijft aan.
| Wat standaard openstaat | Wat de eigen documentatie van WordPress zegt |
|---|---|
| XML-RPC, 80 methoden | standaard aan sinds 3.5: “Enable XML-RPC by default, remove Writing Settings option”4 |
| de REST API, 133 routes | “content that is public on your site is generally publicly accessible via the REST API”5 |
| de gebruikerslijst | GET /wp/v2/users geeft in de standaardweergave van elke auteur de weergavenaam, slug, link en avatar terug6 |
wp-login.php en xmlrpc.php | “xmlrpc.php is a frequent brute‑force target”; “Apply rate limits to /wp-login.php and /xmlrpc.php globally.”7 |
wp-cron.php | “WP-Cron works by checking, on every page load, a list of scheduled tasks to see what needs to be run.”8 |
| de thema- en plugineditor | “This is often the first tool an attacker will use if able to login, since it allows code execution.”9 |
De gebruikerslijst verdient een tweede blik. In mijn lab gaf een anoniem request naar dat endpoint het ene beheerdersaccount terug, met weergavenaam en slug. Iemand die nog nooit heeft ingelogd weet wie de accounts van de site zijn voordat hij ook maar iets heeft geprobeerd, op een verse installatie, en zo is het ontworpen. Niemand hoefde iets verkeerd te configureren.
Dan de bestanden. Het officiële image draait Apache als www-data, en www-data is eigenaar van de hele webroot. Alle 3.785 bestanden, waaronder de 1.514 PHP-bestanden, zijn schrijfbaar voor dezelfde gebruiker die de code draait. Elk bestand. De code inbegrepen. wp-config.php, met het databasewachtwoord erin, staat in de webroot met mode 644. Het databaseaccount dat WordPress gebruikt heeft ALL PRIVILEGES op zijn database.
Dat is het één-procesmodel uit de PHP-post, live en ongewijzigd, uit het officiële image. Het is geen slecht beheerde site. Het is het startpunt waar elke WordPress-site mee begint, voordat de eerste plugin erin gaat.
En de plugins zijn waar het meeste vandaan komt. Patchstack telde in 2025 11.334 nieuwe kwetsbaarheden in het WordPress-ecosysteem. “91% of new vulnerabilities were found in plugins, and 9% were found in themes. There were only 6 vulnerabilities reported in the WordPress core.”10 Bijna de helft had nog geen fix op het moment dat ze openbaar werden, en de mediane tijd tot massaal misbruik was, voor de kwetsbaarheden die aanvallers wilden hebben, vijf uur10.
Wat deze site op internet zet
Deze blog bestaat uit 6.343 bestanden. Dit is alles, per soort:
| Soort bestand | Aantal | Wat het is |
|---|---|---|
.html | 3.486 | de pagina’s, in vijf talen |
.xml | 1.576 | RSS-feeds en sitemaps |
.svg | 1.181 | diagrammen, covers, iconen |
.png | 46 | screenshots |
.json | 10 | de zoekindex en buildstempels |
.js | 4 | kleine scripts die in jouw browser draaien, niet op de server |
.css, fonts en de rest | 40 | opmaak, en bestanden bij posts |
| Uitgevoerd op de server | 0 |
Er zitten een paar .py- en .sh-bestanden tussen. Dat zijn scripts bij posts die lezers kunnen downloaden, en de server stuurt ze als tekst. Niets op de server voert ze uit. Er staat op de server ook helemaal niets waarmee dat zou kunnen.
Het staat bij Cloudflare als statische assets, zonder enig Worker-script. De deployconfiguratie heeft geen entry point, en de documentatie van Cloudflare zegt wat dat betekent: “The main key is optional for assets-only Workers.”11 Als een opgevraagd pad overeenkomt met een bestand, “that file will be served” zonder dat er Worker-code wordt aangeroepen, en “If no Worker script is present, a 404 Not Found response is returned.”12 Het kost ook niets: “Requests to static assets are free and unlimited.”13
Cloudflare is een keuze, geen vereiste. De uitvoer is een map met bestanden, en elke HTTPS-server die een bestand kan versturen kan hem serveren: nginx, Caddy, een Apache die alleen statische bestanden uitlevert, of een object-storagebucket achter een CDN. De links zijn relatief, dus dezelfde build draait ongewijzigd vanaf de root van een domein of vanuit een submap; getest door de gebouwde site onder /preview/ te serveren en te controleren dat alle 592 lokale verwijzingen op zes pagina’s klopten. Van host wisselen is een map kopiëren. Het enige Cloudflare-specifieke stuk is het _headers-bestand; elders horen dezelfde headers in de configuratie van de server. Wat meegaat is de eigenschap die ertoe doet. Wat de site serveert hoeft alleen bestanden te versturen, en hoeft dus nooit jouw code te draaien.
Dus de live site antwoordt zo:
$ for m in POST PUT DELETE; do curl -s -o /dev/null -w "$m %{http_code}\n" -X $m https://blogs.damiendye.uk/en/; done
POST 405
PUT 405
DELETE 405
$ for p in wp-login.php xmlrpc.php wp-admin/ wp-json/; do curl -s -o /dev/null -w "%{http_code} $p\n" https://blogs.damiendye.uk/$p; done
404 wp-login.php
404 xmlrpc.php
404 wp-admin/
404 wp-json/
Er antwoordt niets. Dat is het ontwerp.
De securityheaders gaan met elk bestand mee vanuit een platte-tekstbestand _headers in de repository, dat Cloudflare bij de deploy inleest14. De Content Security Policy beperkt elk script, elke stijl, afbeelding, font en verbinding tot deze ene origin. Ik ben eerlijk over het gat erin: script-src en style-src hebben nog steeds 'unsafe-inline', omdat het thema een paar kleine scripts in de pagina schrijft. Dat is de openstaande klus op dat bestand, en zo staat het ook letterlijk in de repository.
En publiceren heeft geen deur nodig. Bij WordPress is publiceren een schrijfactie over HTTP: de editor, de REST API en XML-RPC bestaan omdat berichten zo op de server komen, en elk daarvan is een weg naar binnen. Hier schrijf ik een bericht als tekstbestand en push ik het naar een Git-repository, en de build uploadt kant-en-klare bestanden via de deploy-API van Cloudflare. Niets wat de site serveert accepteert een schrijfactie vanaf het web, omdat niets er een nodig heeft. De repository is bovendien privé. Vraag GitHub ernaar zonder mijn inloggegevens en het antwoord is een 404, dus de broncode, de concepten en de geschiedenis staan ook niet op internet.
Aanvalsoppervlak is een telling, geen gevoel
OWASP definieert het aanvalsoppervlak als “the sum of all paths for data/commands into and out of the application, and the code that protects these paths”15, en zegt dat je het verkleint door “turning off features and interfaces that aren’t being used”15. Het Britse National Cyber Security Centre zegt het botter: “If would-be attackers can’t reach an interface, they can’t attack it.”16 Het Cyber Essentials-schema maakt er een eis van: “remove or disable unnecessary software (including applications, system utilities and network services)”17.
Tel de paden dus, naar wat ze zijn en niet naar hoeveel het er zijn:
| Pad naar binnen of naar buiten | WordPress | Statische site |
|---|---|---|
| inloggen en wachtwoorden raden | wp-login.php, XML-RPC | geen |
| beheerinterface | /wp-admin/, met een code-editor | geen |
| programmeerbare API | REST en XML-RPC, met schrijfacties | geen |
| servercode bij elk request | core, thema, elke plugin | geen |
| database | ja, bereikbaar vanuit al het bovenstaande | geen |
| schrijfbare opslag op de server | uploads, en in het standaardimage alles | geen |
| geplande taken die bezoekers in gang zetten | wp-cron.php bij het laden van een pagina | geen |
| code van derden die je gepatcht moet houden | elke plugin en elk thema, op de server | de buildtools, buiten de server |
| wat een gestolen sessie een aanvaller oplevert | de site | niets om op in te loggen |
Negen rijen. Geen ervan draait code op de server. Elke keer om dezelfde reden. Het werk was al gedaan voordat iemand om een pagina vroeg.
Die voorlaatste rij is het eerlijke addertje, en daar gaat de rest van deze post over. Een statische site draait nog steeds code. Alleen ergens anders, één keer, vóór het publiceren.
Hoe Hugo de build eerlijk houdt
Hugo is “a static site generator written in Go”18, dus het programma dat bouwt is zelf geheugenveilig en gecompileerd, en dat telt na de PHP-post. De securitypagina van Hugo trekt de lijn precies waar deze post dat doet: de output “runs directly in the browser”, maar “during development and site building, the hugo executable itself is the runtime environment.”19 En over serveren, zonder omwegen: “We have no online production server”19. Mooi.
Binnen de build is het standaard dichtgezet. “Hugo’s built-in security policy, which restricts access to os/exec, remote communication, and similar operations, is configured via allowlists. By default, access is restricted.”20
| Standaard in Hugo | Wat het tegenhoudt |
|---|---|
| “No arbitrary commands”19 | een template kan op de machine geen shell aanroepen |
exec.allow beperkt tot Sass, Go, Git, Node en PostCSS20 | alleen benoemde tools, met vaste flags |
funcs.getenv beperkt tot ^HUGO_ en ^CI$20 | templates kunnen je geheimen niet uit de omgeving lezen |
| fetches op afstand weigeren hosts die een IP-adres of localhost zijn20 | een template kan je eigen netwerk niet in |
modules gecontroleerd tegen go.sum19 | een gemanipuleerde dependency laat de build falen |
Deze site verruimt er bewust één van. Hij voegt de commitvariabelen van de CI toe aan funcs.getenv, zodat de buildstempel op /en/version.json kan vastleggen welke commit de live site heeft gebouwd. Dat is een keuze, gemaakt en opgeschreven, en daar dient een allowlist nu juist voor. Het is dus geen gat. Het is een besluit.
Waar het risico naartoe verhuist: de build
Haal de server weg en het risico verdwijnt niet. Het verhuist naar wat de bestanden bouwt. Het SLSA-framework benoemt de twee dreigingen precies: een “build integrity threat”, waarbij een tegenstander “behavior to an artifact without changing its source code” introduceert, en een “dependency threat”, door “compromising some other artifact that the former depends on at build time”21.
Deze site zit in de middelste rij, en ik ga niet doen alsof dat anders is. Hij wordt bij elke push naar de repository gebouwd in de buildcontainer van Cloudflare. Die container is niet de server die jou antwoordt, en hij is weg zodra de build klaar is. Maar zolang de build duurt, wordt hij vertrouwd met alles wat op de site terechtkomt.
En de build had twee gaten, die ik allebei vond terwijl ik deze post schreef. Eén is gedicht. Eén is een keuze.
| Gat | Waarom het ertoe doet | Status |
|---|---|---|
| het Hugo-binary werd op goed vertrouwen aangenomen: de eigen download van het script had geen checksumcontrole, en in de CI sloeg het de download sowieso over en gebruikte het de kopie die in het buildimage van Cloudflare was geïnstalleerd | een verwisseld binary zou bouwen wat het maar wilde | gedicht, 23 oktober 2026: de build downloadt Hugo altijd zelf, controleert het tegen de gepubliceerde SHA-256 van de release22 en weigert te bouwen als die niet klopt |
het thema volgt bij elke build zijn upstream-branch master | een wijziging upstream kan de live site veranderen zonder een commit hier | open, bewust; de oplossing is het vastpinnen op een commit, of het verhuizen naar een Hugo-module die door go.sum wordt gedekt19 |
De eerste is het soort ding dat je alleen vindt door je eigen buildscript regel voor regel te lezen. Het script controleerde wel de versie. Maar een versiestring is iets wat een binary over zichzelf zegt. Het bewijst niets over de bytes. Nu worden de bytes gecontroleerd:
Fetching Hugo Extended 0.167.0...
Hugo verified: sha256 0163f5c3deddac1f494a1629ddc40c65d18de9d5794facd98f7f96ac2c7d8957
En een kopie van dezelfde tarball met één byte erbij wordt geweigerd:
Refusing to build: hugo_extended_0.167.0_linux-amd64.tar.gz does not match the release checksum
expected 0163f5c3deddac1f494a1629ddc40c65d18de9d5794facd98f7f96ac2c7d8957
got 92682d765d9d34bc53d6f818d56e598d34b5b8267a1cc3b9971da0f53c792251
Het thema is een bewuste afweging, actualiteit boven reproduceerbaarheid, en zo staat het ook opgeschreven. Maar het is precies de dependency threat die SLSA beschrijft, en dan is het eerlijk om dat te zeggen. Geen van beide gaten heeft de live site ooit blootgesteld aan een bezoeker. Allebei waren het vertrouwen dat in de build werd gelegd, en ik zet ze liever zelf op een rij dan dat een lezer ze vindt.
Zelfs de checksum heeft een grens. Hij bewijst dat het bestand dat je hebt gedownload het bestand op de releasepagina is. Hij bewijst niet wie het daar heeft neergezet: er zitten geen handtekeningen tussen de bestanden van de release22. Dat is integriteit, geen authenticiteit, en meer biedt het project vandaag niet.
Offline bouwen, één keer
De strengste versie haalt de build van andermans machine af. Bouw op een machine die je zelf bezit en beheert, controleer wat je hebt gebouwd, en upload alleen de kant-en-klare bestanden. Niets op internet draait ooit je build, en de host ziet nooit iets anders dan output.
| Stap | Waarom |
|---|---|
| installeer de exacte Hugo-release en controleer hem tegen de gepubliceerde SHA-256 | je weet welk binary je site heeft gebouwd |
pin het thema en eventuele modules op commits, en commit go.sum | de build verandert alleen als jij hem verandert |
bouw in een schone outputmap, zonder --buildDrafts | wat je publiceert is wat je bedoelde te publiceren |
| zoek in de output naar alles wat uitvoerbaar is, voordat het de deur uit gaat | een statische site hoort niets te bevatten wat de server zou uitvoeren |
| upload alleen de outputmap, met een credential die alleen kan deployen | de host krijgt nooit je broncode, je tools of je geschiedenis in handen |
| serveer het vanaf een host die voor statische bestanden geen code draait | er valt op serveertijd niets meer te patchen |
Dat zijn een paar commando’s, geen project. En het verandert waar een aanvaller moet zijn om je schade te doen. Om een WordPress-site te veranderen heeft hij één request naar de server nodig. Om dit soort site te veranderen heeft hij je buildmachine nodig. Dat is een veel kleiner doelwit, en het is er een waarvan je de stekker eruit kunt trekken.
Tienduizend pagina’s, gemeten
Aanvalsoppervlak is één prijs van code draaien bij elk verzoek. De stroom is een andere, en de schijf ook. Dus heb ik beide onder dezelfde belasting gezet: 10.000 verzoeken voor de homepage, tien tegelijk, vanaf dezelfde laptop, over gewoon HTTP op localhost zodat geen van beide voor TLS betaalde. Aan de ene kant de standaard-WordPress uit het lab. Aan de andere kant de echte gebouwde bestanden van deze site, geserveerd door nginx in een container, omdat ik geen meter op de edge van Cloudflare kan zetten. De CPU-tijd van elke container heb ik voor en na elke run uit zijn cgroup gelezen, drie runs per kant.
| 10.000 paginaverzoeken | Standaard-WordPress 7.1.3 | Deze site op nginx |
|---|---|---|
| CPU-tijd, drie runs | 409 s, 530 s, 575 s | 0,15 s, 0,18 s, 0,19 s |
| CPU-tijd per pagina | 41 tot 57 ms | 15 tot 19 µs |
| Pagina’s per seconde | 168 tot 236 | 23.679 tot 28.044 |
| Geheugen onder belasting | 302 tot 310 MiB, plus 145 MiB voor de database | 15 MiB |
| Mislukte verzoeken | 0 | 0 |
Dat is twee- tot drieënhalfduizend keer zoveel processortijd voor dezelfde tienduizend pagina’s. Elk van die CPU-seconden kwam uit het stopcontact en werd warmte. De statische kant serveerde alle 10.000 in ongeveer de processortijd die WordPress aan drie of vier van zijn pagina’s besteedde.
De schijf vertelt hetzelfde verhaal:
| Op schijf en in het geheugen | Standaard-WordPress | Deze site |
|---|---|---|
| Containerimages om het te draaien | 803 MB WordPress, 334 MB MariaDB | 166 MB nginx, of helemaal niets bij Cloudflare |
| De site zelf | 115 MB webroot, voor een lege site | 114 MB, vijf talen, elk bericht en elk diagram |
| Database | 163 MB aan bestanden met 0,72 MB aan gegevens | geen |
| Geheugen in rust | 170 MiB, plus 139 MiB voor de database | 13 MiB |
De WordPress-cijfers zijn een verse installatie met één voorbeeldbericht. De statische cijfers zijn dit hele blog.
Eerlijk tegenover WordPress: een plugin voor volledige paginacache zou een groot deel van het CPU-verschil dichten. Maar kijk wat die oplossing is. Het is een statische kopie van elke pagina, voor de PHP gezet zodat de PHP niet draait. Het middel is precies waar deze post voor pleit, aan de zijkant vastgeschroefd op waar hij tegen pleit, met de loginpagina, de API en de database er allemaal nog achter.
Wat je inlevert
De eerlijke lijst, want een statische site is niet voor alles het juiste gereedschap:
| Wat je inlevert | Wat deze site in plaats daarvan doet |
|---|---|
| reacties | geen; lezers kunnen mailen |
| zoeken op de server | een JSON-index die met de site wordt gebouwd en in jouw browser wordt doorzocht |
| formulieren die naar de server posten | geen |
| logins en ledengedeeltes | geen; alles hier is openbaar |
| bewerken in een browser | posts zijn tekstbestanden in een git-repository |
| direct publiceren zonder build | een build duurt een minuut of twee |
Heb je een webwinkel, een ledengedeelte of gebruikersaccounts nodig, dan heb je iets nodig dat op een server draait, en deze post beweert niet het tegendeel. Wat hij wel zegt, is dat een blog, een brochuresite of een set documentatie daar niets van nodig heeft. De meeste WordPress-sites zijn een van die drie. Wees eerlijk over welke de jouwe is.
Bouw het één keer en serveer het dood
Wat me dwarszit is hoeveel risico er zonder enige reden wordt meegesleept. Een bedrijfspagina die twee keer per jaar verandert, en die draait een database, een loginpagina, een API met 62 schrijfbare routes en een takenplanner die afgaat telkens als er iemand langskomt. Allemaal om dezelfde HTML te maken als gisteren. Elke dag.
Dat is geen technische beslissing. Het is een gewoonte, en het is een gewoonte waar iemand een factuur voor stuurt. Hosting, plugins, beveiligingsadd-ons, de opruimklus als het misgaat. Op de prijslijst staat een managed WordPress-pakket. Wat het werkelijk kost, is elk van die paden openhouden, en patchen, en in de gaten houden, voor content die een map met bestanden had kunnen zijn.
De standaard waar ik deze site aan houd is simpel. Er draait niets wat niet hoeft te draaien. Elk gat staat opgeschreven waar de volgende het vindt, ook de twee in de build, en een daarvan is al gedicht. Dat heeft niets met slim zijn te maken. Een standaard is wat je volhoudt als niemand controleert, en niemand controleert een blog tot de dag dat hij de code van iemand anders begint te serveren.
Dus bouw het één keer, controleer wat je hebt gebouwd, en serveer het dood. Wat er dan nog aan te vallen valt, staat op jouw machine, waar het hoort.
PHP — News archive 2026, 21st October — “We consider the risk highest for mod_php, with a demonstrated exploit where an unauthenticated request can trigger the attack before any application code runs”. ↩︎
WordPress 4.7 release notes — “WordPress 4.7 comes with REST API endpoints for posts, comments, terms, users, meta, and settings.” ↩︎
WordPress REST API Handbook — Frequently Asked Questions — “You should not disable the REST API; doing so will break WordPress Admin functionality that depends on the API being active.” ↩︎
WordPress 3.5 release notes — “Enable XML-RPC by default, remove Writing Settings option”. ↩︎
WordPress REST API Handbook — “content that is public on your site is generally publicly accessible via the REST API”. ↩︎
WordPress REST API reference — Users —
GET /wp/v2/users, standaardcontext “view”; name, slug, link en avatar_urls in de view-context. ↩︎WordPress — Brute force attacks — “xmlrpc.php is a frequent brute‑force target”; “Apply rate limits to /wp-login.php and /xmlrpc.php globally.” ↩︎
WordPress Plugin Handbook — Cron — “WP-Cron works by checking, on every page load, a list of scheduled tasks to see what needs to be run.” ↩︎
WordPress — Hardening WordPress — de editor in het Dashboard “is often the first tool an attacker will use if able to login, since it allows code execution.” ↩︎
Patchstack — State of WordPress Security in 2026 — cijfers over 2025: 11.334 nieuwe kwetsbaarheden; 91% in plugins, 9% in thema’s, 6 in core; 46% niet op tijd gefixt voor de bekendmaking; mediaan 5 uur tot massaal misbruik. ↩︎ ↩︎
Cloudflare — Wrangler configuration — “The main key is optional for assets-only Workers.” ↩︎
Cloudflare — Workers static assets — een overeenkomend bestand “will be served” zonder dat er Worker-code wordt aangeroepen; “If no Worker script is present, a 404 Not Found response is returned.” ↩︎
Cloudflare — Static assets billing and limitations — “Requests to static assets are free and unlimited.” ↩︎
Cloudflare — Static assets headers — headers ingesteld via “a plain text file called _headers”. ↩︎
OWASP — Attack Surface Analysis Cheat Sheet — “the sum of all paths for data/commands into and out of the application, and the code that protects these paths”; verklein het door “turning off features and interfaces that aren’t being used”. ↩︎ ↩︎
NCSC — Making compromise difficult, 2.2 Reduce attack surface — “If would-be attackers can’t reach an interface, they can’t attack it.” ↩︎
NCSC — Cyber Essentials: Requirements for IT infrastructure v3.2 — “remove or disable unnecessary software (including applications, system utilities and network services)”. ↩︎
Hugo — Introduction — “Hugo is a static site generator written in Go, optimized for speed and designed for flexibility.” ↩︎
Hugo — Security model — “during development and site building, the hugo executable itself is the runtime environment”; “We have no online production server”; “No arbitrary commands”; een afwijkende checksum in go.sum laat de build falen. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
Hugo — Configure security — “By default, access is restricted”; de standaard-allowlists voor exec, getenv en http. ↩︎ ↩︎ ↩︎ ↩︎
SLSA v1.2 — Threats — build integrity threat: “behavior to an artifact without changing its source code”; dependency threat: “compromising some other artifact that the former depends on at build time”. ↩︎
Hugo v0.167.0 release — 36 assets, waaronder
hugo_0.167.0_checksums.txt(SHA-256); geen bestanden met handtekeningen, SBOM of attestaties. ↩︎ ↩︎