Skip to content

Commit d8a8f18

Browse files
dani-polaniclaude
andcommitted
revert: remove CSP — analytics broken, reverting to investigate
Reverts commits 06de673, c56a9d2, 2d76e3c (CSP implementation and enforcing flip). Restores inline GA bootstrap in app.html and removes kit.csp config. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 2d76e3c commit d8a8f18

6 files changed

Lines changed: 18 additions & 87 deletions

File tree

bitext/server.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,8 @@ const port = Number(process.env.PORT || 3000);
1010
const shutdownTimeout = Number(process.env.SHUTDOWN_TIMEOUT || 30) * 1000;
1111

1212
// HSTS is safe because the site is HTTPS-only (Railway terminates TLS, redirects HTTP).
13-
// Content-Security-Policy is set per-page by SvelteKit (kit.csp in svelte.config.js), which can
14-
// add the required nonce/hash to its own scripts. These headers cover everything else (including
15-
// prerendered pages and static assets, which the SvelteKit `handle` hook does not reach).
13+
// No CSP yet: the app loads Google Fonts, GA, Tally, and a DigitalOcean CDN, so a correct
14+
// policy needs its own change.
1615
const SECURITY_HEADERS = {
1716
'Strict-Transport-Security': 'max-age=31536000; includeSubDomains',
1817
'X-Content-Type-Options': 'nosniff',

bitext/src/app.html

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,18 @@
66
<meta name="theme-color" content="#1e293b" />
77
<meta name="format-detection" content="telephone=no" />
88
<!--
9-
No inline scripts here on purpose: the GA gtag shim is bootstrapped from bundled JS
10-
(deferThirdPartyScripts in src/lib/analytics/defer-third-party.ts) so the CSP can keep
11-
script-src free of 'unsafe-inline'. gtag.js and the Tally widget are lazy-loaded once
12-
the page is interactive to keep third-party JS off the critical path.
9+
Google Analytics (gtag.js): only the tiny shim runs at load — gtag() calls queue in
10+
dataLayer. The gtag.js library AND the Tally widget are lazy-loaded once the page is
11+
interactive (see +layout.svelte) to keep third-party JS off the critical path.
1312
-->
13+
<script>
14+
window.dataLayer = window.dataLayer || [];
15+
function gtag() {
16+
dataLayer.push(arguments);
17+
}
18+
gtag('js', new Date());
19+
gtag('config', 'G-6Z5775NY39');
20+
</script>
1421
%sveltekit.head%
1522
</head>
1623
<body class="light" data-sveltekit-preload-data="hover">

bitext/src/lib/analytics/defer-third-party.ts

Lines changed: 4 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,13 @@
11
import { GA_MEASUREMENT_ID } from '$lib/brand.js';
22

3-
let bootstrapped = false;
4-
5-
/**
6-
* Define the GA `gtag` shim and queue the initial config. Runs immediately and makes no network
7-
* request: gtag() calls accumulate in `dataLayer` and flush once gtag.js loads. Kept out of
8-
* app.html on purpose, so the page ships no inline script and the CSP can omit
9-
* script-src 'unsafe-inline'.
10-
*/
11-
function bootstrapGtag() {
12-
if (bootstrapped) return;
13-
bootstrapped = true;
14-
const w = window as typeof window & {
15-
dataLayer: unknown[];
16-
gtag: (...args: unknown[]) => void;
17-
};
18-
w.dataLayer = w.dataLayer || [];
19-
w.gtag = function gtag(...args: unknown[]) {
20-
w.dataLayer.push(args);
21-
};
22-
w.gtag('js', new Date());
23-
w.gtag('config', GA_MEASUREMENT_ID);
24-
}
25-
263
/**
274
* Load third-party scripts (GA gtag.js, Tally widget) after the page is interactive instead of
28-
* during initial load, to keep their JS off the critical path (helps TBT/INP). The `gtag` shim is
29-
* set up synchronously here so page-view config is queued right away and SPA navigations can call
30-
* `window.gtag`; the heavy libraries load on the first user interaction or when the main thread
31-
* goes idle, whichever comes first. Returns a cleanup function.
5+
* during initial load, to keep their JS off the critical path (helps TBT/INP). gtag() calls made
6+
* before the library loads queue in `dataLayer` and flush once it arrives; Tally binds its
7+
* `data-tally-open` buttons on load. Triggers on the first user interaction or when the main
8+
* thread goes idle, whichever comes first. Returns a cleanup function.
329
*/
3310
export function deferThirdPartyScripts(): () => void {
34-
bootstrapGtag();
35-
3611
const sources = [
3712
`https://www.googletagmanager.com/gtag/js?id=${GA_MEASUREMENT_ID}`,
3813
'https://tally.so/widgets/embed.js'

bitext/src/routes/+layout.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
return deferThirdPartyScripts();
3939
});
4040
41-
/** SPA navigations: initial `enter` is already counted by bootstrapGtag (gtag('config')) */
41+
/** SPA navigations: initial `enter` is already counted by the snippet in app.html */
4242
afterNavigate(({ to, type }) => {
4343
if (!browser || type === 'enter' || !to) return;
4444
const g = (window as Window & { gtag?: (...args: unknown[]) => void }).gtag;

bitext/src/routes/api/csp-report/+server.ts

Lines changed: 0 additions & 10 deletions
This file was deleted.

bitext/svelte.config.js

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,5 @@
11
import adapter from '@sveltejs/adapter-node';
22

3-
/**
4-
* Content-Security-Policy source allowlist. SvelteKit adds a nonce/hash to script-src on its own
5-
* injected scripts (mode 'auto': nonce for SSR, hash for prerendered), so script-src stays free of
6-
* 'unsafe-inline'. style-src keeps 'unsafe-inline' because the preview relies on dynamic inline
7-
* style attributes; SvelteKit skips adding nonces to any directive that already has 'unsafe-inline'.
8-
*/
9-
const cspDirectives = {
10-
'default-src': ["'self'"],
11-
'script-src': ["'self'", 'https://www.googletagmanager.com', 'https://tally.so'],
12-
'style-src': ["'self'", "'unsafe-inline'", 'https://fonts.googleapis.com'],
13-
'font-src': ["'self'", 'data:', 'https://fonts.gstatic.com'],
14-
'img-src': [
15-
"'self'",
16-
'data:',
17-
'blob:',
18-
'https://aligner.fra1.cdn.digitaloceanspaces.com',
19-
'https://www.google-analytics.com'
20-
],
21-
'connect-src': [
22-
"'self'",
23-
'https://www.googletagmanager.com',
24-
'https://*.google-analytics.com',
25-
'https://*.analytics.google.com',
26-
'https://fonts.gstatic.com'
27-
],
28-
'frame-src': ['https://tally.so'],
29-
'frame-ancestors': ["'self'"],
30-
'base-uri': ["'self'"],
31-
'form-action': ["'self'"],
32-
'object-src': ["'none'"],
33-
'report-uri': ['/api/csp-report']
34-
};
35-
363
/** @type {import('@sveltejs/kit').Config} */
374
const config = {
385
compilerOptions: {
@@ -44,13 +11,6 @@ const config = {
4411
/** Canonical / og:url on prerendered routes (examples, privacy). Without this, build bakes `http://sveltekit-prerender/...`. */
4512
prerender: {
4613
origin: 'https://aligner.tinygods.dev'
47-
},
48-
// Report-only first: observe violations via /api/csp-report before enforcing. Flip
49-
// `reportOnly` to `directives` once QA is clean (see the CSP task).
50-
csp: {
51-
mode: 'auto',
52-
// reportOnly: cspDirectives
53-
directives: cspDirectives
5414
}
5515
}
5616
};

0 commit comments

Comments
 (0)