diff --git a/.config/docker_example.yml b/.config/docker_example.yml
index f8124bc9d..e51d7e8f5 100644
--- a/.config/docker_example.yml
+++ b/.config/docker_example.yml
@@ -144,3 +144,6 @@ signToActivityPubGet: true
# Upload or download file size limits (bytes)
#maxFileSize: 262144000
+
+# Value of Content-Security-Policy header
+#contentSecurityPolicy: "script-src 'self' 'unsafe-eval' https://challenges.cloudflare.com https://hcaptcha.com https://*.hcaptcha.com https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/ https://www.recaptcha.net/recaptcha/; base-uri 'self'; object-src 'self';"
diff --git a/.config/example.yml b/.config/example.yml
index a19b5d04e..d9f623821 100644
--- a/.config/example.yml
+++ b/.config/example.yml
@@ -145,3 +145,6 @@ signToActivityPubGet: true
# Upload or download file size limits (bytes)
#maxFileSize: 262144000
+
+# Value of Content-Security-Policy header
+#contentSecurityPolicy: "script-src 'self' 'unsafe-eval' https://challenges.cloudflare.com https://hcaptcha.com https://*.hcaptcha.com https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/ https://www.recaptcha.net/recaptcha/; base-uri 'self'; object-src 'self';"
diff --git a/gulpfile.js b/gulpfile.js
index a04ab4c1a..d08b04eae 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -36,12 +36,18 @@ gulp.task('copy:frontend:locales', cb => {
});
gulp.task('build:backend:script', () => {
- return gulp.src(['./packages/backend/src/server/web/boot.js', './packages/backend/src/server/web/bios.js', './packages/backend/src/server/web/cli.js'])
+ const clientManifestExists = fs.existsSync('./built/_vite_/manifest.json');
+ const clientEntry = clientManifestExists ?
+ JSON.parse(fs.readFileSync('./built/_vite_/manifest.json', 'utf-8'))['src/init.ts'].file
+ : 'src/init.ts'
+
+ return gulp.src(['./packages/backend/src/server/web/boot.js', './packages/backend/src/server/web/bios.js', './packages/backend/src/server/web/cli.js', './packages/backend/src/server/web/flush.js'])
.pipe(replace('LANGS', JSON.stringify(Object.keys(locales))))
+ .pipe(replace('CLIENT_ENTRY', JSON.stringify(clientEntry)))
.pipe(terser({
toplevel: true
}))
- .pipe(gulp.dest('./packages/backend/built/server/web/'));
+ .pipe(gulp.dest('./built/_frontend_dist_/'));
});
gulp.task('build:backend:style', () => {
diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts
index aa98ef1d2..4309f67cb 100644
--- a/packages/backend/src/config.ts
+++ b/packages/backend/src/config.ts
@@ -48,6 +48,8 @@ export type Source = {
allowedPrivateNetworks?: string[];
+ contentSecurityPolicy?: string;
+
maxFileSize?: number;
accesslog?: string;
diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts
index c69ee33ea..7f1a43792 100644
--- a/packages/backend/src/server/web/ClientServerService.ts
+++ b/packages/backend/src/server/web/ClientServerService.ts
@@ -172,6 +172,14 @@ export class ClientServerService {
fastify.addHook('onRequest', (request, reply, done) => {
// クリックジャッキング防止のためiFrameの中に入れられないようにする
reply.header('X-Frame-Options', 'DENY');
+
+ // XSSが存在した場合に影響を軽減する
+ // (script-srcにunsafe-inline等を追加すると意味が無くなるので注意)
+ const csp = this.config.contentSecurityPolicy
+ ?? 'script-src \'self\' \'unsafe-eval\' ' +
+ 'https://challenges.cloudflare.com https://hcaptcha.com https://*.hcaptcha.com https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/ https://www.recaptcha.net/recaptcha/; ' +
+ 'base-uri \'self\'; object-src \'self\';';
+ reply.header('Content-Security-Policy', csp);
done();
});
diff --git a/packages/backend/src/server/web/boot.js b/packages/backend/src/server/web/boot.js
index c6cb25e43..4b9565f4c 100644
--- a/packages/backend/src/server/web/boot.js
+++ b/packages/backend/src/server/web/boot.js
@@ -154,7 +154,7 @@
An error has occurred!
-