Skip to content

fix: compute CSP hashes from final HTML in render:response#705

Open
harlan-zw wants to merge 2 commits intoBaroshem:mainfrom
harlan-zw:fix/csp-hash-after-render-html
Open

fix: compute CSP hashes from final HTML in render:response#705
harlan-zw wants to merge 2 commits intoBaroshem:mainfrom
harlan-zw:fix/csp-hash-after-render-html

Conversation

@harlan-zw
Copy link
Copy Markdown

@harlan-zw harlan-zw commented Apr 14, 2026

Types of changes

  • Bug fix (a non-breaking change which fixes an issue)
  • New feature (a non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Description

CSP SSG hashes were computed during render:html, so any module that mutated inline <script>/<style> content in a later render:html callback (e.g. a minifier) produced served content that no longer matched the stored hashes, causing CSP violations. reorderNitroPlugins() dodged this most of the time but left the ordering fragile.

This moves hash computation, CSP directive update, and CSP meta injection to render:response, where response.body is the finalized HTML string. Hashes now always match what gets served, regardless of other modules' render:html ordering.

Related: nuxt-seo-utils#103.

Checklist:

  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have added tests to cover my changes

A new fixture plugin (test/fixtures/ssgHashes/server/plugins/transform-inline.ts) collapses whitespace inside inline <script>/<style> tags during render:html, modelling a third-party HTML mutator. New parametrized tests in test/ssgHashes.test.ts assert every inline script/style in the served body hashes into both the CSP meta tag and the response header, across 8 routes.

Move SSG CSP hash computation, CSP directive update, and CSP meta tag
injection from `render:html` to `render:response`. This ensures hashes
reflect the final served HTML regardless of the order that other modules'
`render:html` listeners run.

Previously the hashes were computed inside `render:html` and relied on
`reorderNitroPlugins()` placing security plugins last in the Nitro plugin
array so their callbacks registered last. Any module that mutated inline
<script>/<style> content in a `render:html` callback that wasn't
guaranteed to run before nuxt-security's (for example, a minifier running
late in the pipeline) would cause the stored hashes to not match the
served content, producing CSP violations.

By hashing the finalized `response.body` string in `render:response`, the
correctness of CSP hashes no longer depends on plugin registration order.

Reported in harlan-zw/nuxt-seo-utils#103.
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 14, 2026

@harlan-zw is attempting to deploy a commit to the Baroshem's projects Team on Vercel.

A member of the Team first needs to authorize it.

@GreyXor
Copy link
Copy Markdown

GreyXor commented Apr 14, 2026

Fix: #699

@GreyXor
Copy link
Copy Markdown

GreyXor commented Apr 20, 2026

@Baroshem Hello, do we need something for a merge ? thanks @harlan-zw !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants