Local File Inclusion vulnerability in Gatsby
Description
Gatsby develop server local file inclusion via __file-code-frame and __original-stack-frame paths allows arbitrary file read if server is exposed to other interfaces.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Gatsby develop server local file inclusion via `__file-code-frame` and `__original-stack-frame` paths allows arbitrary file read if server is exposed to other interfaces.
Vulnerability
Overview
CVE-2023-34238 is a Local File Inclusion (LFI) vulnerability in the Gatsby framework, a free and open-source React-based framework. The flaw exists in the __file-code-frame and __original-stack-frame endpoints of the Gatsby development server (gatsby develop). Prior to patched versions 4.25.7 and 5.9.1, these endpoints did not properly restrict file paths, allowing arbitrary files within the server's scope to be served [1].
Exploitation
Conditions
Exploitation requires the Gatsby develop server to be running. By default, the server binds only to 127.0.0.1, making it accessible solely from the local machine. However, if an attacker can induce a user to expose the server to other network interfaces via options like --host 0.0.0.0, -H 0.0.0.0, or the GATSBY_HOST=0.0.0.0 environment variable, then remote exploitation becomes possible [1]. The vulnerability is triggered by crafting a request to the __file-code-frame or __original-stack-frame paths with a filePath or moduleId parameter pointing to a file outside the project.
Impact
A successful attack allows an unauthenticated attacker to read any file that the development server process can access, potentially exposing source code, configuration files, or other sensitive data [1]. The NVD CVSS 4.0 vector indicates a high severity, with significant confidentiality impact (see NVD metrics) [1].
Mitigation
Patches are available in Gatsby versions 4.25.7 and 5.9.1 [1]. The fix, as seen in the associated commits [3][4], introduces a check to ensure that only files within the compilation scope are served, preventing the inclusion of arbitrary files. Users unable to upgrade should avoid exposing the development server to any network interfaces other than localhost [1].
AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
gatsbynpm | < 4.25.7 | 4.25.7 |
gatsbynpm | >= 5.0.0, < 5.9.1 | 5.9.1 |
Affected products
2Patches
2fc22f4ba3ad7fix(gatsby): don't serve codeframes for files outside of compilation (#38059) (#38063)
5 files changed · +80 −3
e2e-tests/development-runtime/cypress/integration/hot-reloading/error-handling/overlay-endpoints.js+11 −0 added@@ -0,0 +1,11 @@ +const cwd = Cypress.config(`projectRoot`) + +describe(`overlay handlers don't serve unrelated files`, () => { + it(`__file-code-frame`, () => { + cy.request( + `__file-code-frame?filePath=${cwd}/SHOULD_NOT_SERVE&lineNumber=0` + ).should(response => { + expect(response.body.codeFrame).not.to.match(/CYPRESS-MARKER/) + }) + }) +})
e2e-tests/development-runtime/SHOULD_NOT_SERVE+1 −1 modified@@ -1 +1 @@ -this file shouldn't be allowed to be served +this file shouldn't be allowed to be served. CYPRESS-MARKER
packages/gatsby/src/commands/build-html.ts+6 −0 modified@@ -20,6 +20,8 @@ import { PackageJson } from "../.." import { IPageDataWithQueryResult } from "../utils/page-data" import type { GatsbyWorkerPool } from "../utils/worker/pool" +import { setFilesFromDevelopHtmlCompilation } from "../utils/webpack/utils/is-file-inside-compilations" + type IActivity = any // TODO const isPreview = process.env.GATSBY_IS_PREVIEW === `true` @@ -211,6 +213,10 @@ const doBuildRenderer = async ( ) } + if (stage === `develop-html`) { + setFilesFromDevelopHtmlCompilation(stats.compilation) + } + // render-page.js is hard coded in webpack.config return { rendererPath: `${directory}/${ROUTES_DIRECTORY}render-page.js`,
packages/gatsby/src/utils/start-server.ts+20 −2 modified@@ -1,7 +1,7 @@ import webpackHotMiddleware from "@gatsbyjs/webpack-hot-middleware" import webpackDevMiddleware from "webpack-dev-middleware" import got, { Method } from "got" -import webpack from "webpack" +import webpack, { Compilation } from "webpack" import express from "express" import compression from "compression" import { graphqlHTTP, OptionsData } from "express-graphql" @@ -55,6 +55,7 @@ import { getPageMode } from "./page-mode" import { configureTrailingSlash } from "./express-middlewares" import type { Express } from "express" import { addImageRoutes } from "gatsby-plugin-utils/polyfill-remote-file" +import { isFileInsideCompilations } from "./webpack/utils/is-file-inside-compilations" type ActivityTracker = any // TODO: Replace this with proper type once reporter is typed @@ -502,7 +503,24 @@ export async function startServer( return } - const sourceContent = await fs.readFile(filePath, `utf-8`) + const absolutePath = path.resolve( + store.getState().program.directory, + filePath + ) + + const compilation: Compilation = + res.locals?.webpack?.devMiddleware?.stats?.compilation + if (!compilation) { + res.json(emptyResponse) + return + } + + if (!isFileInsideCompilations(absolutePath, compilation)) { + res.json(emptyResponse) + return + } + + const sourceContent = await fs.readFile(absolutePath, `utf-8`) const codeFrame = codeFrameColumns( sourceContent,
packages/gatsby/src/utils/webpack/utils/is-file-inside-compilations.ts+42 −0 added@@ -0,0 +1,42 @@ +import { Compilation, NormalModule } from "webpack" + +const filesInsideDevelopHtmlCompilation = new Set<string>() + +function removeQueryParams(path: string): string { + return path.split(`?`)[0] +} + +export function setFilesFromDevelopHtmlCompilation( + developHtmlCompilation: Compilation +): void { + filesInsideDevelopHtmlCompilation.clear() + + for (const module of developHtmlCompilation.modules) { + if (module instanceof NormalModule && module.resource) { + filesInsideDevelopHtmlCompilation.add(removeQueryParams(module.resource)) + } + } +} + +/** + * Checks if a file is inside either `develop` or `develop-html` compilation. Used to determine if + * we should generate codeframe for this file for error overlay. + */ +export function isFileInsideCompilations( + absolutePath: string, + developBrowserCompilation: Compilation +): boolean { + if (filesInsideDevelopHtmlCompilation.has(absolutePath)) { + return true + } + + for (const module of developBrowserCompilation.modules) { + if (module instanceof NormalModule && module.resource) { + if (absolutePath === removeQueryParams(module.resource)) { + return true + } + } + } + + return false +}
ae5a654eb346fix(gatsby): don't serve codeframes for files outside of compilation (#38059) (#38062)
5 files changed · +101 −3
e2e-tests/development-runtime/cypress/integration/hot-reloading/error-handling/overlay-endpoints.js+20 −0 added@@ -0,0 +1,20 @@ +const cwd = Cypress.config(`projectRoot`) + +describe(`overlay handlers don't serve unrelated files`, () => { + it(`__file-code-frame`, () => { + cy.request( + `__file-code-frame?filePath=${cwd}/SHOULD_NOT_SERVE&lineNumber=0` + ).should(response => { + expect(response.body.codeFrame).not.to.match(/CYPRESS-MARKER/) + }) + }) + + it(`__original-stack-frame`, () => { + cy.request( + `__original-stack-frame?moduleId=${cwd}/SHOULD_NOT_SERVE&lineNumber=0&skipSourceMap=1` + ).should(response => { + expect(response.body.codeFrame).not.to.match(/CYPRESS-MARKER/) + expect(response.body.sourceContent).not.to.match(/CYPRESS-MARKER/) + }) + }) +})
e2e-tests/development-runtime/SHOULD_NOT_SERVE+1 −1 modified@@ -1 +1 @@ -this file shouldn't be allowed to be served +this file shouldn't be allowed to be served. CYPRESS-MARKER
packages/gatsby/src/commands/build-html.ts+5 −0 modified@@ -26,6 +26,7 @@ import type { ISlicePropsEntry } from "../utils/worker/child/render-html" import { getPageMode } from "../utils/page-mode" import { extractUndefinedGlobal } from "../utils/extract-undefined-global" import { modifyPageDataForErrorMessage } from "../utils/page-data" +import { setFilesFromDevelopHtmlCompilation } from "../utils/webpack/utils/is-file-inside-compilations" type IActivity = any // TODO @@ -218,6 +219,10 @@ const doBuildRenderer = async ( ) } + if (stage === `develop-html`) { + setFilesFromDevelopHtmlCompilation(stats.compilation) + } + // render-page.js is hard coded in webpack.config return { rendererPath: `${directory}/${ROUTES_DIRECTORY}render-page.js`,
packages/gatsby/src/utils/start-server.ts+33 −2 modified@@ -1,7 +1,7 @@ import webpackHotMiddleware from "@gatsbyjs/webpack-hot-middleware" import webpackDevMiddleware from "webpack-dev-middleware" import got, { Method } from "got" -import webpack from "webpack" +import webpack, { Compilation } from "webpack" import express from "express" import compression from "compression" import { createHandler as createGraphqlEndpointHandler } from "graphql-http/lib/use/express" @@ -50,6 +50,7 @@ import { getPageMode } from "./page-mode" import { configureTrailingSlash } from "./express-middlewares" import type { Express } from "express" import { addImageRoutes } from "gatsby-plugin-utils/polyfill-remote-file" +import { isFileInsideCompilations } from "./webpack/utils/is-file-inside-compilations" type ActivityTracker = any // TODO: Replace this with proper type once reporter is typed @@ -413,6 +414,19 @@ export async function startServer( store.getState().program.directory, req.query.moduleId as string ) + + const compilation: Compilation = + res.locals?.webpack?.devMiddleware?.stats?.compilation + if (!compilation) { + res.json(emptyResponse) + return + } + + if (!isFileInsideCompilations(absolutePath, compilation)) { + res.json(emptyResponse) + return + } + try { sourceContent = fs.readFileSync(absolutePath, `utf-8`) } catch (e) { @@ -540,7 +554,24 @@ export async function startServer( return } - const sourceContent = await fs.readFile(filePath, `utf-8`) + const absolutePath = path.resolve( + store.getState().program.directory, + filePath + ) + + const compilation: Compilation = + res.locals?.webpack?.devMiddleware?.stats?.compilation + if (!compilation) { + res.json(emptyResponse) + return + } + + if (!isFileInsideCompilations(absolutePath, compilation)) { + res.json(emptyResponse) + return + } + + const sourceContent = await fs.readFile(absolutePath, `utf-8`) const codeFrame = codeFrameColumns( sourceContent,
packages/gatsby/src/utils/webpack/utils/is-file-inside-compilations.ts+42 −0 added@@ -0,0 +1,42 @@ +import { Compilation, NormalModule } from "webpack" + +const filesInsideDevelopHtmlCompilation = new Set<string>() + +function removeQueryParams(path: string): string { + return path.split(`?`)[0] +} + +export function setFilesFromDevelopHtmlCompilation( + developHtmlCompilation: Compilation +): void { + filesInsideDevelopHtmlCompilation.clear() + + for (const module of developHtmlCompilation.modules) { + if (module instanceof NormalModule && module.resource) { + filesInsideDevelopHtmlCompilation.add(removeQueryParams(module.resource)) + } + } +} + +/** + * Checks if a file is inside either `develop` or `develop-html` compilation. Used to determine if + * we should generate codeframe for this file for error overlay. + */ +export function isFileInsideCompilations( + absolutePath: string, + developBrowserCompilation: Compilation +): boolean { + if (filesInsideDevelopHtmlCompilation.has(absolutePath)) { + return true + } + + for (const module of developBrowserCompilation.modules) { + if (module instanceof NormalModule && module.resource) { + if (absolutePath === removeQueryParams(module.resource)) { + return true + } + } + } + + return false +}
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-c6f8-8r25-c4gcghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2023-34238ghsaADVISORY
- github.com/gatsbyjs/gatsby/commit/ae5a654eb346b2e7a9d341b809b2f82d34c0f17cghsax_refsource_MISCWEB
- github.com/gatsbyjs/gatsby/commit/fc22f4ba3ad7ca5fb3592f38f4f0ca8ae60b4bf7ghsax_refsource_MISCWEB
- github.com/gatsbyjs/gatsby/security/advisories/GHSA-c6f8-8r25-c4gcghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.