test(vscode): extract CSP string building and add policy directive tests

This commit is contained in:
Igor Šćekić
2026-02-20 13:09:52 +01:00
parent cd36bfa973
commit 568d21a816
3 changed files with 111 additions and 12 deletions
+2 -12
View File
@@ -1,5 +1,6 @@
import * as crypto from "crypto"
import * as vscode from "vscode"
import { buildCspString } from "./webview-html-utils"
export function getNonce(): string {
return crypto.randomBytes(16).toString("hex")
@@ -17,18 +18,7 @@ export function buildWebviewHtml(
},
): string {
const nonce = getNonce()
const connectSrc = opts.port
? `http://127.0.0.1:${opts.port} http://localhost:${opts.port} ws://127.0.0.1:${opts.port} ws://localhost:${opts.port}`
: "http://127.0.0.1:* http://localhost:* ws://127.0.0.1:* ws://localhost:*"
const csp = [
"default-src 'none'",
`style-src 'unsafe-inline' ${webview.cspSource}`,
`script-src 'nonce-${nonce}' 'wasm-unsafe-eval'`,
`font-src ${webview.cspSource}`,
`connect-src ${connectSrc}`,
`img-src ${webview.cspSource} data: https:`,
].join("; ")
const csp = buildCspString(webview.cspSource, nonce, opts.port)
return `<!DOCTYPE html>
<html lang="en" data-theme="kilo-vscode">
@@ -0,0 +1,34 @@
/**
* Build the Content-Security-Policy connect-src directive value.
* If a port is specified, restricts connections to that port.
* Otherwise, allows any localhost/127.0.0.1 port.
*/
export function buildConnectSrc(port?: number): string {
if (port) {
return `http://127.0.0.1:${port} http://localhost:${port} ws://127.0.0.1:${port} ws://localhost:${port}`
}
return "http://127.0.0.1:* http://localhost:* ws://127.0.0.1:* ws://localhost:*"
}
/**
* Join an array of CSP directives into a policy string.
*/
export function joinCspDirectives(directives: string[]): string {
return directives.join("; ")
}
/**
* Build the full CSP policy string for a webview.
*/
export function buildCspString(cspSource: string, nonce: string, port?: number): string {
const connectSrc = buildConnectSrc(port)
const directives = [
"default-src 'none'",
`style-src 'unsafe-inline' ${cspSource}`,
`script-src 'nonce-${nonce}' 'wasm-unsafe-eval'`,
`font-src ${cspSource}`,
`connect-src ${connectSrc}`,
`img-src ${cspSource} data: https:`,
]
return joinCspDirectives(directives)
}
@@ -0,0 +1,75 @@
import { describe, it, expect } from "bun:test"
import { buildConnectSrc, buildCspString } from "../../src/webview-html-utils"
describe("buildConnectSrc", () => {
it("uses wildcard ports when no port specified", () => {
const result = buildConnectSrc()
expect(result).toContain("http://127.0.0.1:*")
expect(result).toContain("http://localhost:*")
expect(result).toContain("ws://127.0.0.1:*")
expect(result).toContain("ws://localhost:*")
})
it("restricts to specific port when port provided", () => {
const result = buildConnectSrc(3000)
expect(result).toContain("http://127.0.0.1:3000")
expect(result).toContain("http://localhost:3000")
expect(result).toContain("ws://127.0.0.1:3000")
expect(result).toContain("ws://localhost:3000")
})
it("does not include wildcard when port is provided", () => {
const result = buildConnectSrc(3000)
expect(result).not.toContain(":*")
})
it("uses the exact port number", () => {
expect(buildConnectSrc(54321)).toContain(":54321")
})
})
describe("buildCspString", () => {
const cspSource = "vscode-resource://test"
const nonce = "abc123"
it("includes default-src 'none'", () => {
expect(buildCspString(cspSource, nonce)).toContain("default-src 'none'")
})
it("includes nonce in script-src", () => {
const result = buildCspString(cspSource, nonce)
expect(result).toContain(`'nonce-${nonce}'`)
expect(result).toContain("'wasm-unsafe-eval'")
})
it("includes cspSource in style-src and font-src", () => {
const result = buildCspString(cspSource, nonce)
expect(result).toContain(`style-src 'unsafe-inline' ${cspSource}`)
expect(result).toContain(`font-src ${cspSource}`)
})
it("includes cspSource and https: in img-src", () => {
const result = buildCspString(cspSource, nonce)
expect(result).toContain("img-src")
expect(result).toContain(cspSource)
expect(result).toContain("https:")
expect(result).toContain("data:")
})
it("uses wildcard connect-src when no port provided", () => {
const result = buildCspString(cspSource, nonce)
expect(result).toContain("http://127.0.0.1:*")
})
it("uses specific port in connect-src when port provided", () => {
const result = buildCspString(cspSource, nonce, 9000)
expect(result).toContain("http://127.0.0.1:9000")
expect(result).not.toContain(":*")
})
it("joins directives with semicolons", () => {
const result = buildCspString(cspSource, nonce)
const parts = result.split(";")
expect(parts.length).toBeGreaterThanOrEqual(5)
})
})