Skip to content

Commit ecd4006

Browse files
committed
Test (report-only) a CSP for stricter XSS protection
1 parent 4422b43 commit ecd4006

File tree

5 files changed

+372
-62
lines changed

5 files changed

+372
-62
lines changed

.github/workflows/ci.yml

+2-1
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,11 @@ jobs:
3535
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
3636
env:
3737
POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }}
38-
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
38+
SENTRY_DSN: ${{ env.SENTRY_DSN }}
3939
SENTRY_ORG: http-toolkit
4040
SENTRY_PROJECT: httptoolkit-ui
4141
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
42+
REPORT_URI: ${{ env.REPORT_URI }}
4243
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # To pull server without rate limit issues in CI
4344

4445
- uses: actions/upload-artifact@v4

Caddyfile

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
@get method GET
1515
header @get Cache-Control "public, max-age=60, s-maxage=3600, stale-while-revalidate=600, stale-if-error=86400"
1616

17-
header Content-Security-Policy "frame-ancestors 'none'"
1817
header Referrer-Policy "strict-origin"
19-
2018
header X-Clacks-Overhead "GNU Terry Pratchett" # https://xclacksoverhead.org
19+
20+
import /site/csp.caddyfile # Generated by webpack
2121
}

automation/webpack.prod.ts

+65-4
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import * as path from 'path';
22
import merge from "webpack-merge";
3+
import { RawSource } from 'webpack-sources';
34
import * as SentryPlugin from '@sentry/webpack-plugin';
45

56
import { InjectManifest } from 'workbox-webpack-plugin';
67
import * as ssri from "ssri";
7-
88
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
9+
import CspHtmlWebpackPlugin from 'csp-html-webpack-plugin';
910

1011
import common from "./webpack.common";
1112

@@ -16,6 +17,14 @@ console.log(shouldPublishSentryRelease
1617
: "Sentry source map upload disabled - no token set"
1718
);
1819

20+
const CSP_REPORT_URL = process.env.REPORT_URI && process.env.UI_VERSION
21+
? `${process.env.REPORT_URI}&sentry_release=${process.env.UI_VERSION}`
22+
: false;
23+
console.log(CSP_REPORT_URL
24+
? "CSP reporting enabled"
25+
: `CSP reporting skipped (uri: ${process.env.REPORT_URI}. version: ${process.env.UI_VERSION})`
26+
);
27+
1928
export default merge(common, {
2029
mode: "production",
2130

@@ -68,7 +77,13 @@ export default merge(common, {
6877
'services',
6978
'ui-update-worker.ts'
7079
),
71-
exclude: ['google-fonts', /^api\//, 'ui-update-worker.js', /.map$/],
80+
exclude: [
81+
'google-fonts',
82+
/^api\//,
83+
'ui-update-worker.js',
84+
/\.map$/,
85+
/\.caddyfile$/
86+
],
7287
maximumFileSizeToCacheInBytes: 100 * 1024 * 1024,
7388
manifestTransforms: [
7489
(originalManifest: any, compilation: any) => {
@@ -111,6 +126,52 @@ export default merge(common, {
111126
analyzerMode: 'static',
112127
openAnalyzer: false,
113128
excludeAssets: /api\/.*\.json/
114-
})
129+
}),
130+
...(CSP_REPORT_URL
131+
? [
132+
new CspHtmlWebpackPlugin({
133+
'base-uri': "'self'",
134+
'default-src': "'none'",
135+
'object-src': "'none'",
136+
'frame-ancestors': "'none'",
137+
'img-src': ["'self'", 'https://httptoolkit.com', 'data:'],
138+
'font-src': ["'self'"],
139+
'style-src': ["'report-sample'", "'self'", "'unsafe-inline'"],
140+
'script-src': [
141+
"'report-sample'",
142+
"'unsafe-eval'", // For both wasm & real eval() uses
143+
"'self'", 'https://cdn.auth0.com/', 'https://cdn.eu.auth0.com/'
144+
],
145+
'connect-src': [
146+
"'self'", 'http://127.0.0.1:45456', 'http://127.0.0.1:45457', 'ws://127.0.0.1:45456', 'https://*.httptoolkit.tech', 'https://sentry.io', 'data:'
147+
],
148+
'report-uri': CSP_REPORT_URL,
149+
'report-to': 'csp-endpoint'
150+
}, {
151+
enabled: true,
152+
hashEnabled: {
153+
'script-src': true,
154+
'style-src': false
155+
},
156+
nonceEnabled: {
157+
'script-src': false,
158+
'style-src': false
159+
},
160+
// Output CSP into a Caddy config file, that's imported by Caddyfile
161+
processFn: (
162+
builtPolicy: any,
163+
_htmlPluginData: any,
164+
_obj: any,
165+
compilation: any
166+
) => {
167+
const header = `
168+
header Content-Security-Policy-Report-Only "${builtPolicy}"
169+
header Reporting-Endpoints \`csp-endpoint="${CSP_REPORT_URL}"\`
170+
`;
171+
compilation.emitAsset('csp.caddyfile', new RawSource(header));
172+
}
173+
} as any)
174+
]
175+
: [])
115176
]
116-
});
177+
});

0 commit comments

Comments
 (0)