From 3ced9ca9b578f6c18bb15a07f7b8e95a7a144d92 Mon Sep 17 00:00:00 2001 From: Harlan Wilton Date: Thu, 21 May 2026 15:07:17 +1000 Subject: [PATCH 1/2] fix(security): allow opting out of proxy page token in SSR payload Resolves #783 --- docs/content/docs/1.guides/2.first-party.md | 9 ++++++++ packages/script/src/module.ts | 23 +++++++++++++++++---- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/docs/content/docs/1.guides/2.first-party.md b/docs/content/docs/1.guides/2.first-party.md index ba6a2f98c..c65c0f74f 100644 --- a/docs/content/docs/1.guides/2.first-party.md +++ b/docs/content/docs/1.guides/2.first-party.md @@ -280,6 +280,11 @@ export default defineNuxtConfig({ // Auto-generate and persist a secret to .env in dev mode. // Set to false to disable. autoGenerateSecret: true, + // Emit a per-request proxy page token into the SSR payload so + // client-driven proxy calls authenticate without pre-signed URLs. + // Set to false to keep the token out of the payload (e.g. for a + // stable response etag); client-side calls then need signed URLs. + pageToken: true, } } }) @@ -303,6 +308,10 @@ The module only writes this when running `nuxt dev` with a signed endpoint enabl Page tokens are valid for 1 hour. If a user leaves a tab open longer than that, client-side proxy requests will start returning 403. The page will recover on next navigation or refresh. +**Proxy token changes the response payload on every request** + +The per-request page token is injected into the SSR payload, so the response hash differs each request. If you compute a stable `etag`, set `security.pageToken: false` to keep the token out of the payload. Client-side proxy calls will then need explicitly signed URLs. + #### Static Generation and SPA Mode URL signing requires a server runtime to verify HMAC signatures. Two deployment modes cannot support signing: diff --git a/packages/script/src/module.ts b/packages/script/src/module.ts index f2f073fa0..a71cc940a 100644 --- a/packages/script/src/module.ts +++ b/packages/script/src/module.ts @@ -408,6 +408,17 @@ export interface ModuleOptions { * @default 3600 */ pageTokenMaxAge?: number + /** + * Emit a per-request proxy page token into the SSR payload so client-driven + * proxy calls authenticate without each URL being HMAC-signed up front. + * + * Set to `false` to keep the token out of the payload (e.g. when computing a + * stable response `etag`). Client-side proxy requests that rely on the token + * will then need explicitly signed URLs. + * + * @default true + */ + pageToken?: boolean } /** * Google Static Maps proxy configuration. @@ -1058,10 +1069,14 @@ export default defineNuxtModule({ // Emit a per-request page token during SSR so client-driven proxy // calls (reactive fetches, dynamic image helpers) authenticate via // `_pt` + `_ts` without needing each URL to be HMAC-signed up front. - addPlugin({ - src: await resolvePath('./runtime/plugins/proxy-token.server'), - mode: 'server', - }) + // Opt out via `security.pageToken: false` to keep the token out of the + // SSR payload (e.g. for a stable response etag). + if (config.security?.pageToken !== false) { + addPlugin({ + src: await resolvePath('./runtime/plugins/proxy-token.server'), + mode: 'server', + }) + } } else if (!nuxt.options.dev) { logger.warn( From f5cbad4bb4bb708065b3627035fcca93dc4cfffe Mon Sep 17 00:00:00 2001 From: Harlan Wilton Date: Thu, 21 May 2026 15:07:40 +1000 Subject: [PATCH 2/2] docs: active voice --- docs/content/docs/1.guides/2.first-party.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/docs/1.guides/2.first-party.md b/docs/content/docs/1.guides/2.first-party.md index c65c0f74f..7071d3e86 100644 --- a/docs/content/docs/1.guides/2.first-party.md +++ b/docs/content/docs/1.guides/2.first-party.md @@ -310,7 +310,7 @@ Page tokens are valid for 1 hour. If a user leaves a tab open longer than that, **Proxy token changes the response payload on every request** -The per-request page token is injected into the SSR payload, so the response hash differs each request. If you compute a stable `etag`, set `security.pageToken: false` to keep the token out of the payload. Client-side proxy calls will then need explicitly signed URLs. +The module injects a per-request page token into the SSR payload, so the response hash differs each request. If you compute a stable `etag`, set `security.pageToken: false` to keep the token out of the payload. Client-side proxy calls will then need explicitly signed URLs. #### Static Generation and SPA Mode