It is easy to think a personal site has nothing worth protecting. But it runs code in visitors’ browsers and has a contact form that sends email on my behalf. Here is what I checked and changed.
1. Update what ships to visitors
npm audit split the problems into two groups. The packages that reach the browser had seven known issues, including ways to bypass Angular’s XSS sanitisation. The build tools had many more, but none of that code is shipped.
npm audit fix could not move Angular on its own, because the framework packages must be updated together. Setting all @angular/* packages to the latest patch of the same major version and installing them at once brought the shipped code to zero known vulnerabilities.
2. A Content Security Policy
A CSP tells the browser which scripts, styles and connections the page may use, so an injected script simply will not run. Angular can generate the hard part: with security.autoCsp enabled in angular.json, production builds get a script policy with hashes of the inline scripts and strict-dynamic for the app’s own chunks.
"configurations": {
"production": {
"security": { "autoCsp": true }
}
}On top of that I added a policy limiting styles and fonts to Google Fonts, network requests to the email service, and blocking plugins, frames and form posts elsewhere. I tested it by injecting an inline event handler, an external script and a tracking pixel: all three were blocked.
3. Protecting the contact form
- A honeypot field hidden from people and screen readers; bots that fill it get a fake success.
- Submissions within three seconds of page load are treated as bots.
- A cooldown between sends, and length limits enforced in code as well as HTML.
- Line breaks stripped from name and email so nothing can smuggle extra headers.
- In the email service dashboard: allowed domains and reCAPTCHA, because a public key is visible to anyone.
4. Checks that came back clean
- No innerHTML, eval or sanitiser bypasses anywhere in the app.
- Every link that opens a new tab uses rel="noopener noreferrer".
- No secrets in the repository or its history.
- No source maps in the production build, and no location data in published photos.