Skip to content

Commit 0037d2c

Browse files
author
Eric Wheeler
committed
refactor: centralize CSP generation
Refactor Content Security Policy (CSP) handling to use a centralized approach: - Move CSP generation logic to shared/csp.ts - Use structured CSP directives with base/dev/prod variants - Implement utility functions for source/nonce injection - Update ClineProvider to use new CSP generation Signed-off-by: Eric Wheeler <[email protected]>
1 parent be8527c commit 0037d2c

File tree

2 files changed

+70
-22
lines changed

2 files changed

+70
-22
lines changed

src/core/webview/ClineProvider.ts

Lines changed: 16 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ import { getLmStudioModels } from "../../api/providers/lmstudio"
7373
import { ACTION_NAMES } from "../CodeActionProvider"
7474
import { Cline, ClineOptions } from "../Cline"
7575
import { openMention } from "../mentions"
76+
import { cspGenerate } from "../../shared/csp"
7677
import { getNonce } from "./getNonce"
7778
import { getUri } from "./getUri"
7879
import { telemetryService } from "../../services/telemetry/TelemetryService"
@@ -588,6 +589,7 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
588589
}
589590

590591
const nonce = getNonce()
592+
const csp = cspGenerate(webview, nonce, true)
591593

592594
const stylesUri = getUri(webview, this.contextProxy.extensionUri, [
593595
"webview-ui",
@@ -619,26 +621,17 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
619621
</script>
620622
`
621623

622-
const csp = [
623-
"default-src 'none'",
624-
`font-src ${webview.cspSource}`,
625-
`style-src ${webview.cspSource} 'unsafe-inline' https://* http://${localServerUrl} http://0.0.0.0:${localPort}`,
626-
`img-src ${webview.cspSource} data:`,
627-
`script-src 'unsafe-eval' ${webview.cspSource} https://* https://*.posthog.com http://${localServerUrl} http://0.0.0.0:${localPort} 'nonce-${nonce}'`,
628-
`connect-src https://* https://*.posthog.com ws://${localServerUrl} ws://0.0.0.0:${localPort} http://${localServerUrl} http://0.0.0.0:${localPort}`,
629-
]
630-
631624
return /*html*/ `
632625
<!DOCTYPE html>
633626
<html lang="en">
634627
<head>
635628
<meta charset="utf-8">
636629
<meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no">
637-
<meta http-equiv="Content-Security-Policy" content="${csp.join("; ")}">
630+
<meta http-equiv="Content-Security-Policy" content="${csp}">
638631
<link rel="stylesheet" type="text/css" href="${stylesUri}">
639632
<link href="${codiconsUri}" rel="stylesheet" />
640633
<script nonce="${nonce}">
641-
window.IMAGES_BASE_URI = "${imagesUri}"
634+
window.IMAGES_BASE_URI = "${imagesUri}"
642635
</script>
643636
<title>Roo Code</title>
644637
</head>
@@ -710,21 +703,22 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
710703
in meta tag we add nonce attribute: A cryptographic nonce (only used once) to allow scripts. The server must generate a unique nonce value each time it transmits a policy. It is critical to provide a nonce that cannot be guessed as bypassing a resource's policy is otherwise trivial.
711704
*/
712705
const nonce = getNonce()
706+
const csp = cspGenerate(webview, nonce, false)
713707

714708
// Tip: Install the es6-string-html VS Code extension to enable code highlighting below
715709
return /*html*/ `
716-
<!DOCTYPE html>
717-
<html lang="en">
718-
<head>
719-
<meta charset="utf-8">
720-
<meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no">
721-
<meta name="theme-color" content="#000000">
722-
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; font-src ${webview.cspSource}; style-src ${webview.cspSource} 'unsafe-inline'; img-src ${webview.cspSource} data:; script-src 'nonce-${nonce}' https://us-assets.i.posthog.com; connect-src https://openrouter.ai https://us.i.posthog.com https://us-assets.i.posthog.com;">
710+
<!DOCTYPE html>
711+
<html lang="en">
712+
<head>
713+
<meta charset="utf-8">
714+
<meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no">
715+
<meta name="theme-color" content="#000000">
716+
<meta http-equiv="Content-Security-Policy" content="${csp}">
723717
<link rel="stylesheet" type="text/css" href="${stylesUri}">
724-
<link href="${codiconsUri}" rel="stylesheet" />
725-
<script nonce="${nonce}">
726-
window.IMAGES_BASE_URI = "${imagesUri}"
727-
</script>
718+
<link href="${codiconsUri}" rel="stylesheet" />
719+
<script nonce="${nonce}">
720+
window.IMAGES_BASE_URI = "${imagesUri}"
721+
</script>
728722
<title>Roo Code</title>
729723
</head>
730724
<body>

src/shared/csp.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
type CSPDirectives = {
2+
[key: string]: string[]
3+
}
4+
5+
// Common directives shared between dev and prod. Make changes here when possible
6+
// to avoid duplication and facilitate equivalent testing environments.
7+
const cspBaseDirectives: CSPDirectives = {
8+
"default-src": ["'none'"],
9+
"img-src": ["data:"],
10+
"style-src": ["'unsafe-inline'"],
11+
"connect-src": ["https://openrouter.ai", "https://us.i.posthog.com", "https://us-assets.i.posthog.com"],
12+
}
13+
14+
// Production-only directives
15+
function cspProdDirectives(webview: { cspSource: string }, nonce: string): CSPDirectives {
16+
return {
17+
"default-src": ["'none'"],
18+
"font-src": [webview.cspSource],
19+
"style-src": [webview.cspSource, "'unsafe-inline'"],
20+
"img-src": [webview.cspSource, "data:"],
21+
"script-src": [`'nonce-${nonce}'`, "https://us-assets.i.posthog.com"],
22+
"connect-src": ["https://openrouter.ai", "https://us.i.posthog.com", "https://us-assets.i.posthog.com"],
23+
}
24+
}
25+
26+
// Development-only directives
27+
function cspDevDirectives(webview: { cspSource: string }, nonce: string): CSPDirectives {
28+
return {
29+
"default-src": ["'none'"],
30+
"font-src": [webview.cspSource],
31+
"style-src": [webview.cspSource, "'unsafe-inline'", "https://*", "http://localhost:*"],
32+
"img-src": [webview.cspSource, "data:"],
33+
"script-src": [
34+
"'unsafe-eval'",
35+
webview.cspSource,
36+
"https://*",
37+
"https://*.posthog.com",
38+
"http://localhost:*",
39+
`'nonce-${nonce}'`,
40+
],
41+
"connect-src": ["https://*", "https://*.posthog.com", "ws://localhost:*", "http://localhost:*"],
42+
}
43+
}
44+
45+
/**
46+
* Generates a Content Security Policy string based on environment and parameters
47+
*/
48+
export function cspGenerate(webview: { cspSource: string }, nonce: string, isDevelopment: boolean): string {
49+
const directives = isDevelopment ? cspDevDirectives(webview, nonce) : cspProdDirectives(webview, nonce)
50+
51+
return Object.entries(directives)
52+
.map(([directive, sources]) => `${directive} ${sources.join(" ")}`)
53+
.join("; ")
54+
}

0 commit comments

Comments
 (0)