CSP, sentry, hooks
This commit is contained in:
parent
d0c5f79ae5
commit
b16c3a04b1
|
@ -0,0 +1,83 @@
|
|||
// https://gist.github.com/acoyfellow/d8e86979c66ebea25e1643594e38be73, Rodney Lab
|
||||
|
||||
import {
|
||||
PUBLIC_SITE_DOMAIN,
|
||||
PUBLIC_SENTRY_KEY,
|
||||
PUBLIC_SENTRY_PROJECT_ID,
|
||||
PUBLIC_SENTRY_ORG_ID,
|
||||
PUBLIC_WORKER_URL
|
||||
} from '$env/static/public'
|
||||
|
||||
export const rootDomain = PUBLIC_SITE_DOMAIN // or your server IP for dev
|
||||
|
||||
const directives = {
|
||||
'base-uri': ["'self'"],
|
||||
'child-src': ["'self'", 'blob:'],
|
||||
// 'connect-src': ["'self'", 'ws://localhost:*'],
|
||||
'connect-src': [
|
||||
"'self'",
|
||||
'ws://localhost:*',
|
||||
'https://*.sentry.io',
|
||||
'https://hcaptcha.com',
|
||||
'https://*.hcaptcha.com',
|
||||
'https://*.cartocdn.com',
|
||||
PUBLIC_SITE_DOMAIN,
|
||||
PUBLIC_WORKER_URL
|
||||
],
|
||||
'img-src': ["'self'", 'data:', 'https://images.unsplash.com'],
|
||||
'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',
|
||||
'https://www.openstreetmap.org',
|
||||
'https://*.cartocdn.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',
|
||||
'https://*.cartocdn.com'
|
||||
],
|
||||
'worker-src': ["'self'", 'blob:'],
|
||||
//report-to can throw "Content-Security-Policy: Couldn’t process unknown directive ‘report-to’", leave it for older browsers.
|
||||
'report-to': ["'csp-endpoint'"],
|
||||
'report-uri': [
|
||||
`https://${PUBLIC_SENTRY_ORG_ID}.ingest.us.sentry.io/api/${PUBLIC_SENTRY_PROJECT_ID}/security/?sentry_key=${PUBLIC_SENTRY_KEY}`
|
||||
]
|
||||
}
|
||||
|
||||
export const csp = Object.entries(directives)
|
||||
.map(([key, arr]) => key + ' ' + arr.join(' '))
|
||||
.join('; ')
|
|
@ -0,0 +1,22 @@
|
|||
import { handleErrorWithSentry, replayIntegration } from '@sentry/sveltekit'
|
||||
import * as Sentry from '@sentry/sveltekit'
|
||||
import { PUBLIC_SENTRY_KEY, PUBLIC_SENTRY_PROJECT_ID, PUBLIC_SENTRY_ORG_ID } from '$env/static/public'
|
||||
|
||||
Sentry.init({
|
||||
dsn: `https://${PUBLIC_SENTRY_KEY}@${PUBLIC_SENTRY_ORG_ID}.ingest.us.sentry.io/${PUBLIC_SENTRY_PROJECT_ID}`,
|
||||
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()
|
|
@ -1,7 +1,54 @@
|
|||
import type { Handle } from '@sveltejs/kit'
|
||||
import { sequence } from '@sveltejs/kit/hooks'
|
||||
import { site } from '$lib/config/site'
|
||||
|
||||
export const handle: Handle = async ({ event, resolve }) =>
|
||||
import { handleErrorWithSentry, sentryHandle } from '@sentry/sveltekit'
|
||||
import * as Sentry from '@sentry/sveltekit'
|
||||
import { PUBLIC_SENTRY_KEY, PUBLIC_SENTRY_PROJECT_ID, PUBLIC_SENTRY_ORG_ID } from '$env/static/public'
|
||||
|
||||
import { csp, rootDomain } from './cspDirectives'
|
||||
|
||||
Sentry.init({
|
||||
dsn: `https://${PUBLIC_SENTRY_KEY}@${PUBLIC_SENTRY_ORG_ID}.ingest.us.sentry.io/${PUBLIC_SENTRY_PROJECT_ID}`,
|
||||
tracesSampleRate: 1.0
|
||||
})
|
||||
|
||||
export const cspHandle: Handle = async ({ event, resolve }) => {
|
||||
if (!csp) {
|
||||
throw new Error('csp is undefined')
|
||||
}
|
||||
const response = await resolve(event)
|
||||
|
||||
// Permission fullscreen necessary for maps fullscreen
|
||||
const headers = {
|
||||
'X-Frame-Options': 'SAMEORIGIN',
|
||||
'Referrer-Policy': 'no-referrer',
|
||||
'Permissions-Policy': `accelerometer=(), autoplay=(), camera=(), document-domain=(self, 'js-profiling'), encrypted-media=(), fullscreen=(self ${rootDomain}), gyroscope=(), interest-cohort=(), magnetometer=(), microphone=(), midi=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), sync-xhr=(), usb=(), xr-spatial-tracking=(), geolocation=()`,
|
||||
'X-Content-Type-Options': 'nosniff',
|
||||
// 'Content-Security-Policy-Report-Only': csp,
|
||||
'Content-Security-Policy': csp,
|
||||
'Strict-Transport-Security': 'max-age=31536000; includeSubDomains; preload',
|
||||
'Expect-CT': `max-age=86400, report-uri="https://${PUBLIC_SENTRY_ORG_ID}.ingest.us.sentry.io/api/${PUBLIC_SENTRY_PROJECT_ID}/security/?sentry_key=${PUBLIC_SENTRY_KEY}"`,
|
||||
'Report-To': `{group: "csp-endpoint", "max_age": 10886400, "endpoints": [{"url": "https://${PUBLIC_SENTRY_ORG_ID}.ingest.us.sentry.io/api/${PUBLIC_SENTRY_PROJECT_ID}/security/?sentry_key=${PUBLIC_SENTRY_KEY}"}]}`
|
||||
}
|
||||
|
||||
Object.entries(headers).forEach(([key, value]) => {
|
||||
response.headers.set(key, value)
|
||||
})
|
||||
return response
|
||||
}
|
||||
|
||||
export const langHandle: Handle = async ({ event, resolve }) =>
|
||||
await resolve(event, {
|
||||
transformPageChunk: ({ html }) => html.replace('<html lang="en">', `<html lang="${site.lang ?? 'en'}">`)
|
||||
})
|
||||
|
||||
// If you have custom handlers, make sure to place them after `sentryHandle()` in the `sequence` function.
|
||||
export const handle: Handle = sequence(sentryHandle(), cspHandle, langHandle)
|
||||
|
||||
// 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/
|
||||
|
|
Loading…
Reference in New Issue