e6ddf60465deb96e43be6738c5ca6a7a6168cac8 braney Sat Aug 22 15:08:59 2026 -0700 hg/lib: add an option to send the content policy as an http response header New cspWriteResponseHeader() in hg/lib/hCommon.c, gated on hg.conf's cspResponseHeader, which defaults off everywhere. The policy string itself is still built by the existing code in lib/htmshell.c, which now also knows how to format it as a response header. Both carry the same nonce, since getNonce() is one per process, so a page may safely have the header and the meta tag. Most pages pick it up from addHttpHeaders() in cart.c, the existing hook for extra response headers, which every cart based CGI already passes through. Six places build their own http header block and so call it directly: the two "too many requests" pages, the captcha and its error page, the hubApi help redirect, and the hgSearch redirect to hgTracks. Inline scripts on three of those pages now carry the nonce, and the policy allows the Cloudflare script the bot check loads, so the option works when it is turned on. The Cloudflare entry is the only part of this that takes effect with the option off. diff --git src/lib/htmshell.c src/lib/htmshell.c index d217e315924..4521f919349 100644 --- src/lib/htmshell.c +++ src/lib/htmshell.c @@ -1038,30 +1038,32 @@ // jsHelper dyStringAppend(policy, " cdn.datatables.net/1.10.12/js/jquery.dataTables.min.js"); dyStringAppend(policy, " cdn.datatables.net/2.2.2/js/dataTables.min.js"); dyStringAppend(policy, " cdn.datatables.net/buttons/3.2.2/js/dataTables.buttons.min.js"); dyStringAppend(policy, " cdn.datatables.net/select/3.0.0/js/dataTables.select.min.js"); // for facetedComposite.js: best compatible combination // jquery 3.5.1 (already above), dataTables 1.13.6, dataTables.select 1.7.0 dyStringAppend(policy, " cdn.datatables.net/select/1.7.0/js/dataTables.select.min.js"); dyStringAppend(policy, " cdn.datatables.net/1.13.6/js/jquery.dataTables.min.js"); // shephered js for tutorial overlay dyStringAppend(policy, " cdn.jsdelivr.net/npm/shepherd.js@11.0.1/dist/js/shepherd.min.js"); dyStringAppend(policy, " www.google.com/recaptcha/api.js"); +// used by the captcha in hg/lib/cart.c printCaptcha +dyStringAppend(policy, " challenges.cloudflare.com/turnstile/v0/api.js"); // uppy for hubSpace uploads dyStringAppend(policy, " releases.transloadit.com/uppy/v4.5.0/uppy.min.js"); dyStringAppend(policy, ";"); dyStringAppend(policy, " style-src * 'unsafe-inline';"); /* more secure method not used yet dyStringAppend(policy, " style-src 'self' 'unsafe-inline'"); dyStringAppend(policy, " code.jquery.com"); // used by hgIntegrator dyStringAppend(policy, " netdna.bootstrapcdn.com"); // used by hgIntegrator dyStringAppend(policy, " fonts.googleapis.com"); // used by hgGateway dyStringAppend(policy, " maxcdn.bootstrapcdn.com"); // used by hgGateway @@ -1121,30 +1123,42 @@ { char *policy = getCspPolicyString(); char *meta = getCspMetaString(policy); freeMem(policy); return meta; } void generateCspMetaHeader(FILE *f) /* generate meta CSP header */ { char *meta = getCspMetaHeader(); fputs(meta, f); freeMem(meta); } +void generateCspResponseHeader(FILE *f) +/* generate the CSP as an http response header. Carries the same nonce as the + * meta tag, since getNonce() is one-per-process, so a page may safely have both. + * Must be called before the blank line that ends the http header block. */ +{ +char *policy = getCspPolicyString(); +char *header = getCspMetaResponseHeader(policy); +fputs(header, f); +freeMem(header); +freeMem(policy); +} + void _htmStartWithHead(FILE *f, char *head, char *title, boolean printDocType, int dirDepth) /* Write out bits of header that both stand-alone .htmls * and CGI returned .htmls need, including optional head info */ { if (printDocType) fputs("\n",f); if (doNotTranslate) fputs("\n", f); // switches off auto-translation question else fputs("\n", f); fputs("\n", f);