Astro 5.9 has got your site on lockdown, with experimental support for Content Security Policy, rendering Markdown in content loaders, and more!
🔒 Batten-down the hatches:
- Experimental Content Security Policy support
- Rendering Markdown in content loaders
- Disable default styles in experimental responsive images
- Allow adapters to suppress logs about feature support
To upgrade an existing project, use the automated @astrojs/upgrade
CLI tool. Alternatively, upgrade manually by running the upgrade command for your package manager:
# Recommended:npx @astrojs/upgrade
# Manual:npm install astro@latestpnpm upgrade astro --latestyarn upgrade astro --latest
Experimental Content Security Policy support
Cross-site scripting (XSS) attacks are some of the most common threats faced by websites. By default, web pages can load whichever scripts and styles they want, from wherever they want. The most powerful defense against XSS attacks is to limit this. A Content Security Policy (CSP) lets you do that, with tools to lock down the page to a list of trusted resources.
While these are powerful tools, they can be tricky to implement by hand: it’s much easier if your framework can do it for you. Astro 5.9 introduces experimental support for CSP out of the box, making it easier to secure your Astro projects. This is Astro’s most upvoted feature request so far, and we certainly took our time to implement it. We hope this is worth the wait!
We designed the feature to work in all Astro render modes (static pages, dynamic pages and single page applications),
with maximum flexibility and type-safety in mind. You heard that right! You can ditch the unpleasant unsafe-inline
workaround, use all the Astro features that you like,
any adapter for any runtime, and add an extra layer of security to your site.
How it works
One of the reasons why users like Astro is because it works everywhere – static hosts, serverless hosts, Node.js, edge runtimes, and with many frontend libraries (React, Vue, Svelte, etc). Because of this, an Astro CSP solution must work everywhere with any library.
We started by considering two common approaches to supporting CSP:
- The
nonce
header, which generates a random value for each request and injects it into the HTML. - Calculating hashes of all the resources shipped to the browser, ensuring that only the scripts and styles that you expect to run will be executed.
While the nonce
header is straightforward to implement, it requires a server/edge function that is able to generate
the random value of nonce
, add the Content-Security-Policy
header to the Response
object, and rewrite the HTML to inject the nonce
value for each <script>
and <style>
element.
This solution wouldn’t work for websites that are served from static hosts e.g. GitHub pages, and it wouldn’t work for Single Page Applications (SPAs)
where scripts and styles might be injected dynamically during the lifecycle of the application.
The generation of hashes is more complex to implement because it requires knowing the exact content of every script and stylesheet on the page. However, it can support more use cases than the nonce
header, so our choice was clear even though another fork in the road was waiting for us.
The CSP standard allows hashes to be provided to the browser either via the Response
header called Content-Security-Policy
, or using a <meta>
element called http-equiv='content-security-policy'
. As you may have guessed, using the Response
header wouldn’t work for Astro, because it would leave out static websites and SPAs. For this reason, we decided to use the <meta>
element to provide the CSP to the browser.
The end result is that Astro’s CSP will generate the <meta>
element for you, with all the hashes of the scripts and styles that will be used in the page, even the ones that will be loaded dynamically!
Usage
To try out Astro’s CSP in your own projects today, enable the new experimental flag:
import { defineConfig } from "astro/config"
export default defineConfig({ experimental: { csp: true }})
If you already use the Content-Security-Policy
header in your website via middleware or other means, you can still use it, and the browser will use the stricter policy of the header and the <meta>
element.
But we didn’t stop there! Astro gives you further control over that <meta>
content via configuration. You can change the default algorithm, insert additional directives, and more:
import { defineConfig } from "astro/config"
export default defineConfig({ experimental: { csp: { // change the default algorithm algorithm: "`SHA-512", // insert additional directives directives: [ "default-src: 'self'", "image-src: 'https://images.cdn.example.com'" ], // add more information to the `style-src` directive styleDirective: { hashes: [ "sha384-somehash" // hash generated for some external style e.g. white label, etc. ], // **Override** default resources resources: ["self", "https://styles.cdn.example.com"] },
// add more information to the `style-src` directive scriptDirective: { hashes: [ "sha384-somehash" // hash generated for some external script e.g. analytics, jQuery, etc. ], // **Override** default resources resources: ["self", "https://script.cdn.example.com"], // Toggle the keyword `strict-dynamic` scriptDynamic: true } } }})
For more details, see the experimental CSP documentation.
Rendering Markdown in content loaders
Astro content collections have always supported rendering Markdown files using the render()
function and <Content />
component, and Astro 5 added support for any loader to render any HTML content. However if you wanted to render Markdown content in a content loader, you had to handle the Markdown parsing yourself. This could be confusing as it was inconsistent with how Markdown was rendered in other parts of your site, and didn’t use the same Markdown configuration.
Astro 5.9 adds a new helper function to the loader context - renderMarkdown
- that allows you to render Markdown content directly within your loaders. It uses the same settings and plugins as the renderer used for Markdown files in Astro, including any Markdown settings configured in the Astro project.
The renderMarkdown
function is available in the loader context, and returns an object with two properties: html
and metadata
. These match the rendered
property of content entries in content collections, so they can be used to easily add support for render()
in a loader.
import type { Loader } from 'astro/loaders';import { loadFromCMS } from './cms';
export function myLoader(settings): Loader { return { name: 'my-loader', async load({ renderMarkdown, store }) { const entries = await loadFromCMS(); store.clear(); for (const entry of entries) { // Assume each entry has a 'content' field with Markdown content store.set(entry.id, { id: entry.id, data: entry, rendered: await renderMarkdown(entry.content), }); } }, };}
Now, even with your custom loader, you can access render()
and <Content />
just as if your Markdown content were stored locally in your project:
---import { getEntry, render } from 'astro:content';const entry = await getEntry('my-collection', Astro.params.id);const { Content } = await render(entry);---<Content />
For more details, see the content loaders documentation.
Disable default styles in experimental responsive images
When using experimental responsive images, Astro applies default styles to ensure the images resize correctly. In most cases this is what you want – and they are applied with low specificity so your own styles override them.
However in some cases you may want to disable these default styles entirely. This is particularly useful when using Tailwind 4, because it uses CSS cascade layers to apply styles, making it difficult to override Astro’s default styles.
Astro 5.8.1 added a new image.experimentalDefaultStyles
boolean configuration option for applying these default styles. It defaults to true
, providing the current responsive image behavior. But if you’d rather not fight with Tailwind 4, setting !important
styles everywhere, you can simply disable this option in your Astro config:
export default { image: { experimentalDefaultStyles: false, }, experimental: { responsiveImages: true, },};
For more details, see the experimental responsive images documentation.
Allow adapters to suppress logs about feature support
Astro adapters can declare whether or not they support certain Astro features, and whether that support is stable or experimental.
During builds, Astro will log a warning or error if a site is using features that are not supported by the adapter. However, sometimes an adapter may need to log more specific messages to help users resolve the issue. Previously this could be confusing, as these custom logs were printed in addition to the Astro-generated ones and may appear to contradict them.
Astro 5.9 adds an option to allow adapters to suppress the logging for unsupported features. An adapter author can add suppress: "all"
(to suppress both the default and custom message) or suppress: "default"
(to only suppress Astro’s default message):
setAdapter({ name: 'my-astro-integration', supportedAstroFeatures: { staticOutput: "stable", hybridOutput: "stable", sharpImageService: { support: "limited", message: "The sharp image service isn't available in the deploy environment, but will be used by prerendered pages on build.", suppress: "default", }, }})
For more details, see the Adapters API reference.
Community
The Astro core team is:
Ben Holmes , Caleb Jasik , Chris Swithinbank , Emanuele Stoppa , Erika , Florian Lefebvre , Fred Schott , Fuzzy , HiDeoo , Luiz Ferraz , Matt Kane , Matthew Phillips , Nate Moore , Reuben Tier , Sarah Rainsberger , and Yan Thomas .
Thanks to all the other contributors who helped make Astro 5.9 possible with code and docs additions and improvements, including:
☘, Adriel Martinez, Alexander Niebuhr, Ankur Oberoi, Ariel K, Armand Philippot, Arpan Patel, Ben Limmer, Bjorn Lu, Bugo, Cansin Acarer, Daniel Puscher, Elliot Dong, Hiromasa Fujimori, Hunter Bertoson, Igor Teplostanski, JiPai, Joe, Jonás PerusquÃa Morales, Junseong Park, Juraj Kapsz, kato takeshi, Kenzo Fachin, knj, liruifengv, Louis Escher, Nin3, Paul Valladares, Reuben Tier, Robin Bühler, Stephen Hendricks, sugardave, Thomas Bonnet, and vivek lokhande.