Repository: sveltejs/kit (js)

Issue:
CSP config: Missing quotes in `'script'` when using `require-trusted-types-for: ['script']`
### Describe the bug

Single-quotes are missing in the header for the CSP directive `require-trusted-types-for`.

When configuring CSP for `require-trusted-types-for: ['script']`, the output looks like `require-trusted-types-for script`. It is thus missing single quotes which seems to be required according to the MDN: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/require-trusted-types-for

I believe this could be because "script" is not included in the `quoted` Set here: https://github.com/sveltejs/kit/blob/c60e957142f7424342772500de2339475dfe7f19/packages/kit/src/runtime/server/page/csp.js#L164-L168 but I'm not familiar enough with the codebase to say so with certainty, and I'm also not sure if simply adding "script" to the `quoted` list is going to work -- or if it's going to cause other issues.

### Reproduction

Reprodution: https://stackblitz.com/edit/sveltejs-kit-template-default-2q4dhx?file=svelte.config.js

1. Add `require-trusted-types-for: ['script']` directive to CSP config
2. Reload page and verify content-security-policy response header shows `require-trusted-types-for script` (missing single quotes in script)

### Logs

_No response_

### System Info

```Shell
System:
    OS: Linux 5.15 Ubuntu 22.04.1 LTS 22.04.1 LTS (Jammy Jellyfish)
    CPU: (4) x64 Intel(R) Core(TM) i5-6600K CPU @ 3.50GHz
    Memory: 23.35 GB / 31.28 GB
    Container: Yes
    Shell: 5.8.1 - /usr/bin/zsh
  Binaries:
    Node: 16.15.0 - /usr/local/bin/node
    npm: 8.5.5 - /usr/local/bin/npm
  Browsers:
    Brave Browser: 107.1.45.116
    Chromium: 106.0.5249.119
    Firefox: 106.0.2
  npmPackages:
    @sveltejs/adapter-node: ^1.0.0-next.98 => 1.0.0-next.98 
    @sveltejs/kit: ^1.0.0-next.516 => 1.0.0-next.516 
    svelte: ^3.52.0 => 3.52.0 
    vite: ^3.1.8 => 3.1.8 
```
```


### Severity

annoyance

### Additional Information

_No response_

Pull request description:
quote script in CSP directives
closes #7442. this seems like a reasonable fix to me

### Please don't delete this checklist! Before submitting the PR, please make sure you do the following:
- [x] It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs
- [x] This message body should clearly illustrate what problems it solves.
- [x] Ideally, include a test that fails without this PR but passes with it.

### Tests
- [x] Run the tests with `pnpm test` and lint the project with `pnpm lint` and `pnpm check`

### Changesets
- [x] If your PR makes a change that should be noted in one or more packages' changelogs, generate a changeset by running `pnpm changeset` and following the prompts. All changesets should be `patch` until SvelteKit 1.0


Patch:
diff --git a/.changeset/breezy-walls-provide.md b/.changeset/breezy-walls-provide.md
new file mode 100644
index 000000000..28b778bfa
--- /dev/null
+++ b/.changeset/breezy-walls-provide.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+only prerender a given dependency once
diff --git a/.changeset/dry-poets-crash.md b/.changeset/dry-poets-crash.md
new file mode 100644
index 000000000..58eeacb84
--- /dev/null
+++ b/.changeset/dry-poets-crash.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+`updated.check()` type changed to Promise<boolean>
diff --git a/.changeset/stupid-actors-look.md b/.changeset/stupid-actors-look.md
new file mode 100644
index 000000000..b31e235a4
--- /dev/null
+++ b/.changeset/stupid-actors-look.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+[fix] quote 'script' in CSP directives
diff --git a/documentation/docs/20-core-concepts/20-load.md b/documentation/docs/20-core-concepts/20-load.md
index a4f8d9561..834f4101c 100644
--- a/documentation/docs/20-core-concepts/20-load.md
+++ b/documentation/docs/20-core-concepts/20-load.md
@@ -521,7 +521,7 @@ export async function load({ fetch, depends }) {
 ```svelte
 /// file: src/routes/random-number/+page.svelte
 <script>
-	import { invalidateAll } from '$app/navigation';
+	import { invalidate, invalidateAll } from '$app/navigation';
 
 	/** @type {import('./$types').PageData} */
 	export let data;
diff --git a/packages/kit/src/core/prerender/prerender.js b/packages/kit/src/core/prerender/prerender.js
index aa3e732c6..0e6f86306 100644
--- a/packages/kit/src/core/prerender/prerender.js
+++ b/packages/kit/src/core/prerender/prerender.js
@@ -102,10 +102,20 @@ export async function prerender() {
 	/** @type {import('types').SSRManifest} */
 	const manifest = (await import(pathToFileURL(manifest_path).href)).manifest;
 
+	/** @type {Map<string, string>} */
+	const saved = new Map();
+
 	override({
 		building: true,
 		paths: config.paths,
-		read: (file) => readFileSync(join(config.files.assets, file))
+		read: (file) => {
+			// stuff we just wrote
+			const filepath = saved.get(file);
+			if (filepath) return readFileSync(filepath);
+
+			// stuff in `static`
+			return readFileSync(join(config.files.assets, file));
+		}
 	});
 
 	const server = new Server(manifest);
@@ -222,6 +232,7 @@ export async function prerender() {
 			}
 
 			const body = result.body ?? new Uint8Array(await result.response.arrayBuffer());
+
 			save(
 				'dependencies',
 				result.response,
@@ -351,6 +362,9 @@ export async function prerender() {
 		} else if (response_type !== OK) {
 			handle_http_error({ status: response.status, path: decoded, referrer, referenceType });
 		}
+
+		manifest.assets.add(file);
+		saved.set(file, dest);
 	}
 
 	for (const route of manifest._.routes) {
diff --git a/packages/kit/src/runtime/client/utils.js b/packages/kit/src/runtime/client/utils.js
index 8e1a04764..aa14bcce7 100644
--- a/packages/kit/src/runtime/client/utils.js
+++ b/packages/kit/src/runtime/client/utils.js
@@ -211,6 +211,7 @@ export function create_updated_store() {
 	/** @type {NodeJS.Timeout} */
 	let timeout;
 
+	/** @type {() => Promise<boolean>} */
 	async function check() {
 		if (DEV || !BROWSER) return false;
 
diff --git a/packages/kit/src/runtime/server/page/csp.js b/packages/kit/src/runtime/server/page/csp.js
index 282369140..f7b8eedd4 100644
--- a/packages/kit/src/runtime/server/page/csp.js
+++ b/packages/kit/src/runtime/server/page/csp.js
@@ -16,7 +16,8 @@ const quoted = new Set([
 	'none',
 	'strict-dynamic',
 	'report-sample',
-	'wasm-unsafe-eval'
+	'wasm-unsafe-eval',
+	'script'
 ]);
 
 const crypto_pattern = /^(nonce|sha\d\d\d)-/;
diff --git a/packages/kit/types/ambient.d.ts b/packages/kit/types/ambient.d.ts
index efcc1904d..a8bd4cf9f 100644
--- a/packages/kit/types/ambient.d.ts
+++ b/packages/kit/types/ambient.d.ts
@@ -320,7 +320,7 @@ declare module '$app/stores' {
 	/**
 	 *  A readable store whose initial value is `false`. If [`version.pollInterval`](https://kit.svelte.dev/docs/configuration#version) is a non-zero value, SvelteKit will poll for new versions of the app and update the store value to `true` when it detects one. `updated.check()` will force an immediate check, regardless of polling.
 	 */
-	export const updated: Readable<boolean> & { check(): boolean };
+	export const updated: Readable<boolean> & { check(): Promise<boolean> };
 
 	/**
 	 * A function that returns all of the contextual stores. On the server, this must be called during component initialization.


Test patch:
diff --git a/packages/kit/test/apps/options/svelte.config.js b/packages/kit/test/apps/options/svelte.config.js
index bae1cda49..b41b487d6 100644
--- a/packages/kit/test/apps/options/svelte.config.js
+++ b/packages/kit/test/apps/options/svelte.config.js
@@ -5,7 +5,8 @@ const config = {
 		embedded: true,
 		csp: {
 			directives: {
-				'script-src': ['self']
+				'script-src': ['self'],
+				'require-trusted-types-for': ['script']
 			}
 		},
 		files: {
diff --git a/packages/kit/test/apps/options/test/test.js b/packages/kit/test/apps/options/test/test.js
index 588a7cbe5..97fb3bc36 100644
--- a/packages/kit/test/apps/options/test/test.js
+++ b/packages/kit/test/apps/options/test/test.js
@@ -124,6 +124,13 @@ test.describe('CSP', () => {
 
 		await close();
 	});
+
+	test("quotes 'script'", async ({ page }) => {
+		const response = await page.goto(`/path-base`);
+		expect(response.headers()['content-security-policy']).toMatch(
+			/require-trusted-types-for 'script'/
+		);
+	});
 });
 
 test.describe('Custom extensions', () => {
diff --git a/packages/kit/test/prerendering/basics/src/routes/fetch-endpoint/also-not-buffered/+page.js b/packages/kit/test/prerendering/basics/src/routes/fetch-endpoint/also-not-buffered/+page.js
new file mode 100644
index 000000000..de4123c2a
--- /dev/null
+++ b/packages/kit/test/prerendering/basics/src/routes/fetch-endpoint/also-not-buffered/+page.js
@@ -0,0 +1,9 @@
+/** @type {import('@sveltejs/kit').Load} */
+export async function load({ fetch }) {
+	const url = '/fetch-endpoint/not-buffered.json';
+	const res = await fetch(url);
+
+	return {
+		headers: res.headers
+	};
+}
diff --git a/packages/kit/test/prerendering/basics/src/routes/fetch-endpoint/also-not-buffered/+page.svelte b/packages/kit/test/prerendering/basics/src/routes/fetch-endpoint/also-not-buffered/+page.svelte
new file mode 100644
index 000000000..bf73bfc65
--- /dev/null
+++ b/packages/kit/test/prerendering/basics/src/routes/fetch-endpoint/also-not-buffered/+page.svelte
@@ -0,0 +1,6 @@
+<script>
+	/** @type {import('./$types').PageData} */
+	export let data;
+</script>
+
+<h1>content-type: {data.headers.get('content-type')}</h1>
diff --git a/packages/kit/test/prerendering/basics/src/routes/fetch-endpoint/not-buffered.json/+server.js b/packages/kit/test/prerendering/basics/src/routes/fetch-endpoint/not-buffered.json/+server.js
index 82d3cb7b8..692b07ced 100644
--- a/packages/kit/test/prerendering/basics/src/routes/fetch-endpoint/not-buffered.json/+server.js
+++ b/packages/kit/test/prerendering/basics/src/routes/fetch-endpoint/not-buffered.json/+server.js
@@ -1,5 +1,10 @@
 import { json } from '@sveltejs/kit';
 
+let called = false;
+
 export async function GET() {
+	if (called) throw new Error('should only be called once');
+	called = true;
+
 	return json({ answer: 42 });
 }