Server source code is exposed to the public if sourcemaps are enabled
Description
Astro is a web framework for content-driven websites. A bug in the build process allows any unauthenticated user to read parts of the server source code. During build, along with client assets such as css and font files, the sourcemap files for the server code are moved to a publicly-accessible folder. Any outside party can read them with an unauthorized HTTP GET request to the same server hosting the rest of the website. While some server files are hashed, making their access obscure, the files corresponding to the file system router (those in src/pages) are predictably named. For example. the sourcemap file for src/pages/index.astro gets named dist/client/pages/index.astro.mjs.map. This vulnerability is the root cause of issue #12703, which links to a simple stackblitz project demonstrating the vulnerability. Upon build, notice the contents of the dist/client (referred to as config.build.client in astro code) folder. All astro servers make the folder in question accessible to the public internet without any authentication. It contains .map files corresponding to the code that runs on the server. All server-output projects on Astro 5 versions v5.0.3 through v5.0.7, that have sourcemaps enabled, either directly or through an add-on such as sentry, are affected. The fix for server-output projects was released in astro@5.0.8. Additionally, all static-output projects built using Astro 4 versions 4.16.17 or older, or Astro 5 versions 5.0.8 or older, that have sourcemaps enabled are also affected. The fix for static-output projects was released in astro@5.0.9, and backported to Astro v4 in astro@4.16.18. The immediate impact is limited to source code. Any secrets or environment variables are not exposed unless they are present verbatim in the source code. There is no immediate loss of integrity within the the vulnerable server. However, it is possible to subsequently discover another vulnerability via the revealed source code . There is no immediate impact to availability of the vulnerable server. However, the presence of an unsafe regular expression, for example, can quickly be exploited to subsequently compromise the availability. The fix for server-output projects was released in astro@5.0.8, and the fix for static-output projects was released in astro@5.0.9 and backported to Astro v4 in astro@4.16.18. Users are advised to update immediately if they are using sourcemaps or an integration that enables sourcemaps.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
astronpm | >= 5.0.0-alpha.0, < 5.0.8 | 5.0.8 |
astronpm | < 4.16.18 | 4.16.18 |
Affected products
1Patches
2039d022b1bbaClean sourcemaps from static output (#12749)
6 files changed · +40 −13
.changeset/tame-spoons-shop.md+5 −0 added@@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Clean server sourcemaps from static output
packages/astro/src/core/build/index.ts+3 −2 modified@@ -205,15 +205,16 @@ class AstroBuilder { key: keyPromise, }; - const { internals, ssrOutputChunkNames, contentFileNames } = await viteBuild(opts); + const { internals, ssrOutputChunkNames } = + await viteBuild(opts); const hasServerIslands = this.settings.serverIslandNameMap.size > 0; // Error if there are server islands but no adapter provided. if (hasServerIslands && this.settings.buildOutput !== 'server') { throw new AstroError(AstroErrorData.NoAdapterInstalledServerIslands); } - await staticBuild(opts, internals, ssrOutputChunkNames, contentFileNames); + await staticBuild(opts, internals, ssrOutputChunkNames); // Write any additionally generated assets to disk. this.timer.assetsStart = performance.now();
packages/astro/src/core/build/static-build.ts+10 −8 modified@@ -92,7 +92,6 @@ export async function viteBuild(opts: StaticBuildOptions) { const ssrOutputs = viteBuildReturnToRollupOutputs(ssrOutput); const clientOutputs = viteBuildReturnToRollupOutputs(clientOutput ?? []); await runPostBuildHooks(container, ssrOutputs, clientOutputs); - let contentFileNames: string[] | undefined = undefined; settings.timer.end('Client build'); // Free up memory @@ -112,20 +111,19 @@ export async function viteBuild(opts: StaticBuildOptions) { } } - return { internals, ssrOutputChunkNames, contentFileNames }; + return { internals, ssrOutputChunkNames }; } export async function staticBuild( opts: StaticBuildOptions, internals: BuildInternals, ssrOutputChunkNames: string[], - contentFileNames?: string[], ) { const { settings } = opts; if (settings.buildOutput === 'static') { settings.timer.start('Static generate'); await generatePages(opts, internals); - await cleanServerOutput(opts, ssrOutputChunkNames, contentFileNames, internals); + await cleanServerOutput(opts, ssrOutputChunkNames, internals); settings.timer.end('Static generate'); } else if (settings.buildOutput === 'server') { settings.timer.start('Server generate'); @@ -354,14 +352,12 @@ async function cleanStaticOutput(opts: StaticBuildOptions, internals: BuildInter async function cleanServerOutput( opts: StaticBuildOptions, ssrOutputChunkNames: string[], - contentFileNames: string[] | undefined, internals: BuildInternals, ) { const out = getOutDirWithinCwd(opts.settings.config.outDir); // The SSR output chunks for Astro are all .mjs files const files = ssrOutputChunkNames - .filter((f) => f.endsWith('.mjs')) - .concat(contentFileNames ?? []); + .filter((f) => f.endsWith('.mjs')); if (internals.manifestFileName) { files.push(internals.manifestFileName); } @@ -370,7 +366,11 @@ async function cleanServerOutput( await Promise.all( files.map(async (filename) => { const url = new URL(filename, out); - await fs.promises.rm(url); + const map = new URL(url + '.map'); + await Promise.all([ + fs.promises.rm(url), + fs.promises.rm(new URL(map)).catch((e) => {}) + ]); }), ); @@ -426,6 +426,8 @@ async function ssrMoveAssets(opts: StaticBuildOptions) { cwd: fileURLToPath(serverAssets), }); + console.log("FILES2", files); + if (files.length > 0) { await Promise.all( files.map(async function moveAsset(filename) {
packages/astro/test/astro-basic.test.js+8 −0 modified@@ -167,6 +167,14 @@ describe('Astro basic build', () => { assert.doesNotMatch(otherHtml, /<style/); }); + it('server sourcemaps not included in output', async () => { + const files = await fixture.readdir('/'); + const hasSourcemaps = files.some(fileName => { + return fileName.endsWith('.map'); + }); + assert.equal(hasSourcemaps, false, 'no sourcemap files in output'); + }); + describe('preview', () => { it('returns 200 for valid URLs', async () => { const result = await fixture.fetch('/');
packages/astro/test/fixtures/astro-basic/astro.config.mjs+6 −1 modified@@ -6,5 +6,10 @@ import { defineConfig } from 'astro/config'; export default defineConfig({ integrations: [preact(), mdx()], // make sure CLI flags have precedence - server: () => ({ port: 4321 }) + server: () => ({ port: 4321 }), + vite: { + build: { + sourcemap: true, + } + } });
packages/astro/test/sourcemap.test.js+8 −2 modified@@ -17,7 +17,13 @@ describe('Sourcemap', async () => { }); it('Builds non-empty sourcemap', async () => { - const map = await fixture.readFile('renderers.mjs.map'); - assert.equal(map.includes('"sources":[]'), false); + const assets = await fixture.readdir('/_astro'); + const maps = assets.filter(file => file.endsWith('.map')); + assert.ok(maps.length > 0, 'got source maps'); + for(const mapName of maps) { + const filename = `/_astro/${mapName}`; + const map = await fixture.readFile(filename); + assert.equal(map.includes('"sources":[]'), false); + } }); });
c879f501ff01Revert "fix custom `assetFileNames` issue (#12449)" (#12746)
7 files changed · +29 −30
.changeset/loud-emus-look.md+5 −0 added@@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Remove all assets created from the server build
packages/astro/src/core/build/index.ts+2 −2 modified@@ -205,7 +205,7 @@ class AstroBuilder { key: keyPromise, }; - const { internals, ssrOutputChunkNames, ssrOutputAssetNames, contentFileNames } = + const { internals, ssrOutputChunkNames, contentFileNames } = await viteBuild(opts); const hasServerIslands = this.settings.serverIslandNameMap.size > 0; @@ -214,7 +214,7 @@ class AstroBuilder { throw new AstroError(AstroErrorData.NoAdapterInstalledServerIslands); } - await staticBuild(opts, internals, ssrOutputChunkNames, ssrOutputAssetNames, contentFileNames); + await staticBuild(opts, internals, ssrOutputChunkNames, contentFileNames); // Write any additionally generated assets to disk. this.timer.assetsStart = performance.now();
packages/astro/src/core/build/static-build.ts+15 −13 modified@@ -104,26 +104,21 @@ export async function viteBuild(opts: StaticBuildOptions) { // For static builds, the SSR output won't be needed anymore after page generation. // We keep track of the names here so we only remove these specific files when finished. const ssrOutputChunkNames: string[] = []; - const ssrOutputAssetNames: string[] = []; for (const output of ssrOutputs) { for (const chunk of output.output) { if (chunk.type === 'chunk') { ssrOutputChunkNames.push(chunk.fileName); } - if (chunk.type === 'asset') { - ssrOutputAssetNames.push(chunk.fileName); - } } } - return { internals, ssrOutputChunkNames, ssrOutputAssetNames, contentFileNames }; + return { internals, ssrOutputChunkNames, contentFileNames }; } export async function staticBuild( opts: StaticBuildOptions, internals: BuildInternals, ssrOutputChunkNames: string[], - ssrOutputAssetNames: string[], contentFileNames?: string[], ) { const { settings } = opts; @@ -136,7 +131,7 @@ export async function staticBuild( settings.timer.start('Server generate'); await generatePages(opts, internals); await cleanStaticOutput(opts, internals); - await ssrMoveAssets(opts, ssrOutputAssetNames); + await ssrMoveAssets(opts); settings.timer.end('Server generate'); } } @@ -417,21 +412,28 @@ export async function copyFiles(fromFolder: URL, toFolder: URL, includeDotfiles ); } -async function ssrMoveAssets(opts: StaticBuildOptions, ssrOutputAssetNames: string[]) { +async function ssrMoveAssets(opts: StaticBuildOptions) { opts.logger.info('build', 'Rearranging server assets...'); const serverRoot = opts.settings.buildOutput === 'static' ? opts.settings.config.build.client : opts.settings.config.build.server; const clientRoot = opts.settings.config.build.client; - if (ssrOutputAssetNames.length > 0) { + const assets = opts.settings.config.build.assets; + const serverAssets = new URL(`./${assets}/`, appendForwardSlash(serverRoot.toString())); + const clientAssets = new URL(`./${assets}/`, appendForwardSlash(clientRoot.toString())); + const files = await glob(`**/*`, { + cwd: fileURLToPath(serverAssets), + }); + + if (files.length > 0) { await Promise.all( - ssrOutputAssetNames.map(async function moveAsset(filename) { - const currentUrl = new URL(filename, appendForwardSlash(serverRoot.toString())); - const clientUrl = new URL(filename, appendForwardSlash(clientRoot.toString())); + files.map(async function moveAsset(filename) { + const currentUrl = new URL(filename, appendForwardSlash(serverAssets.toString())); + const clientUrl = new URL(filename, appendForwardSlash(clientAssets.toString())); const dir = new URL(path.parse(clientUrl.href).dir); // It can't find this file because the user defines a custom path - // that includes the folder paths in `assetFileNames` + // that includes the folder paths in `assetFileNames if (!fs.existsSync(dir)) await fs.promises.mkdir(dir, { recursive: true }); return fs.promises.rename(currentUrl, clientUrl); }),
packages/astro/test/custom-assets-name.test.js+5 −11 modified@@ -2,7 +2,7 @@ import assert from 'node:assert/strict'; import { before, describe, it } from 'node:test'; import { loadFixture } from './test-utils.js'; -describe('custom assets name function', () => { +describe('custom the assets name function', () => { /** @type {import('./test-utils').Fixture} */ let fixture; @@ -14,15 +14,9 @@ describe('custom assets name function', () => { await fixture.build(); }); - it('should load CSS file from custom client assets path', async () => { - const files = await fixture.readdir('/client/assets/css'); - const cssFile = files.find((file) => file === 'a.css'); - assert.ok(cssFile, 'Expected CSS file to exist at client/assets/css/a.css'); - }); - - it('should load image file from custom client assets path', async () => { - const files = await fixture.readdir('/client/imgAssets'); - const imgFile = files.find((file) => file === 'penguin1.jpg'); - assert.ok(imgFile, 'Expected image file to exist at client/imgAssets/penguin1.jpg'); + it('It cant find this file cause the node throws an error if the users custom a path that includes the folder path', async () => { + const csslength = await fixture.readFile('client/assets/css/a.css'); + /** @type {Set<string>} */ + assert.equal(!!csslength, true); }); });
packages/astro/test/fixtures/custom-assets-name/astro.config.mjs+2 −2 modified@@ -18,14 +18,14 @@ export default defineConfig({ const { ext, dir, base } = path.parse(option.name); if (ext == ".css") return path.join(dir, "assets/css", 'a.css'); - return "imgAssets/[name].[ext]"; + return "assets/img/[name].[ext]"; } } } } }, build: { - assets: 'assetsDir' + assets: 'assets' }, output: "server", adapter: node({
packages/astro/test/fixtures/custom-assets-name/src/images/penguin1.jpg+0 −0 removedpackages/astro/test/fixtures/custom-assets-name/src/pages/index.astro+0 −2 modified@@ -1,6 +1,5 @@ --- const title = 'My App'; -import p1Url from '../images/penguin1.jpg'; --- <html> @@ -9,7 +8,6 @@ import p1Url from '../images/penguin1.jpg'; </head> <body> <h1>{title}</h1> - <img src={p1Url.src}/> </body> </html>
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
8- github.com/advisories/GHSA-49w6-73cw-chjrghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2024-56159ghsaADVISORY
- github.com/getsentry/sentry-javascript/blob/develop/packages/astro/src/integration/index.tsghsax_refsource_MISCWEB
- github.com/withastro/astro/blob/176fe9f113fd912f9b61e848b00bbcfecd6d5c2c/packages/astro/src/core/build/static-build.tsghsax_refsource_MISCWEB
- github.com/withastro/astro/commit/039d022b1bbaacf9ea83071d27affc5318e0e515ghsaWEB
- github.com/withastro/astro/commit/c879f501ff01b1a3c577de776a1f7100d78f8dd5ghsaWEB
- github.com/withastro/astro/issues/12703ghsax_refsource_MISCWEB
- github.com/withastro/astro/security/advisories/GHSA-49w6-73cw-chjrghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.