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.3Deze site
REST API-routes1330
REST-routes die schrijfacties accepteren620
XML-RPC-methoden800
Bestanden die op de server worden uitgevoerd1.514 PHP0
Bestanden schrijfbaar voor de gebruiker die de site serveert3.785 van 3.785geen: er draait niets dat kan schrijven
Database12 tabellen, app-account met elk privilegegeen
Loginpaginawp-login.php, livegeen; het pad geeft 404
Een POST, PUT of DELETE naar de homepagePHP draait en rendert de pagina tochaan 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.

Wat er op een request antwoordt: WordPress tegenover een statische siteEen WordPress-requestEen request aan deze siteinternetwebserverPHP draait code, bij elk requestWordPress-core: 1.514 PHP-bestandenplus elke plugin en elk thema133 REST-routes, 80 XML-RPC-methodenalle bestanden schrijfbaar voor dezelfde gebruikerdatabase, ALL PRIVILEGESloginpagina, beheer, cron, uploadsinternetCloudflare-edgeer gaat een bestand terug6.343 bestanden, vooraf gebouwder draait geen code van onsPOST, PUT, DELETE: 405niets op de server om naar te schrijvengeen databasegeen loginpagina, geen beheer, geen cron
Wat er op een request antwoordt. Bij WordPress draait elke paginaweergave PHP door core, plugins en een database, op bestanden die dezelfde gebruiker kan herschrijven. Hier wordt een kant-en-klaar bestand teruggestuurd en draait er niets.

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 openstaatWat de eigen documentatie van WordPress zegt
XML-RPC, 80 methodenstandaard 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 gebruikerslijstGET /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 bestandAantalWat het is
.html3.486de pagina’s, in vijf talen
.xml1.576RSS-feeds en sitemaps
.svg1.181diagrammen, covers, iconen
.png46screenshots
.json10de zoekindex en buildstempels
.js4kleine scripts die in jouw browser draaien, niet op de server
.css, fonts en de rest40opmaak, en bestanden bij posts
Uitgevoerd op de server0

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 buitenWordPressStatische site
inloggen en wachtwoorden radenwp-login.php, XML-RPCgeen
beheerinterface/wp-admin/, met een code-editorgeen
programmeerbare APIREST en XML-RPC, met schrijfactiesgeen
servercode bij elk requestcore, thema, elke plugingeen
databaseja, bereikbaar vanuit al het bovenstaandegeen
schrijfbare opslag op de serveruploads, en in het standaardimage allesgeen
geplande taken die bezoekers in gang zettenwp-cron.php bij het laden van een paginageen
code van derden die je gepatcht moet houdenelke plugin en elk thema, op de serverde buildtools, buiten de server
wat een gestolen sessie een aanvaller oplevertde siteniets 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 HugoWat het tegenhoudt
“No arbitrary commands”19een template kan op de machine geen shell aanroepen
exec.allow beperkt tot Sass, Go, Git, Node en PostCSS20alleen benoemde tools, met vaste flags
funcs.getenv beperkt tot ^HUGO_ en ^CI$20templates kunnen je geheimen niet uit de omgeving lezen
fetches op afstand weigeren hosts die een IP-adres of localhost zijn20een template kan je eigen netwerk niet in
modules gecontroleerd tegen go.sum19een 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.

Waar code draait: WordPress, deze site en een offline buildWAAR CODE DRAAITWAT HET PUBLIEK BEREIKTWordPressde publieke server, bij elk requestPHP, plugins, databaseDeze sitebuildcontainer van Cloudflare, per pushalleen statische bestandenOffline gebouwdeen machine van jou, één keeralleen statische bestandende build vertrouwt nog steeds Hugo, het thema en de machine waarop hij draait
Waar code draait in elk model. WordPress draait het op de publieke server, voor elke bezoeker. Deze site draait het één keer per push in de buildcontainer van Cloudflare. Offline gebouwd draait het één keer op een machine die van jou is.

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.

GatWaarom het ertoe doetStatus
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ïnstalleerdeen verwisseld binary zou bouwen wat het maar wildegedicht, 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 mastereen wijziging upstream kan de live site veranderen zonder een commit hieropen, 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.

StapWaarom
installeer de exacte Hugo-release en controleer hem tegen de gepubliceerde SHA-256je weet welk binary je site heeft gebouwd
pin het thema en eventuele modules op commits, en commit go.sumde build verandert alleen als jij hem verandert
bouw in een schone outputmap, zonder --buildDraftswat je publiceert is wat je bedoelde te publiceren
zoek in de output naar alles wat uitvoerbaar is, voordat het de deur uit gaateen statische site hoort niets te bevatten wat de server zou uitvoeren
upload alleen de outputmap, met een credential die alleen kan deployende host krijgt nooit je broncode, je tools of je geschiedenis in handen
serveer het vanaf een host die voor statische bestanden geen code draaiter 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 paginaverzoekenStandaard-WordPress 7.1.3Deze site op nginx
CPU-tijd, drie runs409 s, 530 s, 575 s0,15 s, 0,18 s, 0,19 s
CPU-tijd per pagina41 tot 57 ms15 tot 19 µs
Pagina’s per seconde168 tot 23623.679 tot 28.044
Geheugen onder belasting302 tot 310 MiB, plus 145 MiB voor de database15 MiB
Mislukte verzoeken00

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 geheugenStandaard-WordPressDeze site
Containerimages om het te draaien803 MB WordPress, 334 MB MariaDB166 MB nginx, of helemaal niets bij Cloudflare
De site zelf115 MB webroot, voor een lege site114 MB, vijf talen, elk bericht en elk diagram
Database163 MB aan bestanden met 0,72 MB aan gegevensgeen
Geheugen in rust170 MiB, plus 139 MiB voor de database13 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 inlevertWat deze site in plaats daarvan doet
reactiesgeen; lezers kunnen mailen
zoeken op de servereen JSON-index die met de site wordt gebouwd en in jouw browser wordt doorzocht
formulieren die naar de server postengeen
logins en ledengedeeltesgeen; alles hier is openbaar
bewerken in een browserposts zijn tekstbestanden in een git-repository
direct publiceren zonder buildeen 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.


  1. 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”. ↩︎

  2. WordPress 4.7 release notes — “WordPress 4.7 comes with REST API endpoints for posts, comments, terms, users, meta, and settings.” ↩︎

  3. 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.” ↩︎

  4. WordPress 3.5 release notes — “Enable XML-RPC by default, remove Writing Settings option”. ↩︎

  5. WordPress REST API Handbook — “content that is public on your site is generally publicly accessible via the REST API”. ↩︎

  6. WordPress REST API reference — Users — GET /wp/v2/users, standaardcontext “view”; name, slug, link en avatar_urls in de view-context. ↩︎

  7. WordPress — Brute force attacks — “xmlrpc.php is a frequent brute‑force target”; “Apply rate limits to /wp-login.php and /xmlrpc.php globally.” ↩︎

  8. 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.” ↩︎

  9. 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.” ↩︎

  10. 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. ↩︎ ↩︎

  11. Cloudflare — Wrangler configuration — “The main key is optional for assets-only Workers.” ↩︎

  12. 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.” ↩︎

  13. Cloudflare — Static assets billing and limitations — “Requests to static assets are free and unlimited.” ↩︎

  14. Cloudflare — Static assets headers — headers ingesteld via “a plain text file called _headers”. ↩︎

  15. 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”. ↩︎ ↩︎

  16. NCSC — Making compromise difficult, 2.2 Reduce attack surface — “If would-be attackers can’t reach an interface, they can’t attack it.” ↩︎

  17. NCSC — Cyber Essentials: Requirements for IT infrastructure v3.2 — “remove or disable unnecessary software (including applications, system utilities and network services)”. ↩︎

  18. Hugo — Introduction — “Hugo is a static site generator written in Go, optimized for speed and designed for flexibility.” ↩︎

  19. 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. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  20. Hugo — Configure security — “By default, access is restricted”; de standaard-allowlists voor exec, getenv en http. ↩︎ ↩︎ ↩︎ ↩︎

  21. 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”. ↩︎

  22. Hugo v0.167.0 release — 36 assets, waaronder hugo_0.167.0_checksums.txt (SHA-256); geen bestanden met handtekeningen, SBOM of attestaties. ↩︎ ↩︎