What genuinely goes away
A static site has no application server executing your code per request, no database behind it and no session state. That removes SQL injection, server-side request forgery, insecure deserialisation, most authentication flaws and the entire class of vulnerabilities introduced by an unpatched runtime.
This is a substantial reduction, and it is the strongest argument for a static site in a security-conscious organisation. It is not, however, the same as having nothing to secure.
The browser is still executing your JavaScript
Client-side code that writes untrusted input into the page is exactly as dangerous on a static site as anywhere else. The untrusted input on a static site usually arrives from one of three places: a query string, a fragment identifier, or a JSON file that somebody can edit through a content workflow.
Rendering content from JSON safely
// Dangerous if article.title ever contains markup.
host.innerHTML = `<h3>${article.title}</h3>`;
// Safe regardless of what the content author typed.
const heading = document.createElement('h3');
heading.textContent = article.title;
host.append(heading);
The second form is slightly more code and removes the possibility entirely. On a content-driven site where non-developers can edit the source data, that is not a theoretical benefit.
There are no secrets on a static site
Everything you deploy is public: HTML, JavaScript, and every JSON file in
/config/ and /content/. There is no such thing as a
configuration file the browser can read but a visitor cannot.
Watch out
If a feature needs an API key, the feature needs a server-side endpoint. A small serverless function that holds the key and proxies the call is the correct pattern. An "obfuscated" key in a JavaScript file is not obfuscated; it is published.
Headers are your main control
With no application layer, response headers are where nearly all of your enforcement lives. On Azure Static Web Apps they are declared in the configuration file and versioned alongside the site, which is exactly where they should be.
A starting policy for a site with no third-party scripts
"globalHeaders": {
"Content-Security-Policy": "default-src 'self'; img-src 'self' data:; style-src 'self'; script-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'",
"Strict-Transport-Security": "max-age=31536000; includeSubDomains",
"X-Content-Type-Options": "nosniff",
"Referrer-Policy": "strict-origin-when-cross-origin",
"Permissions-Policy": "camera=(), microphone=(), geolocation=(), interest-cohort=()"
}
A policy this tight is achievable precisely because the site loads no external fonts, no analytics and no embedded third-party widgets. Every one of those you add requires loosening it, which is a useful way to make the cost of a marketing request visible.
Note
frame-ancestors 'none' supersedes X-Frame-Options in modern
browsers. Sending both is harmless and helps older clients.
The pipeline is the real target
If there is no server to compromise, an attacker will go after the thing that writes the files. A deployment pipeline with a long-lived credential and write access to production is the most valuable asset in a static architecture.
- Protect the branch that triggers deployment; require review.
- Require a review specifically for changes to the workflow file itself.
- Use federated credentials rather than stored deployment secrets.
- Pin third-party actions to a commit, not a moving tag.
- Alert on deployments that did not originate from a merge.
Domains and certificates
Two failure modes recur. The first is an expired certificate on a subdomain nobody owns any more. The second is a dangling DNS record pointing at a decommissioned resource, which allows somebody else to claim the name and serve content from your domain.
Keep an inventory of DNS records with an owner against each, and remove the record at the same time as the resource, not afterwards.
A short review checklist
| Area | Check |
|---|---|
| Transport | HTTPS enforced, HSTS set, certificate renewal automated |
| Headers | CSP without unsafe-inline, nosniff, referrer policy |
| Content rendering | No innerHTML with data from JSON or the URL |
| Published files | No keys, no internal hostnames, no draft content |
| Pipeline | Protected branch, federated credential, pinned actions |
| DNS | No dangling records; owner recorded for each entry |
| 404 handling | Real 404 status, not a 200 fallback to the homepage |
Seven lines is a short list, and that is the point. The value of a static architecture is that the security work becomes small enough to actually finish.