VYPR
Moderate severityNVD Advisory· Published Apr 17, 2023· Updated Feb 5, 2025

Path traversal vulnerability in gatsby-plugin-sharp

CVE-2023-30548

Description

Path traversal in gatsby-plugin-sharp allows reading arbitrary files when the Gatsby develop server is exposed to untrusted networks; patched in versions 5.8.1 and 4.25.1.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Path traversal in gatsby-plugin-sharp allows reading arbitrary files when the Gatsby develop server is exposed to untrusted networks; patched in versions 5.8.1 and 4.25.1.

Vulnerability

Overview

CVE-2023-30548 is a path traversal vulnerability in the gatsby-plugin-sharp plugin for the Gatsby framework. The plugin fails to properly validate file paths when serving static assets during development, allowing an attacker to traverse outside the project directory. This issue affects versions prior to 5.8.1 and 4.25.1 [1].

Exploitation

Conditions

By default, the Gatsby develop server (gatsby develop) listens only on localhost (127.0.0.1), making exploitation impossible from remote networks. However, if a developer explicitly exposes the server to other interfaces (e.g., using --host 0.0.0.0 or the GATSBY_HOST=0.0.0.0 environment variable), an attacker can send crafted HTTP requests containing path traversal sequences such as %2e%2e to read arbitrary files. The commit diffs [3][4] include a test that verifies the fix by attempting to access a file outside the project root.

Impact

Successful exploitation grants an attacker read access to all files within the scope of the server process. This could expose sensitive information such as source code, configuration files, environment variables, or other data stored on the server's filesystem [1].

Mitigation

The vulnerability is patched in gatsby-plugin-sharp@5.8.1 and gatsby-plugin-sharp@4.25.1. Users are strongly encouraged to upgrade. For those unable to upgrade immediately, the risk can be mitigated by ensuring the develop server is not exposed to untrusted networks; the default localhost-only configuration is safe [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.

PackageAffected versionsPatched versions
gatsby-plugin-sharpnpm
>= 5.0.0, < 5.8.15.8.1
gatsby-plugin-sharpnpm
< 4.25.14.25.1

Affected products

2

Patches

2
dcf88ed01df2

fix(gatsby-plugin-sharp): don't serve static assets that are not result of currently triggered deferred job (#37796) (#37802)

https://github.com/gatsbyjs/gatsbyGatsbyJS BotMar 29, 2023via ghsa
7 files changed · +25 8
  • e2e-tests/development-runtime/package.json+3 2 modified
    @@ -32,14 +32,15 @@
       "license": "MIT",
       "scripts": {
         "build": "gatsby build",
    -    "develop": "cross-env CYPRESS_SUPPORT=y ENABLE_GATSBY_REFRESH_ENDPOINT=true GATSBY_EXPERIMENTAL_QUERY_ON_DEMAND=y gatsby develop",
    +    "develop": "cross-env CYPRESS_SUPPORT=y ENABLE_GATSBY_REFRESH_ENDPOINT=true GATSBY_EXPERIMENTAL_QUERY_ON_DEMAND=y GATSBY_ENABLE_LAZY_IMAGES_IN_CI=y gatsby develop",
         "serve-static-files": "node ./serve-static-files.mjs",
         "serve": "gatsby serve",
         "clean": "gatsby clean",
         "typecheck": "tsc --noEmit",
         "start": "npm run develop",
         "format": "prettier --write \"src/**/*.js\"",
         "test": "npm run start-server-and-test || (npm run reset && exit 1)",
    +    "test:dir-traversel-access": "! curl -f http://localhost:8000/%2e%2e/SHOULD_NOT_SERVE",
         "posttest": "npm run reset",
         "reset": "node scripts/reset.js",
         "reset:preview": "curl -X POST http://localhost:8000/__refresh",
    @@ -55,7 +56,7 @@
         "playwright:debug": "playwright test --project=chromium --debug",
         "start-server-and-test:playwright": "start-server-and-test develop http://localhost:8000 serve-static-files http://localhost:8888 playwright",
         "start-server-and-test:playwright-debug": "start-server-and-test develop http://localhost:8000 serve-static-files http://localhost:8888 playwright:debug",
    -    "combined": "npm run playwright && npm run cy:run",
    +    "combined": "npm run playwright && npm run cy:run && npm run test:dir-traversel-access",
         "postinstall": "playwright install chromium"
       },
       "devDependencies": {
    
  • e2e-tests/development-runtime/SHOULD_NOT_SERVE+1 0 added
    @@ -0,0 +1 @@
    +this file shouldn't be allowed to be served
    
  • e2e-tests/production-runtime/package.json+2 1 modified
    @@ -36,6 +36,7 @@
         "start": "npm run develop",
         "clean": "gatsby clean",
         "test": "npm run build && npm run start-server-and-test && npm run test-env-vars",
    +    "test:dir-traversel-access": "! curl -f http://localhost:9000/%2e%2e/SHOULD_NOT_SERVE",
         "test:offline": "npm run build:offline && yarn start-server-and-test:offline && npm run test-env-vars",
         "test-env-vars": " node __tests__/env-vars.js",
         "start-server-and-test": "start-server-and-test serve http://localhost:9000 serve-static-files http://localhost:8888 combined",
    @@ -51,7 +52,7 @@
         "playwright:debug": "playwright test --project=chromium --debug",
         "start-server-and-test:playwright": "start-server-and-test serve http://localhost:9000 serve-static-files http://localhost:8888 playwright",
         "start-server-and-test:playwright-debug": "start-server-and-test serve http://localhost:9000 serve-static-files http://localhost:8888 playwright:debug",
    -    "combined": "npm run playwright && npm run cy:run",
    +    "combined": "npm run playwright && npm run cy:run && npm run test:dir-traversel-access",
         "postinstall": "playwright install chromium"
       },
       "devDependencies": {
    
  • e2e-tests/production-runtime/SHOULD_NOT_SERVE+1 0 added
    @@ -0,0 +1 @@
    +this file shouldn't be allowed to be served
    
  • packages/gatsby/cache-dir/__tests__/minimal-config.js+9 0 modified
    @@ -25,6 +25,15 @@ it(`Builds cache-dir with minimal config`, done => {
       })
     
       spawn.on(`close`, function () {
    +    stderr = stderr
    +      .replace(`Browserslist: caniuse-lite is outdated. Please run:`, ``)
    +      .replace(`npx update-browserslist-db@latest`, ``)
    +      .replace(
    +        `Why you should do it regularly: https://github.com/browserslist/update-db#readme`,
    +        ``
    +      )
    +      .trim()
    +
         expect(stderr).toEqual(``)
         expect(stdout).not.toEqual(``)
         done()
    
  • packages/gatsby-plugin-sharp/src/gatsby-node.js+8 4 modified
    @@ -33,16 +33,17 @@ exports.onCreateDevServer = async ({ app, cache, reporter }) => {
         const decodedURI = decodeURIComponent(req.path)
         const pathOnDisk = path.resolve(path.join(`./public/`, decodedURI))
     
    -    if (await pathExists(pathOnDisk)) {
    -      return res.sendFile(pathOnDisk)
    -    }
    -
         const jobContentDigest = await cache.get(decodedURI)
         const cacheResult = jobContentDigest
           ? await cache.get(jobContentDigest)
           : null
     
         if (!cacheResult) {
    +      // this handler is meant to handle lazy images only (images that were registered for
    +      // processing, but deffered to be processed only on request in develop server).
    +      // If we don't have cache result - it means that this is not lazy image or that
    +      // image was already handled in which case `express.static` handler (that is earlier
    +      // than this handler) should take care of handling request.
           return next()
         }
     
    @@ -64,6 +65,9 @@ exports.onCreateDevServer = async ({ app, cache, reporter }) => {
           await removeCachedValue(cache, jobContentDigest)
         }
     
    +    // we reach this point only when this is a lazy image that we just processed
    +    // because `express.static` is earlier handler, we do have to manually serve
    +    // produced file for current request
         return res.sendFile(pathOnDisk)
       })
     }
    
  • packages/gatsby-plugin-sharp/src/index.js+1 1 modified
    @@ -149,7 +149,7 @@ function createJob(job, { reporter }) {
     function lazyJobsEnabled() {
       return (
         process.env.gatsby_executing_command === `develop` &&
    -    !isCI() &&
    +    (!isCI() || process.env.GATSBY_ENABLE_LAZY_IMAGES_IN_CI) &&
         !(
           process.env.ENABLE_GATSBY_EXTERNAL_JOBS === `true` ||
           process.env.ENABLE_GATSBY_EXTERNAL_JOBS === `1`
    
5f442081b227

fix(gatsby-plugin-sharp): don't serve static assets that are not result of currently triggered deferred job (#37796) (#37799)

https://github.com/gatsbyjs/gatsbyGatsbyJS BotMar 29, 2023via ghsa
6 files changed · +16 8
  • e2e-tests/development-runtime/package.json+3 2 modified
    @@ -32,14 +32,15 @@
       "license": "MIT",
       "scripts": {
         "build": "gatsby build",
    -    "develop": "cross-env CYPRESS_SUPPORT=y ENABLE_GATSBY_REFRESH_ENDPOINT=y GATSBY_ENABLE_QUERY_ON_DEMAND_IN_CI=y gatsby develop",
    +    "develop": "cross-env CYPRESS_SUPPORT=y ENABLE_GATSBY_REFRESH_ENDPOINT=y GATSBY_ENABLE_QUERY_ON_DEMAND_IN_CI=y GATSBY_ENABLE_LAZY_IMAGES_IN_CI=y gatsby develop",
         "serve-static-files": "node ./serve-static-files.mjs",
         "serve": "gatsby serve",
         "clean": "gatsby clean",
         "typecheck": "tsc --noEmit",
         "start": "npm run develop",
         "format": "prettier --write \"src/**/*.js\"",
         "test": "npm run start-server-and-test || (npm run reset && exit 1)",
    +    "test:dir-traversel-access": "! curl -f http://localhost:8000/%2e%2e/SHOULD_NOT_SERVE",
         "posttest": "npm run reset",
         "reset": "node scripts/reset.js",
         "reset:preview": "curl -X POST http://localhost:8000/__refresh",
    @@ -55,7 +56,7 @@
         "playwright:debug": "playwright test --project=chromium --debug",
         "start-server-and-test:playwright": "start-server-and-test develop http://localhost:8000 serve-static-files http://localhost:8888 playwright",
         "start-server-and-test:playwright-debug": "start-server-and-test develop http://localhost:8000 serve-static-files http://localhost:8888 playwright:debug",
    -    "combined": "npm run playwright && npm run cy:run",
    +    "combined": "npm run playwright && npm run cy:run && npm run test:dir-traversel-access",
         "postinstall": "playwright install chromium"
       },
       "devDependencies": {
    
  • e2e-tests/development-runtime/SHOULD_NOT_SERVE+1 0 added
    @@ -0,0 +1 @@
    +this file shouldn't be allowed to be served
    
  • e2e-tests/production-runtime/package.json+2 1 modified
    @@ -36,6 +36,7 @@
         "start": "npm run develop",
         "clean": "gatsby clean",
         "test": "npm run build && npm run start-server-and-test && npm run test-env-vars",
    +    "test:dir-traversel-access": "! curl -f http://localhost:9000/%2e%2e/SHOULD_NOT_SERVE",
         "test:offline": "npm run build:offline && yarn start-server-and-test:offline && npm run test-env-vars",
         "test-env-vars": " node __tests__/env-vars.js",
         "start-server-and-test": "start-server-and-test serve http://localhost:9000 serve-static-files http://localhost:8888 combined",
    @@ -51,7 +52,7 @@
         "playwright:debug": "playwright test --project=chromium --debug",
         "start-server-and-test:playwright": "start-server-and-test serve http://localhost:9000 serve-static-files http://localhost:8888 playwright",
         "start-server-and-test:playwright-debug": "start-server-and-test serve http://localhost:9000 serve-static-files http://localhost:8888 playwright:debug",
    -    "combined": "npm run playwright && npm run cy:run",
    +    "combined": "npm run playwright && npm run cy:run && npm run test:dir-traversel-access",
         "postinstall": "playwright install chromium"
       },
       "devDependencies": {
    
  • e2e-tests/production-runtime/SHOULD_NOT_SERVE+1 0 added
    @@ -0,0 +1 @@
    +this file shouldn't be allowed to be served
    
  • packages/gatsby-plugin-sharp/src/gatsby-node.js+8 4 modified
    @@ -33,16 +33,17 @@ exports.onCreateDevServer = async ({ app, cache, reporter }) => {
         const decodedURI = decodeURIComponent(req.path)
         const pathOnDisk = path.resolve(path.join(`./public/`, decodedURI))
     
    -    if (await pathExists(pathOnDisk)) {
    -      return res.sendFile(pathOnDisk)
    -    }
    -
         const jobContentDigest = await cache.get(decodedURI)
         const cacheResult = jobContentDigest
           ? await cache.get(jobContentDigest)
           : null
     
         if (!cacheResult) {
    +      // this handler is meant to handle lazy images only (images that were registered for
    +      // processing, but deffered to be processed only on request in develop server).
    +      // If we don't have cache result - it means that this is not lazy image or that
    +      // image was already handled in which case `express.static` handler (that is earlier
    +      // than this handler) should take care of handling request.
           return next()
         }
     
    @@ -64,6 +65,9 @@ exports.onCreateDevServer = async ({ app, cache, reporter }) => {
           await removeCachedValue(cache, jobContentDigest)
         }
     
    +    // we reach this point only when this is a lazy image that we just processed
    +    // because `express.static` is earlier handler, we do have to manually serve
    +    // produced file for current request
         return res.sendFile(pathOnDisk)
       })
     }
    
  • packages/gatsby-plugin-sharp/src/index.js+1 1 modified
    @@ -149,7 +149,7 @@ function createJob(job, { reporter }) {
     function lazyJobsEnabled() {
       return (
         process.env.gatsby_executing_command === `develop` &&
    -    !isCI() &&
    +    (!isCI() || process.env.GATSBY_ENABLE_LAZY_IMAGES_IN_CI) &&
         !(
           process.env.ENABLE_GATSBY_EXTERNAL_JOBS === `true` ||
           process.env.ENABLE_GATSBY_EXTERNAL_JOBS === `1`
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.