Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ All notable changes to Upright are listed here. The format follows

## [Unreleased]

### Changed

- The website now sends a strict Content-Security-Policy, HSTS and X-Frame-Options on every
page, with a fresh nonce per page so Cloudflare's bot check can run, and the pages.dev preview
host is marked noindex.

### Added

- An [Upright vs OpenLogi](https://upright.hellobasalt.dev/upright-vs-openlogi) comparison page
Expand Down
14 changes: 14 additions & 0 deletions functions/_middleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Gives each HTML response a fresh CSP nonce. Cloudflare's Bot Fight Mode injects an
// inline script into every page on the zone and copies the nonce from script-src onto
// it, so the script runs without 'unsafe-inline'. Our own scripts load from /assets/,
// and the one-line inline "js" class script is allowed by its hash in _headers.
export async function onRequest({ next }) {
const res = await next();
const csp = res.headers.get("Content-Security-Policy");
if (!csp || !(res.headers.get("Content-Type") || "").includes("text/html")) return res;
const bytes = crypto.getRandomValues(new Uint8Array(16));
const nonce = btoa(String.fromCharCode(...bytes));
const out = new Response(res.body, res);
out.headers.set("Content-Security-Policy", csp.replace("script-src 'self'", `script-src 'self' 'nonce-${nonce}'`));
return out;
}
16 changes: 16 additions & 0 deletions site/_headers
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
X-Frame-Options: DENY
Permissions-Policy: camera=(), microphone=(), geolocation=(), interest-cohort=()
Content-Security-Policy: default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; font-src 'self'; script-src 'self' 'sha256-/x7W7R75k8Roq0WaVRQX9blP4OufE5xbAdzklGxsgpw='; connect-src 'self'; object-src 'none'; base-uri 'self'; form-action 'none'; frame-ancestors 'none'
Strict-Transport-Security: max-age=31536000; includeSubDomains

https://:project.pages.dev/*
X-Robots-Tag: noindex

https://:version.:project.pages.dev/*
X-Robots-Tag: noindex

/assets/*
Cache-Control: public, max-age=3600
5 changes: 5 additions & 0 deletions site/_routes.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"version": 1,
"include": ["/*"],
"exclude": ["/assets/*", "/robots.txt", "/sitemap.xml", "/llms.txt"]
}
Loading