From f29429789c4766692d316f8fadda71ccc7e571cc Mon Sep 17 00:00:00 2001 From: Zhi Date: Tue, 9 Jun 2026 20:06:27 +0800 Subject: [PATCH 1/3] docs: recommend --lock-write over manual lockfile editing --- AGENTS.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 52039c9f8f7..c9cd0de2de0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -52,11 +52,15 @@ with workspace members in `packages/*` and `www/`. ### Lockfile quirks -The lockfile contains remote specifiers pointing to `refs/heads/main` (e.g. -`raw.githubusercontent.com/.../refs/heads/main/...`). These hashes go stale when -upstream pushes. When that happens, manually update the hash in `deno.lock` -since `deno cache --reload` cannot fix it (see -https://github.com/denoland/deno/issues/32991). +The lockfile may contain unpinned remote specifiers whose content can change +(known limitation, see https://github.com/denoland/deno/issues/32991). If +`deno install` fails with an integrity check error, run: + + deno install --lock-write + +This tells Deno to accept the new content and update the lockfile. The +`--reload` flag alone is not sufficient here because it re-fetches content but +still validates against the existing lockfile integrity. ## Architecture From 7e4a82f40b237444b98cd3b894fa8ab7730dad9d Mon Sep 17 00:00:00 2001 From: Zhi Date: Tue, 9 Jun 2026 20:28:52 +0800 Subject: [PATCH 2/3] fix: use namespace imports for packages losing default exports on canary --- packages/plugin-tailwindcss/src/mod.ts | 4 ++-- packages/plugin-vite/src/mod.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/plugin-tailwindcss/src/mod.ts b/packages/plugin-tailwindcss/src/mod.ts index 6db172a80b5..54df0a82e5c 100644 --- a/packages/plugin-tailwindcss/src/mod.ts +++ b/packages/plugin-tailwindcss/src/mod.ts @@ -1,5 +1,5 @@ import type { Builder } from "fresh/dev"; -import twPostcss from "@tailwindcss/postcss"; +import * as twPostcss from "@tailwindcss/postcss"; import postcss from "postcss"; import type { TailwindPluginOptions } from "./types.ts"; @@ -11,7 +11,7 @@ export function tailwind( options: TailwindPluginOptions = {}, ): void { const { exclude, ...tailwindOptions } = options; - const instance = postcss(twPostcss({ + const instance = postcss(twPostcss.default({ optimize: builder.config.mode === "production", ...tailwindOptions, })); diff --git a/packages/plugin-vite/src/mod.ts b/packages/plugin-vite/src/mod.ts index c49c244526c..e859c016232 100644 --- a/packages/plugin-vite/src/mod.ts +++ b/packages/plugin-vite/src/mod.ts @@ -6,7 +6,7 @@ import { } from "./utils.ts"; import { deno } from "./plugins/deno.ts"; -import prefresh from "@prefresh/vite"; +import * as prefresh from "@prefresh/vite"; import { serverEntryPlugin } from "./plugins/server_entry.ts"; import { clientEntryPlugin } from "./plugins/client_entry.ts"; import { devServer } from "./plugins/dev_server.ts"; @@ -256,7 +256,7 @@ export function fresh(config?: FreshViteConfig): Plugin[] { ...clientSnapshot(fConfig), buildIdPlugin(), ...devServer(fConfig), - prefresh({ + prefresh.default({ include: [/\.[cm]?[tj]sx?$/], exclude: [/node_modules/, /[\\/]+deno[\\/]+npm[\\/]+/], parserPlugins: [ From cb2d986450964d2b20212ef4baf2d9e422345a90 Mon Sep 17 00:00:00 2001 From: Zhi Date: Tue, 9 Jun 2026 21:11:19 +0800 Subject: [PATCH 3/3] feat: add insecureUnsafeInline option to CSP middleware --- packages/fresh/src/middlewares/csp.ts | 15 ++++++ packages/fresh/src/middlewares/csp_test.tsx | 51 +++++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/packages/fresh/src/middlewares/csp.ts b/packages/fresh/src/middlewares/csp.ts index 0f0b6f558c4..2a37e244b53 100644 --- a/packages/fresh/src/middlewares/csp.ts +++ b/packages/fresh/src/middlewares/csp.ts @@ -19,6 +19,14 @@ export interface CSPOptions { * allow those Fresh-rendered inline elements. */ useNonce?: boolean; + + /** + * If true and `useNonce` is also true, keeps `'unsafe-inline'` + * alongside the nonce in the CSP header instead of removing it. + * This is useful when third-party inline scripts or styles (e.g. + * analytics, reporting widgets) also need to execute on the page. + */ + insecureUnsafeInline?: boolean; } /** @@ -64,6 +72,7 @@ export function csp(options: CSPOptions = {}): Middleware { reportTo, csp = [], useNonce = false, + insecureUnsafeInline = false, } = options; const defaultCsp = [ @@ -124,6 +133,12 @@ export function csp(options: CSPOptions = {}): Middleware { const spaceIdx = d.indexOf(" "); const name = spaceIdx === -1 ? d : d.slice(0, spaceIdx); if (INLINE_DIRECTIVES.has(name) && d.includes("'unsafe-inline'")) { + if (insecureUnsafeInline) { + return d.replaceAll( + "'unsafe-inline'", + `'unsafe-inline' 'nonce-${nonce}'`, + ); + } return d.replaceAll("'unsafe-inline'", `'nonce-${nonce}'`); } return d; diff --git a/packages/fresh/src/middlewares/csp_test.tsx b/packages/fresh/src/middlewares/csp_test.tsx index 3fc50f0fd4c..f9cbf7bc75c 100644 --- a/packages/fresh/src/middlewares/csp_test.tsx +++ b/packages/fresh/src/middlewares/csp_test.tsx @@ -260,3 +260,54 @@ Deno.test("CSP - useNonce replaces unsafe-inline in default-src", async () => { // default-src should have nonce, not unsafe-inline expect(cspHeader).toMatch(/default-src 'self' 'nonce-[a-f0-9]+'/); }); + +Deno.test("CSP - useNonce with insecureUnsafeInline keeps both", async () => { + const app = new App() + .use(csp({ useNonce: true, insecureUnsafeInline: true })) + .get("/", (ctx) => { + return ctx.render( + + + + + +

hello

+ + , + ); + }); + + const server = new FakeServer(app.handler()); + const res = await server.get("/"); + const html = await res.text(); + const cspHeader = res.headers.get("Content-Security-Policy")!; + + // Should contain both unsafe-inline and nonce + expect(cspHeader).toContain("'unsafe-inline'"); + expect(cspHeader).toMatch( + /script-src 'self' 'unsafe-inline' 'nonce-[a-f0-9]+'/, + ); + expect(cspHeader).toMatch( + /style-src 'self' 'unsafe-inline' 'nonce-[a-f0-9]+'/, + ); + + // HTML should still have nonce on the style tag + const nonceMatch = cspHeader.match(/nonce-([a-f0-9]+)/); + expect(nonceMatch).not.toBeNull(); + const nonce = nonceMatch![1]; + expect(html).toContain(`nonce="${nonce}"`); +}); + +Deno.test("CSP - insecureUnsafeInline without useNonce has no effect", async () => { + const handler = new App() + .use(csp({ insecureUnsafeInline: true })) + .get("/", () => new Response("ok")) + .handler(); + + const res = await handler(new Request("https://localhost/")); + const cspHeader = res.headers.get("Content-Security-Policy")!; + + // Without useNonce, insecureUnsafeInline should not change anything + expect(cspHeader).toContain("'unsafe-inline'"); + expect(cspHeader).not.toContain("'nonce-"); +});