implement sentry error reporting + csp
This commit is contained in:
parent
2b358f92f2
commit
0c51ec1dc0
|
@ -0,0 +1,73 @@
|
||||||
|
import {
|
||||||
|
PUBLIC_DOMAIN,
|
||||||
|
PUBLIC_SENTRY_KEY,
|
||||||
|
PUBLIC_SENTRY_PROJECT_ID,
|
||||||
|
PUBLIC_WORKER_URL,
|
||||||
|
} from '$env/static/public';
|
||||||
|
|
||||||
|
|
||||||
|
const rootDomain = PUBLIC_DOMAIN; // or your server IP for dev
|
||||||
|
|
||||||
|
const directives = {
|
||||||
|
'base-uri': ["'self'"],
|
||||||
|
'child-src': ["'self'"],
|
||||||
|
// 'connect-src': ["'self'", 'ws://localhost:*'],
|
||||||
|
'connect-src': [
|
||||||
|
"'self'",
|
||||||
|
'ws://localhost:*',
|
||||||
|
'https://hcaptcha.com',
|
||||||
|
'https://*.hcaptcha.com',
|
||||||
|
PUBLIC_WORKER_URL,
|
||||||
|
],
|
||||||
|
'img-src': ["'self'", 'data:'],
|
||||||
|
'font-src': ["'self'", 'data:'],
|
||||||
|
'form-action': ["'self'"],
|
||||||
|
'frame-ancestors': ["'self'"],
|
||||||
|
'frame-src': [
|
||||||
|
"'self'",
|
||||||
|
// "https://*.stripe.com",
|
||||||
|
// "https://*.facebook.com",
|
||||||
|
// "https://*.facebook.net",
|
||||||
|
'https://hcaptcha.com',
|
||||||
|
'https://*.hcaptcha.com',
|
||||||
|
],
|
||||||
|
'manifest-src': ["'self'"],
|
||||||
|
'media-src': ["'self'", 'data:'],
|
||||||
|
'object-src': ["'none'"],
|
||||||
|
// 'style-src': ["'self'", "'unsafe-inline'"],
|
||||||
|
'style-src': ["'self'", "'unsafe-inline'", 'https://hcaptcha.com', 'https://*.hcaptcha.com'],
|
||||||
|
'default-src': [
|
||||||
|
"'self'",
|
||||||
|
rootDomain,
|
||||||
|
`ws://${rootDomain}`,
|
||||||
|
// 'https://*.google.com',
|
||||||
|
// 'https://*.googleapis.com',
|
||||||
|
// 'https://*.firebase.com',
|
||||||
|
// 'https://*.gstatic.com',
|
||||||
|
// 'https://*.cloudfunctions.net',
|
||||||
|
// 'https://*.algolia.net',
|
||||||
|
// 'https://*.facebook.com',
|
||||||
|
// 'https://*.facebook.net',
|
||||||
|
// 'https://*.stripe.com',
|
||||||
|
'https://*.sentry.io',
|
||||||
|
],
|
||||||
|
'script-src': [
|
||||||
|
"'self'",
|
||||||
|
"'unsafe-inline'",
|
||||||
|
// 'https://*.stripe.com',
|
||||||
|
// 'https://*.facebook.com',
|
||||||
|
// 'https://*.facebook.net',
|
||||||
|
'https://hcaptcha.com',
|
||||||
|
'https://*.hcaptcha.com',
|
||||||
|
'https://*.sentry.io',
|
||||||
|
// 'https://polyfill.io',
|
||||||
|
],
|
||||||
|
'worker-src': ["'self'"],
|
||||||
|
// remove report-to & report-uri if you do not want to use Sentry reporting
|
||||||
|
'report-to': ["'csp-endpoint'"],
|
||||||
|
'report-uri': [
|
||||||
|
`https://sentry.io/api/${PUBLIC_SENTRY_PROJECT_ID}/security/?sentry_key=${PUBLIC_SENTRY_KEY}`,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default directives;
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { handleErrorWithSentry, replayIntegration } from "@sentry/sveltekit";
|
||||||
|
import * as Sentry from '@sentry/sveltekit';
|
||||||
|
|
||||||
|
Sentry.init({
|
||||||
|
dsn: 'https://962a7ed3891a335e112746e5c6c6bf42@o4505828687478784.ingest.us.sentry.io/4506871754326016',
|
||||||
|
tracesSampleRate: 1.0,
|
||||||
|
|
||||||
|
// This sets the sample rate to be 10%. You may want this to be 100% while
|
||||||
|
// in development and sample at a lower rate in production
|
||||||
|
replaysSessionSampleRate: 0.1,
|
||||||
|
|
||||||
|
// If the entire session is not sampled, use the below sample rate to sample
|
||||||
|
// sessions when an error occurs.
|
||||||
|
replaysOnErrorSampleRate: 1.0,
|
||||||
|
|
||||||
|
// If you don't want to use Session Replay, just remove the line below:
|
||||||
|
integrations: [replayIntegration()],
|
||||||
|
});
|
||||||
|
|
||||||
|
// If you have a custom error handler, pass it to `handleErrorWithSentry`
|
||||||
|
export const handleError = handleErrorWithSentry();
|
|
@ -0,0 +1,54 @@
|
||||||
|
import { sequence } from "@sveltejs/kit/hooks";
|
||||||
|
import { handleErrorWithSentry, sentryHandle } from "@sentry/sveltekit";
|
||||||
|
import { cspDirectives } from './cspDirectives.js'
|
||||||
|
import * as Sentry from '@sentry/sveltekit';
|
||||||
|
import {
|
||||||
|
PUBLIC_SENTRY_KEY,
|
||||||
|
PUBLIC_SENTRY_PROJECT_ID,
|
||||||
|
} from '$env/static/public';
|
||||||
|
|
||||||
|
Sentry.init({
|
||||||
|
dsn: 'https://962a7ed3891a335e112746e5c6c6bf42@o4505828687478784.ingest.us.sentry.io/4506871754326016',
|
||||||
|
tracesSampleRate: 1.0,
|
||||||
|
});
|
||||||
|
|
||||||
|
const csp = Object.entries(cspDirectives)
|
||||||
|
.map(([key, arr]) => key + ' ' + arr.join(' '))
|
||||||
|
.join('; ');
|
||||||
|
|
||||||
|
export const cspHandle = async ({ event, resolve }) => {
|
||||||
|
const response = await resolve(event);
|
||||||
|
response.headers.set('X-Frame-Options', 'SAMEORIGIN');
|
||||||
|
response.headers.set('Referrer-Policy', 'no-referrer');
|
||||||
|
response.headers.set(
|
||||||
|
'Permissions-Policy',
|
||||||
|
'accelerometer=(), autoplay=(), camera=(), document-domain=(), encrypted-media=(), fullscreen=(), gyroscope=(), interest-cohort=(), magnetometer=(), microphone=(), midi=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), sync-xhr=(), usb=(), xr-spatial-tracking=(), geolocation=()',
|
||||||
|
);
|
||||||
|
response.headers.set('X-Content-Type-Options', 'nosniff');
|
||||||
|
/* Switch from Content-Security-Policy-Report-Only to Content-Security-Policy once you are satisifed policy is what you want
|
||||||
|
* on switch comment out the Report-Only line
|
||||||
|
*/
|
||||||
|
response.headers.set('Content-Security-Policy-Report-Only', csp);
|
||||||
|
// response.headers.set('Content-Security-Policy', csp);
|
||||||
|
response.headers.set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
|
||||||
|
response.headers.set(
|
||||||
|
'Expect-CT',
|
||||||
|
`max-age=86400, report-uri="https://sentry.io/api/${PUBLIC_SENTRY_PROJECT_ID}/security/?sentry_key=${PUBLIC_SENTRY_KEY}"`,
|
||||||
|
);
|
||||||
|
response.headers.set(
|
||||||
|
'Report-To',
|
||||||
|
`{group: "csp-endpoint", "max_age": 10886400, "endpoints": [{"url": "https://sentry.io/api/${PUBLIC_SENTRY_PROJECT_ID}/security/?sentry_key=${PUBLIC_SENTRY_KEY}"}]}`,
|
||||||
|
);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// If you have custom handlers, make sure to place them after `sentryHandle()` in the `sequence` function.
|
||||||
|
export const handle = sequence(sentryHandle(), cspHandle());
|
||||||
|
|
||||||
|
// If you have a custom error handler, pass it to `handleErrorWithSentry`
|
||||||
|
export const handleError = handleErrorWithSentry();
|
||||||
|
// https://gist.github.com/acoyfellow/d8e86979c66ebea25e1643594e38be73
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
|
||||||
|
// https://scotthelme.co.uk/content-security-policy-an-introduction/
|
||||||
|
// scanner: https://securityheaders.com/
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { sentrySvelteKit } from "@sentry/sveltekit";
|
||||||
import { purgeCss } from 'vite-plugin-tailwind-purgecss';
|
import { purgeCss } from 'vite-plugin-tailwind-purgecss';
|
||||||
import { sveltekit } from '@sveltejs/kit/vite';
|
import { sveltekit } from '@sveltejs/kit/vite';
|
||||||
import { defineConfig } from 'vitest/config';
|
import { defineConfig } from 'vitest/config';
|
||||||
|
@ -8,15 +9,18 @@ export default defineConfig({
|
||||||
host: 'localhost',
|
host: 'localhost',
|
||||||
port: 5174,
|
port: 5174,
|
||||||
},
|
},
|
||||||
|
envPrefix: "PUBLIC_",
|
||||||
plugins: [
|
plugins: [sentrySvelteKit({
|
||||||
sveltekit(),
|
sourceMapsUploadOptions: {
|
||||||
purgeCss({
|
org: "none-b0c3fadae",
|
||||||
|
project: "javascript-sveltekit"
|
||||||
|
}
|
||||||
|
}), sveltekit(), purgeCss({
|
||||||
safelist: {
|
safelist: {
|
||||||
// any selectors that begin with "hljs-" will not be purged
|
// any selectors that begin with "hljs-" will not be purged
|
||||||
greedy: [/^hljs-/],
|
greedy: [/^hljs-/],
|
||||||
},
|
},
|
||||||
}),],
|
})],
|
||||||
test: {
|
test: {
|
||||||
include: ['src/**/*.{test,spec}.{js,ts}']
|
include: ['src/**/*.{test,spec}.{js,ts}']
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue