Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(core): use virtual file system for SRI #435

Merged
merged 1 commit into from
Apr 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions playground/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { defineNuxtConfig } from 'nuxt/config'

export default defineNuxtConfig({
modules: ['../src/module'],
devtools: { enabled: true },

// Per route configuration
routeRules: {
Expand Down Expand Up @@ -46,6 +47,7 @@ export default defineNuxtConfig({
// Global configuration
security: {
headers: {
crossOriginEmbedderPolicy: false,
xXSSProtection: '0'
},
rateLimiter: {
Expand Down
11 changes: 9 additions & 2 deletions src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,8 +195,15 @@ export default defineNuxtModule<ModuleOptions>({
// Import composables
addImportsDir(resolver.resolve('./runtime/composables'))

// Calculates SRI hashes at build time
nuxt.hook('nitro:build:before', hashBundledAssets)
// Record SRI Hashes in the Virtual File System at build time
let sriHashes: Record<string, string> = {}
nuxt.options.nitro.virtual = defu(
{ '#sri-hashes': () => `export default ${JSON.stringify(sriHashes)}` },
nuxt.options.nitro.virtual
)
nuxt.hook('nitro:build:before', async(nitro) => {
sriHashes = await hashBundledAssets(nitro)
})
}
})

Expand Down
21 changes: 4 additions & 17 deletions src/runtime/nitro/plugins/50-subresourceIntegrity.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,16 @@
import { useStorage, defineNitroPlugin } from '#imports'
import { defineNitroPlugin } from '#imports'
import { resolveSecurityRules } from '../utils'

//@ts-expect-error : we are importing from the virtual file system
import sriHashes from '#sri-hashes'

export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('render:html', async(html, { event }) => {
nitroApp.hooks.hook('render:html', (html, { event }) => {
// Exit if SRI not enabled for this route
const rules = resolveSecurityRules(event)
if (!rules.enabled || !rules.sri) {
return
}

// Retrieve the sriHases that we computed at build time
//
// - If we are in a pre-rendering step of nuxi generate
// Then the /integrity directory does not exist in server assets
// But it is still in the .nuxt build directory
//
// - Conversely, if we are in a standalone SSR server pre-built by nuxi build
// Then we don't have a .nuxt build directory anymore
// But we did save the /integrity directory into the server assets
const prerendering = !!import.meta.prerender
const storageBase = prerendering ? 'build' : 'assets'
const sriHashes = await useStorage(storageBase).getItem<Record<string, string>>('integrity:sriHashes.json') || {}


// Scan all relevant sections of the NuxtRenderHtmlContext
// Note: integrity can only be set on scripts and on links with rel preload, modulepreload and stylesheet
// However the SRI standard provides that other elements may be added to that list in the future
Expand Down
15 changes: 2 additions & 13 deletions src/runtime/utils/hashes.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createHash } from 'node:crypto'
import { existsSync } from 'node:fs'
import { readdir, readFile, writeFile, mkdir } from 'node:fs/promises'
import { readdir, readFile } from 'node:fs/promises'
import type { Nitro } from 'nitropack'
import { join } from 'pathe'

Expand Down Expand Up @@ -47,18 +47,7 @@ export async function hashBundledAssets(nitro: Nitro) {
}
}
}

// Save hashes in a /integrity directory within the .nuxt build for later use with SSG
const buildDir = nitro.options.buildDir
const integrityDir = join(buildDir, 'integrity')
if (!existsSync(integrityDir)) {
await mkdir(integrityDir)
}
const hashFilePath = join(integrityDir, 'sriHashes.json')
await writeFile(hashFilePath, JSON.stringify(sriHashes))

// Mount the /integrity directory into server assets for later use with SSR
nitro.options.serverAssets.push({ dir: integrityDir, baseName: 'integrity' })
return sriHashes
}

export function generateHash (content: Buffer | string, hashAlgorithm: string) {
Expand Down
Loading