This site is static. Astro compiles it to plain HTML and CSS: no server, no database, no login, and no session worth stealing. The comfortable conclusion is that there is nothing to attack. That conclusion held right up until I added interactive demos.
A few of the project pages carry small vanilla JavaScript islands. One of them, the ChartCraft demo, takes pasted CSV-like text and renders a live bar chart. During a security pass this week I found that each bar’s label was going into the DOM through innerHTML, straight from the pasted text. Paste a row like this and the markup executes:
<img src=x onerror=alert(1)>,5
Calling the severity honestly
This is self-XSS. The only person who can trigger it is the person doing the pasting, in their own browser, on a site that holds no credentials and sets no cookies of value. Nobody’s account gets taken over. If I were triaging someone else’s bug report against this site, I would mark it low severity and mean it.
I fixed it the same day anyway. Three reasons:
- Paste is a social-engineering shape. “Copy this text and paste it into the box” is exactly how support-scam and self-XSS attacks are delivered in the wild. A demo that invites pasting should not also execute what gets pasted.
- Demo code travels. Snippets get lifted into other projects, and the next project might have a session behind it. The habit of escaping at the boundary is worth more than the individual fix.
- It was cheap. The fix replaced
innerHTMLwithcreateElementandtextContent, with bar size and color set through the style API. A few lines, no behavior change for honest input.
What else the pass turned up
The same review found a quieter bug in another demo: a cleanup listener registered on document for Astro’s client-side router events, once per page visit, never removed. Navigate back and forth enough and the handlers pile up. Registering with { once: true } lets each instance clean itself up on the way out.
Neither finding mattered much on its own. Both came from the same discipline: when a page accepts input or holds a listener, someone should read that code the way an attacker or a leak detector would, even when the stakes look like zero.
The principle
Any place user input meets innerHTML is a finding, even when the exploit chain ends in your own tab. Static hosting removes whole classes of risk. It does not remove the ones you ship yourself.