Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
8 changes: 8 additions & 0 deletions packages/astro/src/container/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,10 @@ function createManifest(
checkOrigin: false,
middleware: manifest?.middleware ?? middlewareInstance,
key: createKey(),
clientScriptHashes: manifest?.clientScriptHashes ?? [],
clientStyleHashes: manifest?.clientStyleHashes ?? [],
shouldInjectCspMetaTags: manifest?.shouldInjectCspMetaTags ?? false,
astroIslandHashes: manifest?.astroIslandHashes ?? [],
};
}

Expand Down Expand Up @@ -250,6 +254,10 @@ type AstroContainerManifest = Pick<
| 'publicDir'
| 'outDir'
| 'cacheDir'
| 'clientScriptHashes'
| 'clientStyleHashes'
| 'shouldInjectCspMetaTags'
| 'astroIslandHashes'
>;

type AstroContainerConstructor = {
Expand Down
7 changes: 7 additions & 0 deletions packages/astro/src/core/app/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,13 @@ export type SSRManifest = {
publicDir: string | URL;
buildClientDir: string | URL;
buildServerDir: string | URL;
clientScriptHashes: string[];
clientStyleHashes: string[];
/**
* When enabled, Astro tracks the hashes of script and styles, and eventually it will render the `<meta>` tag
*/
shouldInjectCspMetaTags: boolean;
astroIslandHashes: string[];
};

export type SSRActions = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
// This file is code-generated, please don't change it manually
export default [
export const ASTRO_ISLAND_HASHES = [
"GI/D8grziRZwfj/Mqmn+dcgU/i8sylHSR/IfobqcUT4=",
"HDWxd14AUw8OvjrhhRRyyZFHCGnzxXGDrg59Qi8ayhc=",
"XN6a2Vn8uvpBr/WhdYPdK0jVeCzlcOD2XYaP10veV4Y=",
"ZR0ZAU8UNTzLmo/ApeWH0y1mVLT+XtFkvZ5nw32W8jI=",
"cSNmhdbFlyTDRozeu9HPjo+B2S4QAeMp0RO41PqgAcA=",
"mH3H4wSoDVWMXJKrmeBKYJQMdAZQ3dArB2N66JomkzI=",
"mH3H4wSoDVWMXJKrmeBKYJQMdAZQ3dArB2N66JomkzI="
"mH3H4wSoDVWMXJKrmeBKYJQMdAZQ3dArB2N66JomkzI=",
"s81ZcLcyAa7P/Jh5M5hUxYthTGwW+iZY3e6aHrQ8H9E="
];
1 change: 1 addition & 0 deletions packages/astro/src/core/base-pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import { sequence } from './middleware/sequence.js';
import { RouteCache } from './render/route-cache.js';
import { createDefaultRoutes } from './routing/default.js';
import crypto from 'node:crypto';

Check warning on line 22 in packages/astro/src/core/base-pipeline.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/correctness/noUnusedImports

This import is unused.

/**
* The `Pipeline` represents the static parts of rendering that do not change between requests.
Expand Down
20 changes: 17 additions & 3 deletions packages/astro/src/core/build/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
getStaticImageList,
prepareAssetsGenerationEnv,
} from '../../assets/build/generate.js';
import { type BuildInternals, hasPrerenderedPages } from '../../core/build/internal.js';
import { type BuildInternals, hasPrerenderedPages } from './internal.js';
import {
isRelativePath,
joinPaths,
Expand Down Expand Up @@ -49,6 +49,9 @@
StylesheetAsset,
} from './types.js';
import { getTimeStat, shouldAppendForwardSlash } from './util.js';
import crypto from 'node:crypto';

Check warning on line 52 in packages/astro/src/core/build/generate.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/correctness/noUnusedImports

This import is unused.
import { shouldTrackCspHashes, trackScriptHashes, trackStyleHashes } from '../csp/common.js';
import { ASTRO_ISLAND_HASHES } from '../astro-islands-hashes.js';

export async function generatePages(options: StaticBuildOptions, internals: BuildInternals) {
const generatePagesTimer = performance.now();
Expand Down Expand Up @@ -600,8 +603,6 @@
* It creates a `SSRManifest` from the `AstroSettings`.
*
* Renderers needs to be pulled out from the page module emitted during the build.
* @param settings
* @param renderers
*/
function createBuildManifest(
settings: AstroSettings,
Expand All @@ -612,6 +613,15 @@
key: Promise<CryptoKey>,
): SSRManifest {
let i18nManifest: SSRManifestI18n | undefined = undefined;

let clientStyleHashes: string[] = [];
let clientScriptHashes: string[] = [];

if (shouldTrackCspHashes(settings.config)) {
clientScriptHashes = trackScriptHashes(internals, settings);
clientStyleHashes = trackStyleHashes(internals);
}

if (settings.config.i18n) {
i18nManifest = {
fallback: settings.config.i18n.fallback,
Expand Down Expand Up @@ -655,5 +665,9 @@
checkOrigin:
(settings.config.security?.checkOrigin && settings.buildOutput === 'server') ?? false,
key,
clientStyleHashes,
clientScriptHashes,
shouldInjectCspMetaTags: shouldTrackCspHashes(settings.config),
astroIslandHashes: ASTRO_ISLAND_HASHES,
};
}
16 changes: 16 additions & 0 deletions packages/astro/src/core/build/plugins/plugin-manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
import type { AstroBuildPlugin } from '../plugin.js';
import type { StaticBuildOptions } from '../types.js';
import { makePageDataKey } from './util.js';
import crypto from 'node:crypto';

Check warning on line 26 in packages/astro/src/core/build/plugins/plugin-manifest.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/correctness/noUnusedImports

This import is unused.
import { shouldTrackCspHashes, trackScriptHashes, trackStyleHashes } from '../../csp/common.js';
import type { AstroSettings } from '../../../types/astro.js';

Check warning on line 28 in packages/astro/src/core/build/plugins/plugin-manifest.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/correctness/noUnusedImports

This import is unused.
import { ASTRO_ISLAND_HASHES } from '../../astro-islands-hashes.js';

const manifestReplace = '@@ASTRO_MANIFEST_REPLACE@@';
const replaceExp = new RegExp(`['"]${manifestReplace}['"]`, 'g');
Expand Down Expand Up @@ -275,6 +279,14 @@
};
}

let clientScriptHashes: string[] = [];
let clientStyleHashes: string[] = [];

if (shouldTrackCspHashes(settings.config)) {
clientScriptHashes = trackScriptHashes(internals, opts.settings);
clientStyleHashes = trackStyleHashes(internals);
}

return {
hrefRoot: opts.settings.config.root.toString(),
cacheDir: opts.settings.config.cacheDir.toString(),
Expand Down Expand Up @@ -304,5 +316,9 @@
serverIslandNameMap: Array.from(settings.serverIslandNameMap),
key: encodedKey,
sessionConfig: settings.config.session,
shouldInjectCspMetaTags: shouldTrackCspHashes(opts.settings.config),
clientStyleHashes,
clientScriptHashes,
astroIslandHashes: ASTRO_ISLAND_HASHES,
};
}
2 changes: 2 additions & 0 deletions packages/astro/src/core/config/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ export const ASTRO_CONFIG_DEFAULTS = {
session: false,
headingIdCompat: false,
preserveScriptOrder: false,
csp: false,
},
} satisfies AstroUserConfig & { server: { open: boolean } };

Expand Down Expand Up @@ -626,6 +627,7 @@ export const AstroConfigSchema = z.object({
.boolean()
.optional()
.default(ASTRO_CONFIG_DEFAULTS.experimental.preserveScriptOrder),
csp: z.boolean().optional().default(ASTRO_CONFIG_DEFAULTS.experimental.csp),
})
.strict(
`Invalid or outdated experimental feature.\nCheck for incorrect spelling or outdated Astro version.\nSee https://docs.astro.build/en/reference/experimental-flags/ for a list of all current experiments.`,
Expand Down
40 changes: 40 additions & 0 deletions packages/astro/src/core/csp/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import type { AstroConfig } from '../../types/public/index.js';
import type { BuildInternals } from '../build/internal.js';
import crypto from 'node:crypto';
import type { AstroSettings } from '../../types/astro.js';

export function shouldTrackCspHashes(config: AstroConfig): boolean {
return config.experimental?.csp === true;
}

export function trackStyleHashes(internals: BuildInternals): string[] {
const clientStyleHashes: string[] = [];
for (const [_, page] of internals.pagesByViteID.entries()) {
for (const style of page.styles) {
if (style.sheet.type === 'inline') {
clientStyleHashes.push(
crypto.createHash('sha256').update(style.sheet.content).digest('base64'),
);
}
}
}

return clientStyleHashes;
}

export function trackScriptHashes(internals: BuildInternals, settings: AstroSettings): string[] {
const clientScriptHashes: string[] = [];

for (const script of internals.inlinedScripts.values()) {
clientScriptHashes.push(crypto.createHash('sha256').update(script).digest('base64'));
}

for (const script of settings.scripts) {
const { content, stage } = script;
if (stage === 'head-inline' || stage === 'before-hydration') {
clientScriptHashes.push(crypto.createHash('sha256').update(content).digest('base64'));
}
}

return clientScriptHashes;
}
3 changes: 3 additions & 0 deletions packages/astro/src/core/render-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,9 @@ export class RenderContext {
extraHead: [],
propagators: new Set(),
},
shouldInjectCspMetaTags: manifest.shouldInjectCspMetaTags,
clientScriptHashes: manifest.clientScriptHashes,
clientStyleHashes: manifest.clientStyleHashes,
};

return result;
Expand Down
17 changes: 10 additions & 7 deletions packages/astro/src/integrations/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
} from '../types/public/integrations.js';
import type { RouteData } from '../types/public/internal.js';
import { validateSupportedFeatures } from './features-validation.js';
import type { Pipeline } from '../core/base-pipeline.js';

Check warning on line 37 in packages/astro/src/integrations/hooks.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/correctness/noUnusedImports

This import is unused.

async function withTakingALongTimeMsg<T>({
name,
Expand Down Expand Up @@ -129,19 +130,21 @@
return `${normalizeCodegenDir(integrationName)}${filename.replace(SAFE_CHARS_RE, '_')}`;
}

interface RunHookConfigSetup {
settings: AstroSettings;
command: 'dev' | 'build' | 'preview' | 'sync';
logger: Logger;
isRestart?: boolean;
fs?: typeof fsMod;
}

export async function runHookConfigSetup({
settings,
command,
logger,
isRestart = false,
fs = fsMod,
}: {
settings: AstroSettings;
command: 'dev' | 'build' | 'preview' | 'sync';
logger: Logger;
isRestart?: boolean;
fs?: typeof fsMod;
}): Promise<AstroSettings> {
}: RunHookConfigSetup): Promise<AstroSettings> {
// An adapter is an integration, so if one is provided add it to the list of integrations.
if (settings.config.adapter) {
settings.config.integrations.unshift(settings.config.adapter);
Expand Down
2 changes: 2 additions & 0 deletions packages/astro/src/runtime/server/astro-island-styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// NOTE: if you change the value inside the style tag, you must update `prebuild.js` too
export const ISLAND_STYLES = `<style>astro-island,astro-slot,astro-static-slot{display:contents}</style>`;
2 changes: 1 addition & 1 deletion packages/astro/src/runtime/server/render/common.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { RenderInstruction } from './instruction.js';

import type { SSRResult } from '../../../types/public/internal.js';
import type { HTMLBytes, HTMLString } from '../escape.js';
import { markHTMLString } from '../escape.js';
Expand Down Expand Up @@ -99,6 +98,7 @@ function stringifyChunk(
}
return '';
}

default: {
throw new Error(`Unknown chunk type: ${(chunk as any).type}`);
}
Expand Down
21 changes: 21 additions & 0 deletions packages/astro/src/runtime/server/render/head.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,27 @@ export function renderAllHeadContent(result: SSRResult) {
}
}

const hashes = [];

if (result.shouldInjectCspMetaTags) {
for (const scriptHash of [...result.clientScriptHashes, ...result.clientStyleHashes]) {
hashes.push(
renderElement(
'meta',
{
props: {
'http-equiv': 'content-security-policy',
content: scriptHash,
},
children: '',
},
false,
),
);
}
}
content += hashes.join('\n');

return markHTMLString(content);
}

Expand Down
3 changes: 1 addition & 2 deletions packages/astro/src/runtime/server/scripts.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import type { SSRResult } from '../../types/public/internal.js';
import islandScriptDev from './astro-island.prebuilt-dev.js';
import islandScript from './astro-island.prebuilt.js';

const ISLAND_STYLES = `<style>astro-island,astro-slot,astro-static-slot{display:contents}</style>`;
import { ISLAND_STYLES } from './astro-island-styles.js';

export function determineIfNeedsHydrationScript(result: SSRResult): boolean {
if (result._metadata.hasHydrationScript) {
Expand Down
7 changes: 7 additions & 0 deletions packages/astro/src/types/public/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2170,6 +2170,13 @@ export interface ViteUserConfig extends OriginalViteUserConfig {
*/
headingIdCompat?: boolean;


/**
*
*/
// TODO: add docs once we are reaching the end
csp?: boolean,

/**
* @name experimental.preserveScriptOrder
* @type {boolean}
Expand Down
6 changes: 6 additions & 0 deletions packages/astro/src/types/public/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,12 @@ export interface SSRResult {
trailingSlash: AstroConfig['trailingSlash'];
key: Promise<CryptoKey>;
_metadata: SSRMetadata;
/**
* Whether Astro should inject the CSP <meta> tag into the head of the component.
*/
shouldInjectCspMetaTags: boolean;
clientScriptHashes: string[];
clientStyleHashes: string[];
}

/**
Expand Down
9 changes: 7 additions & 2 deletions packages/astro/src/vite-plugin-astro-server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import { DevPipeline } from './pipeline.js';
import { handleRequest } from './request.js';
import { setRouteError } from './server-state.js';
import { trailingSlashMiddleware } from './trailing-slash.js';
import { ASTRO_ISLAND_HASHES } from '../core/astro-islands-hashes.js';
import { shouldTrackCspHashes } from '../core/csp/common.js';

export interface AstroPluginOptions {
settings: AstroSettings;
Expand Down Expand Up @@ -100,8 +102,7 @@ export default function createVitePluginAstroServer({
});
const store = localStorage.getStore();
if (store instanceof IncomingMessage) {
const request = store;
setRouteError(controller.state, request.url!, error);
setRouteError(controller.state, store.url!, error);
}
const { errorWithMetadata } = recordServerError(loader, settings.config, pipeline, error);
setTimeout(
Expand Down Expand Up @@ -207,5 +208,9 @@ export function createDevelopmentManifest(settings: AstroSettings): SSRManifest
};
},
sessionConfig: settings.config.experimental.session ? settings.config.session : undefined,
clientScriptHashes: [],
clientStyleHashes: [],
shouldInjectCspMetaTags: shouldTrackCspHashes(settings.config),
astroIslandHashes: ASTRO_ISLAND_HASHES,
};
}
Loading
Loading