Skip to content

Add CSP #7990

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open

Add CSP #7990

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@
<html lang="en">
<head>
<meta charset="utf-8" />
<!-- PERPETUAL TODO reconsider this option.
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
<!--
This all-blocking CSP is replaced by the vite html-transform plugin.

We have to use the <meta> tag because the Electron app is hosted over file:// and does not support HTTP headers.
Reporting functionality is not supported with <meta> tags so we have to get this right.
-->
<meta http-equiv="Content-Security-Policy" content="default-src 'none';">

<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
Expand Down
1 change: 1 addition & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,7 @@ app.on('window-all-closed', () => {
app.on('ready', (event, data) => {
// Avoid potentially 2 ready fires
if (mainWindow) return

// Create the mainWindow
mainWindow = createWindow()
// Set menu application to null to avoid default electron menu
Expand Down
19 changes: 18 additions & 1 deletion vercel.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,20 @@
"version": 2,
"headers": [
{
"source": "/",
"source": "/(.*)",
"headers": [
{
"key": "Reporting-Endpoints",
"value": "csp-reporting-endpoint=\"https://csp-logger.vercel.app/csp-report\""
},
{
"key": "Content-Security-Policy-Report-Only",
"value": "default-src 'self';style-src 'self' 'unsafe-inline';img-src * blob: 'unsafe-inline';connect-src 'self' https://plausible.corp.zoo.dev https://api.zoo.dev wss://api.zoo.dev https://api.dev.zoo.dev wss://api.dev.zoo.dev;object-src 'none';frame-ancestors 'none';script-src 'self' 'wasm-unsafe-eval' https://plausible.corp.zoo.dev/js/script.tagged-events.js;report-uri https://csp-logger.vercel.app/csp-report;report-to csp-reporting-endpoint;" }
}
]
},
{
"source": "/(.*)",
"missing": [
{
"type": "host",
Expand All @@ -13,6 +26,10 @@
{
"key": "X-Robots-Tag",
"value": "noindex"
},
{
"key": "Content-Security-Policy-Report-Only",
"value": "default-src 'self';style-src 'self' 'unsafe-inline';img-src * blob: 'unsafe-inline';connect-src 'self' https://plausible.corp.zoo.dev https://api.zoo.dev wss://api.zoo.dev https://api.dev.zoo.dev wss://api.dev.zoo.dev;object-src 'none';frame-ancestors 'none';frame-src 'self' https://vercel.live;script-src 'self' 'wasm-unsafe-eval' https://plausible.corp.zoo.dev/js/script.tagged-events.js https://vercel.live 'unsafe-eval';"
}
]
}
Expand Down
69 changes: 69 additions & 0 deletions vite.base.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,72 @@
},
}
}

export function indexHtmlCsp(enabled: boolean): Plugin {
const csp = [
// By default, only allow same origin.
"default-src 'self'",
// Allow inline styles and styles from the same origin. This is how we use CSS rightnow.
"style-src 'self' 'unsafe-inline'",
// Allow images from any source and inline images. We fetch user profile images from any origin.
"img-src * blob: 'unsafe-inline'",
// Allow WebSocket connections and fetches to our API.
"connect-src 'self' https://plausible.corp.zoo.dev https://api.zoo.dev wss://api.zoo.dev https://api.dev.zoo.dev wss://api.dev.zoo.dev https://api.zoogov.dev wss://api.zoogov.dev",
// Disallow legacy stuff
"object-src 'none'",
]

// Allow scripts from the same origin and from Plausible Analytics. Allow WASM execution.
const cspScriptBase = `script-src 'self' 'wasm-unsafe-eval' https://plausible.corp.zoo.dev/js/script.tagged-events.js`
const vercelCspBase = ["frame-ancestors 'none'"]

const vercelCsp =
csp
.concat(vercelCspBase)
.concat(
// frame ancestors can only be blocked using HTTP headers (see vercel.json)
[
cspScriptBase,
'report-uri https://csp-logger.vercel.app/csp-report',
'report-to csp-reporting-endpoint',
]
)
.join(';') + ';'

console.log(
'Content-Security-Policy for Vercel (prod) (vercel.json): ',
vercelCsp
)

console.log(
'Content-Security-Policy for Vercel (preview) (vercel.json): ',
// vercel.live is used for feedback scripts in preview deployments.
csp
.concat(vercelCspBase)
.concat([
"frame-src 'self' https://vercel.live",
`${cspScriptBase} https://vercel.live 'unsafe-eval'`,
])
.join(';') + ';'
)

return {
name: 'html-transform',
transformIndexHtml(html: string) {
let indexHtmlRegex =
/<meta\shttp-equiv="Content-Security-Policy"\scontent="(.*?)">/
if (!enabled) {
// Web deployments that are deployed to vercel don't need a CSP in the indexHTML.
// They get it through vercel.json.
return html.replace(indexHtmlRegex, '')
} else {
return html.replace(
indexHtmlRegex,
`<meta http-equiv="Content-Security-Policy" content="${
csp.concat([cspScriptBase]).join(';') + ';'
}">`

Check warning on line 181 in vite.base.config.ts

View workflow job for this annotation

GitHub Actions / semgrep-oss/scan

html-in-template-string

This template literal looks like HTML and has interpolated variables. These variables are not HTMLencoded by default. If the variables contain HTML tags these may be interpreted by the browser resulting in crosssite scripting XSS.
)
}
},
}
}
2 changes: 2 additions & 0 deletions vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import version from 'vite-plugin-package-version'
import topLevelAwait from 'vite-plugin-top-level-await'
import viteTsconfigPaths from 'vite-tsconfig-paths'
import { configDefaults, defineConfig } from 'vitest/config'
import { indexHtmlCsp } from './vite.base.config'

export default defineConfig(({ command, mode }) => {
const runMillion = process.env.RUN_MILLION
Expand Down Expand Up @@ -77,6 +78,7 @@ export default defineConfig(({ command, mode }) => {
},
plugins: [
react(),
indexHtmlCsp(mode != process.env.VERCEL && mode === 'production'),
viteTsconfigPaths(),
eslint(),
version(),
Expand Down
3 changes: 2 additions & 1 deletion vite.renderer.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { defineConfig } from 'vite'
import topLevelAwait from 'vite-plugin-top-level-await'
import viteTsconfigPaths from 'vite-tsconfig-paths'

import { pluginExposeRenderer } from './vite.base.config'
import { pluginExposeRenderer, indexHtmlCsp } from './vite.base.config'

// https://vitejs.dev/config
export default defineConfig((env) => {
Expand All @@ -23,6 +23,7 @@ export default defineConfig((env) => {
// Needed for electron-forge (in npm run tron:start)
optimizeDeps: { esbuildOptions: { target: 'es2022' } },
plugins: [
indexHtmlCsp(true),
pluginExposeRenderer(name),
viteTsconfigPaths(),
lezer(),
Expand Down
Loading