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: generate csp headers for static content served on vercel #111

Merged
merged 3 commits into from
Sep 30, 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
1 change: 1 addition & 0 deletions .github/FUNDING.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@

github: [KindSpells, castarco]
open_collective: kindspells-labs
polar: kindspells
2 changes: 1 addition & 1 deletion .github/workflows/npm_publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
- name: Install PNPM # v3.0.0
uses: pnpm/action-setup@a3252b78c470c02df07e9d59298aecedc3ccdd6d
with:
version: '9.10.0'
version: '9.11.0'
- name: Use Node.js ${{ matrix.node-version }} # v4.0.2
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8
with:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:
- name: Install PNPM # v3.0.0
uses: pnpm/action-setup@a3252b78c470c02df07e9d59298aecedc3ccdd6d
with:
version: '9.10.0'
version: '9.11.0'
- name: Use Node.js ${{ matrix.node-version }} # v4.0.2
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8
with:
Expand Down
2 changes: 1 addition & 1 deletion .moon/toolchain.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
node:
packageManager: 'pnpm'
pnpm:
version: 9.10.0
version: 9.11.0
2 changes: 1 addition & 1 deletion .node-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
22.8.0
22.9.0
12 changes: 6 additions & 6 deletions @kindspells/astro-shield/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@kindspells/astro-shield",
"version": "1.5.3",
"version": "1.6.0",
"description": "Astro integration to enhance your website's security with SubResource Integrity hashes, Content-Security-Policy headers, and other techniques.",
"private": false,
"type": "module",
Expand Down Expand Up @@ -63,14 +63,14 @@
"astro": "^4.0.0"
},
"devDependencies": {
"@types/node": "^22.5.5",
"astro": "^4.15.8",
"@types/node": "^22.7.4",
"astro": "^4.15.9",
"get-tsconfig": "^4.8.1",
"rollup": "^4.22.0",
"rollup": "^4.22.5",
"rollup-plugin-dts": "^6.1.1",
"rollup-plugin-esbuild": "^6.1.1",
"typescript": "^5.6.2",
"vite": "^5.4.6",
"vite": "^5.4.8",
"vitest": "^2.1.1"
},
"repository": {
Expand All @@ -89,7 +89,7 @@
"url": "https://ko-fi.com/coderspirit"
}
],
"packageManager": "pnpm@9.10.0",
"packageManager": "pnpm@9.11.0",
"engines": {
"node": ">= 18.0.0"
},
Expand Down
82 changes: 59 additions & 23 deletions @kindspells/astro-shield/src/core.mts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { doesFileExist, scanDirectory } from './fs.mts'
import { patchHeaders } from './headers.mts'
import type {
HashesCollection,
IntegrationState,
Logger,
MiddlewareHashes,
PerPageHashes,
Expand All @@ -25,6 +26,9 @@ import type {
} from './types.mts'
import { patchNetlifyHeadersConfig } from './netlify.mts'
import { exhaustiveGuard } from './utils.mts'
import { patchVercelHeadersConfig } from './vercel.mts'

type AstroHooks = AstroIntegration['hooks']

export type HashesModule = {
[k in keyof HashesCollection]: HashesCollection[k] extends Set<string>
Expand Down Expand Up @@ -741,7 +745,7 @@ const newHashesCollection = (): HashesCollection => ({
// TODO: TEST CASE!
export const processStaticFiles = async (
logger: Logger,
{ distDir, sri, securityHeaders }: StrictShieldOptions,
{ state, distDir, sri, securityHeaders }: StrictShieldOptions,
): Promise<void> => {
const h = newHashesCollection()

Expand All @@ -761,23 +765,23 @@ export const processStaticFiles = async (
const provider = securityHeaders.enableOnStaticPages.provider
switch (provider) {
case 'netlify': {
if (
(securityHeaders.enableOnStaticPages.mode ?? '_headers') !==
'_headers'
) {
throw new Error(
'Netlify provider only supports "_headers" mode for now',
)
}
await patchNetlifyHeadersConfig(
resolve(distDir, '_headers'),
securityHeaders,
h,
h.perPageSriHashes,
)
break
}
case 'vercel': {
await patchVercelHeadersConfig(
logger,
distDir,
state.config,
securityHeaders,
h.perPageSriHashes,
)
break
}
case 'vercel':
throw new Error('Vercel provider is still not supported')
default:
exhaustiveGuard(provider, 'provider')
}
Expand Down Expand Up @@ -992,29 +996,61 @@ export const getViteMiddlewarePlugin = (
}
}

const getAstroBuildDone = (
state: IntegrationState,
sri: Required<SRIOptions>,
securityHeaders: SecurityHeadersOptions | undefined,
): NonNullable<AstroHooks['astro:build:done']> =>
(async ({ dir, logger }) => {
if (sri.enableStatic) {
await processStaticFiles(logger, {
state,
distDir: fileURLToPath(dir),
sri,
securityHeaders,
})
}
}) satisfies NonNullable<AstroHooks['astro:build:done']>

/**
* @param {Required<SRIOptions>} sri
* @param {SecurityHeadersOptions | undefined} securityHeaders
* @returns
*/
export const getAstroConfigSetup = (
state: IntegrationState,
sri: Required<SRIOptions>,
securityHeaders: SecurityHeadersOptions | undefined,
): Required<AstroIntegration['hooks']>['astro:config:setup'] => {
// biome-ignore lint/suspicious/useAwait: We have to conform to the Astro API
return async ({ logger, addMiddleware, config, updateConfig }) => {
const publicDir = fileURLToPath(config.publicDir)
const plugin = getViteMiddlewarePlugin(
logger,
sri,
securityHeaders,
publicDir,
)
updateConfig({ vite: { plugins: [plugin] } })
state.config = config

if (sri.enableMiddleware) {
const publicDir = fileURLToPath(config.publicDir)
const plugin = getViteMiddlewarePlugin(
logger,
sri,
securityHeaders,
publicDir,
)
updateConfig({ vite: { plugins: [plugin] } })

addMiddleware({
order: 'post',
entrypoint: 'virtual:@kindspells/astro-shield/middleware',
})
}

addMiddleware({
order: 'post',
entrypoint: 'virtual:@kindspells/astro-shield/middleware',
updateConfig({
integrations: [
{
name: '@kindspells/astro-shield-post-config-setup',
hooks: {
'astro:build:done': getAstroBuildDone(state, sri, securityHeaders),
},
},
],
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
},
"license": "MIT",
"dependencies": {
"@astrojs/node": "^8.3.3",
"astro": "^4.15.8"
"@astrojs/node": "^8.3.4",
"astro": "^4.15.9"
},
"devDependencies": {
"@kindspells/astro-shield": "workspace:*"
Expand Down
4 changes: 2 additions & 2 deletions @kindspells/astro-shield/src/e2e/fixtures/hybrid/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
},
"license": "MIT",
"dependencies": {
"@astrojs/node": "^8.3.3",
"astro": "^4.15.8"
"@astrojs/node": "^8.3.4",
"astro": "^4.15.9"
},
"devDependencies": {
"@kindspells/astro-shield": "workspace:*"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
},
"license": "MIT",
"dependencies": {
"@astrojs/node": "^8.3.3",
"astro": "^4.15.8"
"@astrojs/node": "^8.3.4",
"astro": "^4.15.9"
},
"devDependencies": {
"@kindspells/astro-shield": "workspace:*"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
},
"license": "MIT",
"dependencies": {
"@astrojs/node": "^8.3.3",
"astro": "^4.15.8"
"@astrojs/node": "^8.3.4",
"astro": "^4.15.9"
},
"devDependencies": {
"@kindspells/astro-shield": "workspace:*"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
},
"license": "MIT",
"dependencies": {
"astro": "^4.15.8"
"astro": "^4.15.9"
},
"devDependencies": {
"@kindspells/astro-shield": "workspace:*"
Expand Down
36 changes: 5 additions & 31 deletions @kindspells/astro-shield/src/main.mts
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,10 @@
* SPDX-License-Identifier: MIT
*/

import { fileURLToPath } from 'node:url'

import type { AstroIntegration } from 'astro'

import { getAstroConfigSetup, processStaticFiles } from '#as/core'
import type {
SecurityHeadersOptions,
ShieldOptions,
SRIOptions,
} from './types.mts'

type AstroHooks = AstroIntegration['hooks']

const getAstroBuildDone = (
sri: Required<SRIOptions>,
securityHeaders: SecurityHeadersOptions | undefined,
): NonNullable<AstroHooks['astro:build:done']> =>
(async ({ dir, logger }) =>
await processStaticFiles(logger, {
distDir: fileURLToPath(dir),
sri,
securityHeaders,
})) satisfies NonNullable<AstroHooks['astro:build:done']>
import { getAstroConfigSetup } from '#as/core'
import type { IntegrationState, ShieldOptions, SRIOptions } from './types.mts'

const logWarn = (msg: string): void =>
console.warn(`\nWARNING (@kindspells/astro-shield):\n\t${msg}\n`)
Expand Down Expand Up @@ -54,19 +35,12 @@ export const shield = ({
logWarn('`sri.hashesModule` is ignored when `sri.enableStatic` is `false`')
}

const state: IntegrationState = { config: {} }

return {
name: '@kindspells/astro-shield',
hooks: {
...(_sri.enableStatic === true
? {
'astro:build:done': getAstroBuildDone(_sri, securityHeaders),
}
: undefined),
...(_sri.enableMiddleware === true
? {
'astro:config:setup': getAstroConfigSetup(_sri, securityHeaders),
}
: undefined),
'astro:config:setup': getAstroConfigSetup(state, _sri, securityHeaders),
},
} satisfies AstroIntegration
}
Expand Down
Loading