diff --git a/astro.sidebar.ts b/astro.sidebar.ts index d494e32dc76e9..6c167dfce705b 100644 --- a/astro.sidebar.ts +++ b/astro.sidebar.ts @@ -142,6 +142,7 @@ export const sidebar = [ 'reference/experimental-flags/content-intellisense', 'reference/experimental-flags/preserve-scripts-order', 'reference/experimental-flags/heading-id-compat', + 'reference/experimental-flags/csp', ], }), 'reference/legacy-flags', diff --git a/src/content/docs/en/reference/adapter-reference.mdx b/src/content/docs/en/reference/adapter-reference.mdx index ac13fe16961d4..5f94d6967631c 100644 --- a/src/content/docs/en/reference/adapter-reference.mdx +++ b/src/content/docs/en/reference/adapter-reference.mdx @@ -26,12 +26,12 @@ An adapter __must__ call the `setAdapter` API in the `astro:config:done` hook li ```js title="my-adapter.mjs" export default function createIntegration() { return { - name: '@matthewp/my-adapter', + name: '@example/my-adapter', hooks: { 'astro:config:done': ({ setAdapter }) => { setAdapter({ - name: '@matthewp/my-adapter', - serverEntrypoint: '@matthewp/my-adapter/server.js', + name: '@example/my-adapter', + serverEntrypoint: '@example/my-adapter/server.js', supportedAstroFeatures: { staticOutput: 'stable' } @@ -71,6 +71,7 @@ export type AdapterSupportsKind = 'unsupported' | 'stable' | 'experimental' | 'd export type AdapterSupportWithMessage = { support: Exclude; message: string; + suppress?: 'default' | 'all'; }; export type AdapterSupport = AdapterSupportsKind | AdapterSupportWithMessage; @@ -147,12 +148,12 @@ And then in your integration, where you call `setAdapter`, provide this name in ```js title="my-adapter.mjs" ins={9} export default function createIntegration() { return { - name: '@matthewp/my-adapter', + name: '@example/my-adapter', hooks: { 'astro:config:done': ({ setAdapter }) => { setAdapter({ - name: '@matthewp/my-adapter', - serverEntrypoint: '@matthewp/my-adapter/server.js', + name: '@example/my-adapter', + serverEntrypoint: '@example/my-adapter/server.js', exports: ['handler'], }); }, @@ -371,21 +372,21 @@ Astro features are a way for an adapter to tell Astro whether they are able to s When using these properties, Astro will: - run specific validation; -- emit contextual to the logs; +- emit contextual information to the logs; -These operations are run based on the features supported or not supported, their level of support, and the configuration that the user uses. +These operations are run based on the features supported or not supported, their level of support, the [desired amount of logging](#suppress), and the user's own configuration. The following configuration tells Astro that this adapter has experimental support for the Sharp-powered built-in image service: ```js title="my-adapter.mjs" ins={9-11} export default function createIntegration() { return { - name: '@matthewp/my-adapter', + name: '@example/my-adapter', hooks: { 'astro:config:done': ({ setAdapter }) => { setAdapter({ - name: '@matthewp/my-adapter', - serverEntrypoint: '@matthewp/my-adapter/server.js', + name: '@example/my-adapter', + serverEntrypoint: '@example/my-adapter/server.js', supportedAstroFeatures: { sharpImageService: 'experimental' } @@ -399,9 +400,9 @@ export default function createIntegration() { If the Sharp image service is used, Astro will log a warning and error to the terminal based on your adapter's support: ``` -[@matthewp/my-adapter] The feature is experimental and subject to issues or changes. +[@example/my-adapter] The feature is experimental and subject to issues or changes. -[@matthewp/my-adapter] The currently selected adapter `@matthewp/my-adapter` is not compatible with the service "Sharp". Your project will NOT be able to build. +[@example/my-adapter] The currently selected adapter `@example/my-adapter` is not compatible with the service "Sharp". Your project will NOT be able to build. ``` A message can additionally be provided to give more context to the user: @@ -409,16 +410,76 @@ A message can additionally be provided to give more context to the user: ```js title="my-adapter.mjs" ins={9-14} export default function createIntegration() { return { - name: '@matthewp/my-adapter', + name: '@example/my-adapter', hooks: { 'astro:config:done': ({ setAdapter }) => { setAdapter({ - name: '@matthewp/my-adapter', - serverEntrypoint: '@matthewp/my-adapter/server.js', + name: '@example/my-adapter', + serverEntrypoint: '@example/my-adapter/server.js', supportedAstroFeatures: { sharpImageService: { support: 'limited', - message: 'This adapter has limited support for Sharp, certain features may not work as expected.' + message: 'This adapter has limited support for Sharp. Certain features may not work as expected.' + } + } + }); + }, + }, + }; +} +``` + +### `suppress` + +

+ + **Type:** `'default' | 'all'`
+ +

+ +An option to prevent showing some or all logged messages about an adapter's support for a feature. + +If Astro's default log message is redundant, or confusing to the user in combination with your custom `message`, you can use `suppress: "default"` to suppress the default message and only log your message: + +```js title="my-adapter.mjs" ins={13} +export default function createIntegration() { + return { + name: '@example/my-adapter', + hooks: { + 'astro:config:done': ({ setAdapter }) => { + setAdapter({ + name: '@example/my-adapter', + serverEntrypoint: '@example/my-adapter/server.js', + supportedAstroFeatures: { + sharpImageService: { + support: 'limited', + message: 'The adapter has limited support for Sharp. It will be used for images during build time, but will not work at runtime.', + suppress: 'default' // custom message is more detailed than the default + } + } + }); + }, + }, + }; +} +``` + +You can also use `suppress: "all"` to suppress all messages about support for the feature. This is useful when these messages are unhelpful to users in a specific context, such as when they have a configuration setting that means they are not using that feature. For example, you can choose to prevent logging any messages about Sharp support from your adapter: + +```js title="my-adapter.mjs" ins={13} +export default function createIntegration() { + return { + name: '@example/my-adapter', + hooks: { + 'astro:config:done': ({ setAdapter }) => { + setAdapter({ + name: '@example/my-adapter', + serverEntrypoint: '@example/my-adapter/server.js', + supportedAstroFeatures: { + sharpImageService: { + support: 'limited', + message: 'This adapter has limited support for Sharp. Certain features may not work as expected.', + suppress: 'all' } } }); @@ -446,12 +507,12 @@ When enabled, this prevents middleware code from being bundled and imported by a ```js title="my-adapter.mjs" ins={9-11} export default function createIntegration() { return { - name: '@matthewp/my-adapter', + name: '@example/my-adapter', hooks: { 'astro:config:done': ({ setAdapter }) => { setAdapter({ - name: '@matthewp/my-adapter', - serverEntrypoint: '@matthewp/my-adapter/server.js', + name: '@example/my-adapter', + serverEntrypoint: '@example/my-adapter/server.js', adapterFeatures: { edgeMiddleware: true } @@ -467,12 +528,12 @@ Then, consume the hook [`astro:build:ssr`](/en/reference/integrations-reference/ ```js title="my-adapter.mjs" ins={15-20} export default function createIntegration() { return { - name: '@matthewp/my-adapter', + name: '@example/my-adapter', hooks: { 'astro:config:done': ({ setAdapter }) => { setAdapter({ - name: '@matthewp/my-adapter', - serverEntrypoint: '@matthewp/my-adapter/server.js', + name: '@example/my-adapter', + serverEntrypoint: '@example/my-adapter/server.js', adapterFeatures: { edgeMiddleware: true } @@ -508,12 +569,12 @@ Enable the feature by passing any valid `AdapterSupportsKind` value to the adapt ```js title="my-adapter.mjs" ins={9-11} export default function createIntegration() { return { - name: '@matthewp/my-adapter', + name: '@example/my-adapter', hooks: { 'astro:config:done': ({ setAdapter }) => { setAdapter({ - name: '@matthewp/my-adapter', - serverEntrypoint: '@matthewp/my-adapter/server.js', + name: '@example/my-adapter', + serverEntrypoint: '@example/my-adapter/server.js', adapterFeatures: { envGetSecret: 'stable' } @@ -583,12 +644,12 @@ This property allows you to force a specific output shape for the build. This ca ```js title="my-adapter.mjs" ins={9-11} export default function createIntegration() { return { - name: '@matthewp/my-adapter', + name: '@example/my-adapter', hooks: { 'astro:config:done': ({ setAdapter }) => { setAdapter({ - name: '@matthewp/my-adapter', - serverEntrypoint: '@matthewp/my-adapter/server.js', + name: '@example/my-adapter', + serverEntrypoint: '@example/my-adapter/server.js', adapterFeatures: { buildOutput: 'static' } diff --git a/src/content/docs/en/reference/content-loader-reference.mdx b/src/content/docs/en/reference/content-loader-reference.mdx index 28591b78d6084..55964d7a38ba3 100644 --- a/src/content/docs/en/reference/content-loader-reference.mdx +++ b/src/content/docs/en/reference/content-loader-reference.mdx @@ -351,6 +351,45 @@ export function feedLoader({ url }): Loader { } ``` +#### `renderMarkdown` + +

+ +**Type**: `(markdown: string) => Promise` + +

+ +Renders a Markdown string to HTML, returning a `RenderedContent` object. + +This allows allows you to render Markdown content directly within your loaders using the same Markdown processing as Astro's built-in `glob` loader and provides access to the `render()` function and `` component for [rendering body content](/en/guides/content-collections/#rendering-body-content). + +Assign this object to the [rendered](#rendered) field of the [DataEntry](#dataentry) object to allow users to [render the content in a page](/en/guides/content-collections/#rendering-body-content). + +```ts title=loader.ts {16-17} +import type { Loader } from 'astro/loaders'; +import { loadFromCMS } from './cms.js'; + +export function myLoader(settings): Loader { + return { + name: 'cms-loader', + async load({ renderMarkdown, store }) { + const entries = await loadFromCMS(); + + store.clear(); + + for (const entry of entries) { + store.set(entry.id, { + id: entry.id, + data: entry, + // Assume each entry has a 'content' field with markdown content + rendered: await renderMarkdown(entry.content), + }); + } + }, + }; +} +``` + #### `generateDigest`

@@ -618,7 +657,7 @@ The format of the `RenderedContent` object is: /** Rendered HTML string. If present then `render(entry)` will return a component that renders this HTML. */ html: string; metadata?: { - /** Any images that are present in this entry. Relative to the {@link DataEntry} filePath. */ + /** Any images that are present in this entry. Relative to the DataEntry filePath. */ imagePaths?: Array; /** Any headings that are present in this file. Returned as `headings` from `render()` */ headings?: MarkdownHeading[]; @@ -629,3 +668,5 @@ The format of the `RenderedContent` object is: }; } ``` + +If the entry has Markdown content then you can use the [`renderMarkdown()`](#rendermarkdown) function to generate this object from the Markdown string. diff --git a/src/content/docs/en/reference/error-reference.mdx b/src/content/docs/en/reference/error-reference.mdx index 3c63160754e74..61cebf144e16c 100644 --- a/src/content/docs/en/reference/error-reference.mdx +++ b/src/content/docs/en/reference/error-reference.mdx @@ -95,6 +95,7 @@ The following reference is a complete list of the errors you may encounter while - [**CannotLoadFontProvider**](/en/reference/errors/cannot-load-font-provider/)
Cannot load font provider - [**ExperimentalFontsNotEnabled**](/en/reference/errors/experimental-fonts-not-enabled/)
Experimental fonts are not enabled - [**FontFamilyNotFound**](/en/reference/errors/font-family-not-found/)
Font family not found +- [**CspNotEnabled**](/en/reference/errors/csp-not-enabled/)
CSP feature isn't enabled ## CSS Errors diff --git a/src/content/docs/en/reference/errors/csp-not-enabled.mdx b/src/content/docs/en/reference/errors/csp-not-enabled.mdx new file mode 100644 index 0000000000000..244ac07ec9f23 --- /dev/null +++ b/src/content/docs/en/reference/errors/csp-not-enabled.mdx @@ -0,0 +1,22 @@ +--- +# NOTE: This file is auto-generated from 'scripts/error-docgen.mjs' +# Do not make edits to it directly, they will be overwritten. +# Instead, change this file: https://github.com/withastro/astro/blob/main/packages/astro/src/core/errors/errors-data.ts +# Translators, please remove this note and the component. + +title: CSP feature isn't enabled +i18nReady: true +githubURL: https://github.com/withastro/astro/blob/main/packages/astro/src/core/errors/errors-data.ts +--- +import DontEditWarning from '~/components/DontEditWarning.astro' + + + + +> The `experimental.csp` configuration isn't enabled. + +## What went wrong? +The CSP feature isn't enabled + + + diff --git a/src/content/docs/en/reference/experimental-flags/csp.mdx b/src/content/docs/en/reference/experimental-flags/csp.mdx new file mode 100644 index 0000000000000..7c0771c02d2ae --- /dev/null +++ b/src/content/docs/en/reference/experimental-flags/csp.mdx @@ -0,0 +1,413 @@ +--- +title: Experimental Content Security Policy +sidebar: + label: Content Security Policy +i18nReady: true +tableOfContents: + minHeadingLevel: 2 + maxHeadingLevel: 6 +--- + +import Since from '~/components/Since.astro' + +

+ + **Type:** `boolean | object`
+ **Default:** `false`
+ +

+ +Enables support for [Content Security Policy (CSP)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP) to help minimize certain types of security threats by controlling which resources a document is allowed to load. This provides additional protection against [cross-site scripting (XSS)](https://developer.mozilla.org/en-US/docs/Glossary/Cross-site_scripting) attacks. + +Enabling this feature adds additional security to **Astro's handling of processed and bundled scripts and styles** by default, and allows you to further configure these, and additional, content types. + +This experimental CSP feature has some limitations. Inline scripts are not supported out of the box, but you can [provide your own hashes](#hashes) for external and inline scripts. Additionally, [Astro's view transitions](/en/guides/view-transitions/) using the `` are not yet fully supported: when navigating from one page to another, some styles may not be applied and some scripts may not be executed. + + +:::note +Due to the nature of the Vite dev server, this feature isn't supported while working in `dev` mode. Instead, you can test this in your Astro project using `build` and `preview`. +::: + +To enable this feature, add the experimental flag in your Astro config: + +```js title="astro.config.mjs" ins={4-6} +import { defineConfig } from 'astro/config'; + +export default defineConfig({ + experimental: { + csp: true + } +}); +``` + +When enabled, Astro will add a `` element inside the `` element of each page. + +This element will have the `http-equiv="content-security-policy"` attribute, and the `content` attribute will provide values for the `script-src` and `style-src` [directives](#directives) based on the script and styles used in the page. + +```html + + + +``` + +## Configuration + +You can further customize the `` element by enabling this feature with a configuration object that includes additional options. + +### `algorithm` + +

+ +**Type:** `'SHA-256' | 'SHA-512' | 'SHA-384'`
+**Default:** `'SHA-256'`
+ +

+ +The [hash function](https://developer.mozilla.org/en-US/docs/Glossary/Hash_function) to use when generating the hashes of the styles and scripts emitted by Astro. + +```js title="astro.config.mjs" +import { defineConfig } from 'astro/config'; + +export default defineConfig({ + experimental: { + csp: { + algorithm: 'SHA-512' + } + } +}); +``` + +### `directives` + +

+ +**Type:** `CspDirective[]`
+**Default:** `[]`
+ +

+ +A list of [CSP directives](https://content-security-policy.com/#directive) that defines valid sources for specific content types. + +While Astro needs to control the `script-src` and `style-src` directives, it is possible to control other CSP directives using the `csp.directives` field. These directives are added to all pages. It accepts a list of type-safe directives: + +```js title="astro.config.mjs" +import { defineConfig } from 'astro/config'; + +export default defineConfig({ + experimental: { + csp: { + directives: [ + "default-src 'self'", + "image-src 'self' 'https://images.cdn.example.com'" + ] + } + } +}); +``` + +After the build, the `` element will add your directives into the `content` value alongside Astro's default directives: + +```html + +``` + +### `styleDirective` and `scriptDirective` + +

+ +**Type:** `object`
+**Default:** `{}`
+ +

+ +Configuration objects that allow you to override the default sources for the `style-src` and `script-src` directives with the [`resources`](#resources) property, or to provide additional [hashes](#hashes) to be rendered. + +These properties are added to all pages and **completely override Astro's default resources**, not add to them. Therefore, you must explicitly specify any default values that you want to be included. + +#### `resources` + +

+ +**Type:** `string[]`
+**Default:** `[]`
+ +

+ +A list of valid sources for the `script-src` and `style-src` directives. + +The `script-src` and `style-src` directives are handled by Astro by default, and use the `'self'` resource. This means that scripts and styles can only be downloaded by the current host (usually the current website). + +To override the default source, you can provide a list of resources instead. This will not include `'self'` by default, and must be included in this list if you wish to keep it. These resources are added to all pages. + +```js title="astro.config.mjs" +import { defineConfig } from 'astro/config'; + +export default defineConfig({ + experimental: { + csp: { + styleDirective: { + resources: [ + "self", + "https://styles.cdn.example.com" + ] + }, + scriptDirective: { + resources: [ + "https://cdn.example.com" + ] + } + } + } +}); +``` + +After the build, the `` element will instead apply your sources to the `style-src` and `script-src` directives: + +```html + + + +``` + +#### `hashes` + +

+ +**Type:** `CspHash[]`
+**Default:** `[]`
+ +

+ +A list of additional hashes to be rendered. + +If you have external scripts or styles that aren't generated by Astro, or inline scripts, this configuration option allows you to provide additional hashes to be rendered. + +You must provide hashes that start with `sha384-`, `sha512-` or `sha256-`. Other values will cause a validation error. These hashes are added to all pages. + + +```js title="astro.config.mjs" +import { defineConfig } from 'astro/config'; + +export default defineConfig({ + experimental: { + csp: { + styleDirective: { + hashes: [ + "sha384-styleHash", + "sha512-styleHash", + "sha256-styleHash" + ] + }, + scriptDirective: { + hashes: [ + "sha384-scriptHash", + "sha512-scriptHash", + "sha256-scriptHash" + ] + } + } + } +}); +``` + +After the build, the `` element will include your additional hashes in the `script-src` and `style-src` directives: + +```html + +``` + +#### `strictDynamic` + +

+ +**Type:** `boolean`
+**Default:** `false`
+ +

+ +Enables [the `strict-dynamic` keyword](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP#the_strict-dynamic_keyword) to support the dynamic injection of scripts. + +```js title="astro.config.mjs" +import { defineConfig } from 'astro/config'; + +export default defineConfig({ + experimental: { + csp: { + scriptDirective: { + strictDynamic: true + } + } + } +}); +``` +## Runtime APIs + +You can customize the `` element per page via runtime APIs available from the `Astro` global inside `.astro` components, or the `APIContext` type in endpoints and middleware. + +### `addDirective` + +

+ **Type:** `(directive: CspDirective) => void`
+ +

+ +Adds a single directive to the current page. You can call this method multiple times to add additional directives. + +```astro +--- +Astro.addDirective("default-src 'self'"); +Astro.addDirective("img-src 'self' 'https://images.cdn.example.com'"); +--- +``` + +After the build, the `` element for this individual page will add your directives to the included `style-src` and `script-src` directives: + +```html + +``` + +### `insertStyleResource` + +

+ **Type:** `(resource: string) => void`
+ +

+ +Inserts a new resource to be used for the `style-src` directive. + +```astro +--- +Astro.insertStyleResource("'https://styles.cdn.example.com'"); +--- +``` + +After the build, the `` element for this individual page will add your source to the default `style-src` directive: + +```html + +``` + + +### `addStyleHash` + +

+ **Type:** `(hash: CspHash) => void`
+ +

+ +Adds a new hash to the `style-src` directive. + +```astro +--- +Astro.addStyleHash("sha512-styleHash"); +--- +``` + +After the build, the `` element for this individual page will add your hash to the default `style-src` directive: + +```html + +``` + + +### `insertScriptResource` + +

+ **Type:** `(resource: string) => void`
+ +

+ +Inserts a new valid source to be used for the `script-src` directive. + + +```astro +--- +Astro.insertScriptResource("'https://scripts.cdn.example.com'"); +--- +``` + +After the build, the `` element for this individual page will add your source to the default `script-src` directive: + +```html + +``` + +### `addScriptHash` + +

+ **Type:** `(hash: CspHash) => void`
+ +

+ +Adds a new hash to the `script-src` directive. + + +```astro +--- +Astro.addScriptHash("sha512-scriptHash"); +--- +``` + +After the build, the `` element for this individual page will add your hash to the default `script-src` directive: + +```html + +```