From f5b8aa991883d7a43dc0f7c4e3117136ce94c6e7 Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Mon, 26 Oct 2026 18:36:41 +0000 Subject: [PATCH 1/2] Footer 0.4.0: inline newsletter feedback, nonce + form-action for pit.world - pnpm-lock: @profullstack/footer 0.4.0 for web (spec stays latest) and the installer (^0.4.0). - web: the ring footer gets the request's CSP nonce, so the form's inline script runs under script-src 'nonce-...'; form-action allows https://pit.world for the no-script fallback. connect-src already allows https:. Co-Authored-By: Claude Opus 5.5 --- apps/installer/package.json | 2 +- apps/web/src/components/footer.tsx | 13 ++++++++++++- apps/web/src/middleware.ts | 3 ++- pnpm-lock.yaml | 26 ++++++-------------------- 4 files changed, 21 insertions(+), 23 deletions(-) diff --git a/apps/installer/package.json b/apps/installer/package.json index 4bb19b87..63c49336 100644 --- a/apps/installer/package.json +++ b/apps/installer/package.json @@ -15,7 +15,7 @@ }, "dependencies": { "@hono/node-server": "^2.1.5", - "@profullstack/footer": "^0.2.6", + "@profullstack/footer": "^0.4.0", "hono": "^4.13.14" }, "devDependencies": { diff --git a/apps/web/src/components/footer.tsx b/apps/web/src/components/footer.tsx index 626ee202..c1cb796c 100644 --- a/apps/web/src/components/footer.tsx +++ b/apps/web/src/components/footer.tsx @@ -1,4 +1,5 @@ import Link from 'next/link'; +import { headers } from 'next/headers'; import { Footer as PfsFooter } from '@profullstack/footer/react'; import { useTranslations } from 'next-intl'; import { Logo } from '@/components/Logo'; @@ -61,6 +62,16 @@ const socialLinks = [ }, ]; +/** + * The ring footer with the request's CSP nonce (middleware sets x-nonce), so the + * newsletter form's inline script (@profullstack/footer 0.4.0) runs under + * script-src 'nonce-...'. + */ +async function RingFooter({ tagline }: { tagline: string }) { + const nonce = (await headers()).get('x-nonce') ?? undefined; + return ; +} + export function Footer() { const t = useTranslations('footer'); return ( @@ -96,7 +107,7 @@ export function Footer() { {/* Copyright + Profullstack ring, from @profullstack/footer */} - + ); diff --git a/apps/web/src/middleware.ts b/apps/web/src/middleware.ts index a038fdeb..8c4dba1d 100644 --- a/apps/web/src/middleware.ts +++ b/apps/web/src/middleware.ts @@ -44,7 +44,8 @@ function buildCsp(nonce: string, embeddable: boolean): string { // path keeps the restrictive list. embeddable ? 'frame-ancestors *' : "frame-ancestors 'self' chrome-extension:", "base-uri 'self'", - "form-action 'self'", + // The footer's newsletter form posts to pit.world when script is off. + "form-action 'self' https://pit.world", ].join('; '); } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f0edaa9a..de0925cc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -236,8 +236,8 @@ importers: specifier: ^2.1.5 version: 2.1.5(hono@4.12.25) '@profullstack/footer': - specifier: ^0.2.6 - version: 0.2.6(react@19.3.1) + specifier: ^0.4.0 + version: 0.4.0(react@19.3.1) hono: specifier: ^4.12.4 version: 4.12.25 @@ -390,7 +390,7 @@ importers: version: 1.0.3 '@profullstack/footer': specifier: latest - version: 0.3.1(react@19.3.1) + version: 0.4.0(react@19.3.1) '@profullstack/live-translate': specifier: ^0.3.0 version: 0.3.0 @@ -2949,18 +2949,8 @@ packages: '@profullstack/emailer@1.0.3': resolution: {integrity: sha512-VyAtCcfnSI6LOwJvWogCr3ZNmEsaZw+iuWrqnosLsdcKsbRyCrCmXigMNZJZEjlgYlR+lK//PwNPG+xtCFvWAw==} - '@profullstack/footer@0.2.6': - resolution: {integrity: sha512-BKwbDszAc7e0mYIBiPjgr/5WS8WyDt8IW718s/zW/8pCWOIB1BGwSNs6fmImRSo5kqIjQKUVbXp22jZQ5GEipA==} - engines: {node: '>=18'} - hasBin: true - peerDependencies: - react: '>=18' - peerDependenciesMeta: - react: - optional: true - - '@profullstack/footer@0.3.1': - resolution: {integrity: sha512-uiiCgEuOZAFd4UKpOUIhAseVLTXp+tbGF9kosYDuW2M/+/A3QcWtUCu9lYGSNfOUnvnlscqrLjJC2uYZlJmufQ==} + '@profullstack/footer@0.4.0': + resolution: {integrity: sha512-EJZnMUQmWDi9Gzx6BAjogloeaPFFSSCHtHJuC9dsXiOz8M87hfn6Z+Zx9AMYWk8s6xemPrPlkXKGB3PZugkcEA==} engines: {node: '>=18'} hasBin: true peerDependencies: @@ -12304,11 +12294,7 @@ snapshots: '@profullstack/emailer@1.0.3': {} - '@profullstack/footer@0.2.6(react@19.3.1)': - optionalDependencies: - react: 19.3.1 - - '@profullstack/footer@0.3.1(react@19.3.1)': + '@profullstack/footer@0.4.0(react@19.3.1)': optionalDependencies: react: 19.3.1 From 2f85f8949ea99156d8b2facfdb4c2121fe1afa74 Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Mon, 26 Oct 2026 18:40:02 +0000 Subject: [PATCH 2/2] footer: pass nonce only when set (exactOptionalPropertyTypes) Co-Authored-By: Claude Opus 5.5 --- apps/web/src/components/footer.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/web/src/components/footer.tsx b/apps/web/src/components/footer.tsx index c1cb796c..ff7a6bf4 100644 --- a/apps/web/src/components/footer.tsx +++ b/apps/web/src/components/footer.tsx @@ -68,8 +68,9 @@ const socialLinks = [ * script-src 'nonce-...'. */ async function RingFooter({ tagline }: { tagline: string }) { - const nonce = (await headers()).get('x-nonce') ?? undefined; - return ; + const nonce = (await headers()).get('x-nonce'); + // exactOptionalPropertyTypes: pass nonce only when there is one. + return ; } export function Footer() {