Hoe deze controle werkt
Scanaris ontleedt de HTML van je hoofdpagina en loopt alle elementen langs met een src-, href- of action-attribuut: scripts, afbeeldingen, stylesheets, links, formulieren. Voor elke waarde controleert hij of die wijst naar een host die buiten je eigen machine of netwerk niet hoort te bestaan: localhost, 127.0.0.1, 0.0.0.0, de privé-IP-reeksen 10.x.x.x, 192.168.x.x en 172.16.x.x tot en met 172.31.x.x, en elk domein dat eindigt op .local. Vindt hij er een, dan opent hij een bevinding met lage ernst en de eerste verwijzingen. Alleen de initiële HTML wordt onderzocht; wat JavaScript daarna opbouwt, wordt langs deze weg niet gezien.
Waarom dit belangrijk is
Een verwijzing naar http://localhost:3000/api/... in productie heeft twee gevolgen. Het eerste is functioneel: voor je gebruiker is localhost zijn eigen computer, dus die afbeelding laadt niet, dat formulier wordt nergens heen gestuurd en dat script faalt stilletjes. Het tweede is informatief: je vertelt iedereen dat je API op poort 3000 draait, dat je interne backend api.bedrijf.local heet of dat je netwerk de reeks 10.0.1.x gebruikt. Niets daarvan is op zichzelf een uitbuitbare fout, vandaar de lage ernst, maar het zijn stukjes die een aanvaller bewaart voor later, en ze verraden dat de deployment zonder controle naar buiten ging.
Waarom het gebeurt
Het is bijna altijd een URL die tijdens de ontwikkeling met de hand is getypt en die niemand heeft veranderd. De AI die het project genereerde zette http://localhost:8000 in de fetch van het formulier, of in de src van een testafbeelding, en het werkte lokaal, dus bleef het staan. Andere oorzaken: een ontwikkel-.env die in de productiebuild terechtkwam, een WordPress gemigreerd vanuit een lokale omgeving zonder de site-URL bij te werken, of een formulier-action gekopieerd uit een voorbeeld.
Hoe je het oplost
- Haal de URL's uit de code. De basis van je API hoort in een omgevingsvariabele (
NUXT_PUBLIC_API_BASE,NEXT_PUBLIC_API_URL,VITE_API_URL) met een andere waarde per omgeving. In productie injecteert het platform (Vercel, Netlify, Railway) die bij de build. - Gebruik relatieve paden zodra de bron op je eigen domein leeft:
/api/contactin plaats vanhttp://localhost:3000/api/contact. Ze werken lokaal en in productie hetzelfde. - WordPress: werk de site-URL's bij onder Algemene instellingen en vervang de oude URL's in de database met een tool die serialisatie respecteert.
- Doorzoek de build voordat je deployt. Een CI-stap die faalt als de gecompileerde uitvoer deze hosts bevat, pakt het probleem bij de wortel:
grep -rEl "localhost|127\.0\.0\.1|192\.168\.|\.local\b" dist/ .output/public/ 2>/dev/null
Komt er een bestand uit, dan is er een verwijzing om te corrigeren voordat je publiceert.
Controleer of het opgelost is
curl -s https://jouwdomein.nl | grep -oE '(src|href|action)="https?://[^"]+"' | grep -iE "localhost|127\.0\.0\.1|0\.0\.0\.0|10\.[0-9]+\.|192\.168\.|172\.(1[6-9]|2[0-9]|3[01])\.|\.local\b"
Er mag niets uitkomen. Probeer ook een of twee binnenpagina's, vooral die met formulieren.
Veelgestelde vragen
Het is een testafbeelding die niemand ziet. Maakt het uit?
De browser van elke bezoeker probeert hem van zijn eigen computer te laden, en de HTML blijft je interne host aankondigen. Verander of verwijder hem; het is een minuut werk.
Wordt het gecompileerde JavaScript ook gecontroleerd?
Niet door deze controle, alleen de HTML. Maar een interne URL in de bundel gaat meestal samen met andere dingen die er niet horen; geheimen in JavaScript zoekt sleutels en tokens, en HTML-commentaar zoekt restanten in de HTML zelf. De drie samen dekken wat er meestal overblijft van een ontwikkelomgeving.
Mijn link wijst bewust naar een .local-domein.
.local-domeinen zijn gereserveerd voor lokale netwerken (mDNS) en worden op internet niet opgelost. Wil je een intern hulpmiddel linken, gebruik dan een echt subdomein met authenticatie. Wat je app onbedoeld lekt, met meer voorbeelden, staat in geheimen die je app in de browser lekt.