Serve index.html no-cache on every route; check the theme script's CSP hash
`try_files` served the SPA fallback inside `location /`, so `/` and every client route went out without `Cache-Control: no-cache`. The fallback is now the last argument, an internal redirect to the exact no-cache location. `src/csp.check.ts` recomputes the inline theme script's sha256 and fails `bun run check` with the new value when the CSP no longer names it.
This commit is contained in:
@@ -3,7 +3,8 @@
|
||||
#
|
||||
# script-src: the bundle, plus the theme script inline in index.html by its
|
||||
# hash. Edit that script and this hash must change with it (sha256 of the text
|
||||
# between the tags, base64), or the app paints light-then-dark again.
|
||||
# between the tags, base64), or the app paints light-then-dark again;
|
||||
# `bun run check` (src/csp.check.ts) fails with the new value.
|
||||
# style-src 'unsafe-inline': Monaco, Radix's scroll lock and the toasts insert
|
||||
# <style> elements at runtime, which no hash can name.
|
||||
# img/frame-src take any http(s): a camera stream or website widget points
|
||||
|
||||
+4
-1
@@ -42,10 +42,13 @@ server {
|
||||
|
||||
include /etc/nginx/security-headers.conf;
|
||||
|
||||
# The fallback is the last argument, so it is an internal redirect to the
|
||||
# exact location below and `/` and every SPA route get its no-cache too; as
|
||||
# a middle argument it was served from here, without one.
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html index.htm;
|
||||
try_files $uri /index.html =404;
|
||||
try_files $uri /index.html;
|
||||
}
|
||||
|
||||
# Vite puts a content hash in every asset's name, so one can never change
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* The CSP's hash for the theme script, checked against the script.
|
||||
*
|
||||
* ponytail: a script rather than a suite, as with the other `.check.ts` files:
|
||||
*
|
||||
* cd frontend && bun run src/csp.check.ts
|
||||
*
|
||||
* `index.html` runs one inline script, the theme class before first paint, and
|
||||
* `nginx-security-headers.conf` admits it by its sha256. Edit one without the
|
||||
* other and the browser blocks the script, so the app paints light-then-dark
|
||||
* again, with nothing failing anywhere but the console.
|
||||
*/
|
||||
|
||||
import assert from "node:assert/strict"
|
||||
import { createHash } from "node:crypto"
|
||||
import { readFileSync } from "node:fs"
|
||||
|
||||
const read = (path: string) =>
|
||||
readFileSync(new URL(path, import.meta.url), "utf8")
|
||||
|
||||
const inline = [
|
||||
...read("../index.html").matchAll(/<script>([\s\S]*?)<\/script>/g),
|
||||
]
|
||||
assert.equal(inline.length, 1, "index.html has one inline script")
|
||||
|
||||
const hash = createHash("sha256").update(inline[0][1]).digest("base64")
|
||||
const csp = read("../nginx-security-headers.conf")
|
||||
assert.ok(
|
||||
csp.includes(`'sha256-${hash}'`),
|
||||
`the theme script's hash is not in nginx-security-headers.conf; set it to 'sha256-${hash}'`,
|
||||
)
|
||||
|
||||
console.log("csp: ok")
|
||||
Reference in New Issue
Block a user