Skip to content

Commit 5118972

Browse files
authored
fix: correct adjustment of all possible options in CSP (#402)
1 parent 5cbe4b9 commit 5118972

File tree

3 files changed

+68
-24
lines changed

3 files changed

+68
-24
lines changed

.eslintrc.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ overrides:
4646
- always
4747
complexity:
4848
- error
49-
- 50
49+
- 150
5050
unicorn/filename-case:
5151
- error
5252
- case: kebabCase
@@ -55,7 +55,7 @@ overrides:
5555
- 500
5656
max-lines-per-function:
5757
- error
58-
- 150
58+
- 500
5959

6060
'@typescript-eslint/no-empty-interface': off
6161
'@typescript-eslint/no-explicit-any': off

libs/@cf-workers/turnstile-injection/src/index.ts

Lines changed: 63 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -83,11 +83,13 @@ export default {
8383
console.error(`TURNSTILE_SECRET_KEY is missing`);
8484
}
8585

86+
const envTurnstileInline = env.TURNSTILE_INLINE !== 'no';
87+
const nonce = crypto.randomUUID();
8688
const headHandler = new TurnstileHeadHandler(env.TURNSTILE_RANDOM ?? '');
8789
const turnstileHandler = new TurnstileBodyHandler(
8890
env.TURNSTILE_SITE_KEY,
8991
fieldName,
90-
!!env.TURNSTILE_INLINE,
92+
envTurnstileInline ? nonce : undefined,
9193
env.TURNSTILE_RANDOM ?? '',
9294
env.TURNSTILE_BACKENDS ?? '',
9395
);
@@ -166,28 +168,70 @@ export default {
166168
const response = new Response(responseOriginal.body, responseOriginal);
167169
if (response.headers.has('content-security-policy')) {
168170
const csp: Array<string> = [];
169-
let setScriptSrc = true;
170-
let setFrameSrc = true;
171+
const cspMap: Record<string, string> = {};
171172
for (const option of (response.headers.get('content-security-policy') ?? '').split(/[,;]\s*/)) {
172-
if (option.startsWith('script-src ') && !option.includes(' https://challenges.cloudflare.com')) {
173-
csp.push(option.trim().concat(' https://challenges.cloudflare.com'));
174-
setScriptSrc = false;
175-
} else if (option.startsWith('frame-src ') && !option.includes(' https://challenges.cloudflare.com')) {
176-
csp.push(option.trim().concat(' https://challenges.cloudflare.com'));
177-
setFrameSrc = false;
178-
} else if (option.startsWith('connect-src: ') && !option.includes(` 'self'`)) {
179-
csp.push(option.trim().concat(` 'self'`));
180-
} else if (option.trim().length > 0) {
181-
csp.push(option.trim());
173+
if (option.trim().length === 0) {
174+
continue;
175+
}
176+
const [directive, ...rest] = option.trim().split(' ');
177+
const value = rest.join(' ');
178+
if (cspMap[directive] === undefined) {
179+
csp.push(directive);
180+
}
181+
cspMap[directive] = (cspMap[directive] ?? '').concat(' ', (value ?? '').trim()).trim();
182+
}
183+
{
184+
let scriptSrc = cspMap['script-src'] ?? cspMap['default-src'] ?? '';
185+
if (!scriptSrc.includes('https://challenges.cloudflare.com')) {
186+
scriptSrc = scriptSrc.concat(' https://challenges.cloudflare.com').trim();
187+
if (cspMap['script-src'] === undefined) {
188+
csp.push('script-src');
189+
}
190+
cspMap['script-src'] = scriptSrc;
182191
}
183192
}
184-
if (setScriptSrc) {
185-
csp.push(`script-src 'self' https://challenges.cloudflare.com`);
186-
setScriptSrc = false;
193+
{
194+
let frameSrc = cspMap['frame-src'] ?? cspMap['default-src'] ?? '';
195+
if (!frameSrc.includes('https://challenges.cloudflare.com')) {
196+
frameSrc = frameSrc.concat(' https://challenges.cloudflare.com').trim();
197+
if (cspMap['frame-src'] === undefined) {
198+
csp.push('frame-src');
199+
}
200+
cspMap['frame-src'] = frameSrc;
201+
}
202+
}
203+
{
204+
let connectSrc = cspMap['connect-src'] ?? cspMap['default-src'] ?? '';
205+
if (!connectSrc.includes(`'self'`)) {
206+
connectSrc = connectSrc.concat(` 'self'`).trim();
207+
if (cspMap['connect-src'] === undefined) {
208+
csp.push('connect-src');
209+
}
210+
cspMap['connect-src'] = connectSrc;
211+
}
187212
}
188-
if (setFrameSrc) {
189-
csp.push(`frame-src 'self' https://challenges.cloudflare.com`);
190-
setFrameSrc = false;
213+
{
214+
if (envTurnstileInline && !(cspMap['script-src'] ?? '').includes(`'nonce-${nonce}'`)) {
215+
const scriptSrc = (cspMap['script-src'] ?? cspMap['default-src'] ?? '').concat(` 'nonce-${nonce}'`).trim();
216+
if (cspMap['script-src'] === undefined) {
217+
csp.push('script-src');
218+
}
219+
cspMap['script-src'] = scriptSrc;
220+
}
221+
}
222+
{
223+
if (!envTurnstileInline && !(cspMap['script-src'] ?? cspMap['default-src'] ?? '').includes(`'self'`)) {
224+
const scriptSrc = (cspMap['script-src'] ?? cspMap['default-src'] ?? '').concat(` 'self'`).trim();
225+
if (cspMap['script-src'] === undefined) {
226+
csp.push('script-src');
227+
}
228+
cspMap['script-src'] = scriptSrc;
229+
}
230+
}
231+
232+
for (let cspIndex = 0; cspIndex < csp.length; cspIndex += 1) {
233+
const directive = csp[cspIndex];
234+
csp[cspIndex] = `${directive} ${cspMap[directive]}`;
191235
}
192236
response.headers.set('Content-Security-Policy', csp.join('; '));
193237
}

libs/@cf-workers/turnstile-injection/src/turnstile-element-handlers.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export class TurnstileBodyHandler implements HTMLRewriterElementContentHandlers
3030
constructor(
3131
public readonly siteKey: string | undefined,
3232
public readonly fieldName = 'cfr',
33-
public readonly inline = false,
33+
public readonly inlineNonce: string | undefined = undefined,
3434
public readonly random: string,
3535
public readonly hosts: string,
3636
) {}
@@ -75,8 +75,8 @@ export class TurnstileBodyHandler implements HTMLRewriterElementContentHandlers
7575

7676
element.append(
7777
`
78-
<script type="application/javascript"${this.inline ? '' : ` async src="/cftsc.js?v=${WEBPACK_BUILD_VERSION}"`}>${
79-
this.inline ? this.script() : ''
78+
<script type="application/javascript"${this.inlineNonce ? ` nonce="${this.inlineNonce}"` : ` async src="/cftsc.js?v=${WEBPACK_BUILD_VERSION}"`}>${
79+
this.inlineNonce ? this.script() : ''
8080
}</script>
8181
<div id="cfc${this.random}"></div>
8282
`

0 commit comments

Comments
 (0)