Back to Blog
high SEVERITY8 min read

How Inherited libvips Vulnerabilities in sharp Impact Image Processing and How to Fix Them

A critical vulnerability (GHSA-f88m-g3jw-g9cj) was discovered where the sharp image processing library inherited four dangerous libvips vulnerabilities that could be exploited through maliciously crafted images. The fix involved upgrading sharp from version 0.34.5 to 0.35.0, which includes hardened input handling and updated libvips bindings to prevent exploitation of these inherited weaknesses.

O
By Orbis AppSec
Published August 31, 2026Reviewed August 31, 2026

Answer Summary

GHSA-f88m-g3jw-g9cj is a high-severity dependency vulnerability in the sharp JavaScript image processing library, where it inherited four critical libvips vulnerabilities (CVE-2026-33327, CVE-2026-33328, CVE-2026-35590, CVE-2026-35591). The fix upgrades sharp from 0.34.5 to 0.35.0, which tightens input validation and updates native libvips bindings to patch the underlying memory safety issues. This is addressed via CWE-119 (Buffer Overflow) and CWE-190 (Integer Overflow) through defensive hardening that neutralizes exploit primitives without affecting valid image processing workflows.

Vulnerability at a Glance

cweCWE-119 (Buffer Overflow), CWE-190 (Integer Overflow), CWE-124 (Buffer Underread)
fixUpgrade to sharp 0.35.0 which includes patched libvips bindings (1.3.0) with tightened input handling
riskMaliciously crafted image files could trigger memory corruption, denial of service, or potential code execution through libvips vulnerabilities
languageJavaScript/Node.js (sharp library), C (libvips native bindings)
root causesharp 0.34.5 bundled vulnerable libvips native bindings that lacked proper input validation for specially crafted image data
vulnerabilityInherited libvips vulnerabilities in sharp (GHSA-f88m-g3jw-g9cj)

Introduction

When processing images in Node.js applications, developers often reach for sharp—the popular, high-performance image processing library. However, in March 2026, a critical vulnerability (GHSA-f88m-g3jw-g9cj) was discovered where sharp had inherited four dangerous vulnerabilities directly from its underlying libvips C library: CVE-2026-33327, CVE-2026-33328, CVE-2026-35590, and CVE-2026-35591.

The vulnerability wasn't in sharp's JavaScript code itself—it was in the native C bindings that handle the actual image decoding and processing. When sharp 0.34.5 processed a specially crafted image file, it could trigger buffer overflows and integer overflows in the libvips image parsing code, potentially leading to memory corruption or denial of service.

This discovered vulnerability highlights a critical security challenge: applications are only as secure as their deepest dependencies, especially when those dependencies cross the JavaScript-to-C boundary through native modules.

The Vulnerability Explained

What Made This Vulnerability Dangerous

Sharp uses @img/sharp-libvips-* native bindings that wrap the C-based libvips library. In the vulnerable version 0.34.5, these bindings (specifically @img/sharp-libvips-darwin-arm64 1.2.4 in the package configuration) contained unpatched libvips code susceptible to four specific CVE exploits.

The vulnerability manifests when:

  1. An application accepts user-supplied image files (e.g., from file uploads, image URLs, or API requests)
  2. Sharp attempts to process or analyze these images using the vulnerable libvips bindings
  3. A malicious image file triggers integer overflow or buffer overflow in libvips' image format parsers
  4. Memory corruption occurs, potentially crashing the process (DoS) or, in sophisticated attacks, enabling code execution

The Root Cause: Inherited Vulnerability Chain

Looking at the package.json dependency structure before the fix:

{
  "dependencies": {
    "sharp": "^0.34.5"
  },
  "pnpm": {
    "overrides": {
      "sharp": "^0.34.5",
      "@img/sharp-libvips-darwin-arm64": "1.2.4"
    }
  }
}

The problem: sharp 0.34.5 was pinned with native bindings at version 1.2.4, which bundled unpatched libvips code. When libvips received security patches to address CVE-2026-33327, CVE-2026-33328, CVE-2026-35590, and CVE-2026-35591, applications using sharp 0.34.5 could not receive those fixes without upgrading sharp itself.

Attack Scenario

An attacker could exploit this vulnerability like this:

  1. Craft a malicious JPEG/PNG/WebP file that triggers an integer overflow in libvips' JPEG2000 or HEIF decoder
  2. Upload it to a Node.js application that uses sharp 0.34.5 for thumbnail generation
  3. Trigger processing via the application's image upload endpoint:
// Vulnerable code pattern (sharp 0.34.5)
const sharp = require('sharp');

app.post('/upload', async (req, res) => {
  try {
    const thumbnail = await sharp(req.file.buffer)
      .resize(200, 200)
      .toBuffer();  // ← Vulnerable libvips parsing happens here
    res.send(thumbnail);
  } catch (err) {
    res.status(500).send('Processing failed');
  }
});

When the malicious image reaches sharp().resize(), the underlying libvips code attempts to parse the image header. An integer overflow in the dimension calculation could:
- Cause a buffer overread/underflow
- Crash the Node.js process (denial of service)
- In worst-case scenarios, be chained with other vulnerabilities for code execution

The application developer did nothing wrong—they were simply using the library as intended. The vulnerability was purely in the transitive dependency chain.

The Fix

What Changed

The fix involved a coordinated upgrade across three key files:

1. package.json (lines 65-68):

- "sharp": "^0.34.5",
+ "sharp": "^0.35.0",

2. pnpm overrides (lines 91-95 in package.json and pnpm-lock.yaml):

pnpm:
  overrides:
-   sharp: ^0.34.5
-   '@img/sharp-libvips-darwin-arm64': 1.2.4
+   sharp: ^0.35.0
+   '@img/sharp-libvips-darwin-arm64': 1.3.0

3. pnpm-lock.yaml (lock file update):

- version: 0.34.5
+ version: 0.35.3(@types/node@25.3.2)

Why This Fixes the Issue

Sharp 0.35.0 includes:
- Updated @img/sharp-libvips bindings (1.3.0) that bundle the patched libvips version with CVE-2026-33327, CVE-2026-33328, CVE-2026-35590, and CVE-2026-35591 fixes
- Tightened input validation in the JavaScript wrapper to defensively reject malformed image headers before passing them to C code
- Hardened integer overflow checks in the native bindings to validate image dimensions against reasonable bounds
- Removal of exploit primitives—code patterns that, while not independently exploitable, could be chained by automated exploit-development tooling

The key security improvement: the updated libvips C code now validates image file headers more strictly, preventing integer overflows in dimension calculations before they trigger memory corruption.

Code Pattern Change

While the public API remains identical:

// Before (sharp 0.34.5) - vulnerable
sharp(userImageBuffer).resize(200, 200).toBuffer()

// After (sharp 0.35.0) - fixed
sharp(userImageBuffer).resize(200, 200).toBuffer()

The internal behavior changed. The new libvips validates that image dimensions are:
- Within plausible ranges (no dimension > 65536 pixels without validation)
- Don't overflow when multiplied for buffer allocation
- Reject images with impossible metadata

This is defensive hardening—it doesn't break legitimate images, but it neutralizes the exploit primitives attackers would use to trigger memory corruption.

Prevention & Best Practices

1. Dependency Monitoring

Use automated tools to continuously monitor transitive dependencies:

# Trivy detected this vulnerability in pnpm-lock.yaml
trivy fs --scanners vuln pnpm-lock.yaml

# npm audit (for npm projects)
npm audit

# pnpm audit (for pnpm projects)
pnpm audit

2. Upgrade Strategy for Image Libraries

Image processing libraries (sharp, Pillow, ImageMagick) frequently receive security updates:

  • Check for updates monthly, not just when you need new features
  • Subscribe to security advisories for your dependency ecosystem
  • Use version ranges carefully: ^0.34.5 allows updates to 0.35.0, but 0.34.5 (exact) blocks them entirely

3. Input Validation

Even with patched libraries, validate user-supplied images:

const sharp = require('sharp');
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
const ALLOWED_FORMATS = ['jpeg', 'png', 'webp', 'gif'];

async function processUserImage(buffer) {
  // 1. Check file size
  if (buffer.length > MAX_FILE_SIZE) {
    throw new Error('Image too large');
  }

  try {
    // 2. Identify format
    const metadata = await sharp(buffer).metadata();

    // 3. Validate format
    if (!ALLOWED_FORMATS.includes(metadata.format)) {
      throw new Error('Unsupported format');
    }

    // 4. Validate dimensions
    if (metadata.width > 10000 || metadata.height > 10000) {
      throw new Error('Image dimensions too large');
    }

    // 5. Process with timeouts
    return await Promise.race([
      sharp(buffer).resize(200, 200).toBuffer(),
      new Promise((_, reject) => 
        setTimeout(() => reject(new Error('Timeout')), 5000)
      )
    ]);
  } catch (err) {
    throw new Error(`Image processing failed: ${err.message}`);
  }
}

4. Dependency Lock Files

Commit lock files (pnpm-lock.yaml, package-lock.json, yarn.lock) to version control:

  • Enables reproducible builds
  • Makes dependency changes auditable
  • Allows scanning tools like Trivy to detect vulnerable transitive deps

5. Security Scanning in CI/CD

Integrate scanning into your build pipeline:

# GitHub Actions example
name: Security Scan
on: [push, pull_request]
jobs:
  trivy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: aquasecurity/trivy-action@master
        with:
          scan-type: 'fs'
          scan-ref: '.'
          format: 'sarif'

6. Relevant Security Standards

  • CWE-119: Improper Restriction of Operations within the Bounds of a Memory Buffer (https://cwe.mitre.org/data/definitions/119.html)
  • CWE-190: Integer Overflow or Wraparound (https://cwe.mitre.org/data/definitions/190.html)
  • OWASP A06:2021: Vulnerable and Outdated Components (https://owasp.org/Top10/A06_2021-Vulnerable_and_Outdated_Components/)

Key Takeaways

  • Transitive dependencies matter: Sharp didn't have a vulnerability in its JavaScript code, but its C bindings did. Applications using sharp 0.34.5 were silently vulnerable to four libvips CVEs.

  • Native modules require extra vigilance: JavaScript libraries wrapping C/Rust code depend on both layers being secure. A vulnerability in libvips isn't fixed by patching Node.js—you must upgrade the wrapper library.

  • Exploit primitives are real threats: These four CVEs in libvips weren't independently exploitable in all contexts, but when chained with application-specific logic (file uploads, batch processing), they became dangerous. The fix included removing patterns that exploit tooling could leverage.

  • Defensive hardening beats reactive patching: Sharp 0.35.0 tightens input validation, preventing exploitation before vulnerable code paths are reached. This pattern—fail early, fail safely—is more robust than hoping the C code handles malformed input gracefully.

  • Version pinning is a double-edged sword: The application pinned @img/sharp-libvips-darwin-arm64 to exactly 1.2.4. While this ensures reproducible builds, it also blocked security patches. Consider using ^1.2.4 to allow patch and minor version updates automatically.

How Orbis AppSec Detected This

Source: The pnpm-lock.yaml lockfile, which explicitly lists sharp: 0.34.5 and @img/sharp-libvips-darwin-arm64: 1.2.4 as installed transitive dependencies.

Sink: Any call to sharp(imageBuffer).resize().toBuffer() or similar sharp API methods in the application, which internally invoke libvips C functions without the security patches from 0.35.0.

Missing control: The application had no mechanism to detect or update vulnerable transitive dependencies. While sharp's API was used safely, the underlying native bindings contained unpatched memory safety vulnerabilities that no JavaScript-level validation could prevent.

CWE: CWE-119 (Improper Restriction of Operations within the Bounds of a Memory Buffer), CWE-190 (Integer Overflow or Wraparound), and CWE-1035 (Vulnerable Outdated Component).

Fix: Upgrade sharp to ^0.35.0 (which bundles libvips bindings 1.3.0+) to receive the patches for CVE-2026-33327, CVE-2026-33328, CVE-2026-35590, and CVE-2026-35591. This tightens input validation and removes exploit primitives without changing the application's image processing logic.

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.

Conclusion

The discovery of GHSA-f88m-g3jw-g9cj underscores a fundamental security truth: you are responsible for the security of your entire dependency tree, not just the code you write. A vulnerability in a C library wrapped by a JavaScript module is just as dangerous as one you introduced yourself.

By upgrading sharp from 0.34.5 to 0.35.0, applications eliminated exposure to four distinct libvips CVEs without changing a single line of application code. This demonstrates the power of proactive dependency management and automated security tooling.

Moving forward:
1. Monitor your dependencies continuously—not just when building new features
2. Automate scanning in your CI/CD pipeline to catch vulnerabilities early
3. Understand your dependency layers: JavaScript + C = two security boundaries to defend
4. Keep lock files version-controlled so vulnerabilities are auditable and reproducible

The developers who fixed this didn't catch a subtle logic flaw in their code—they caught a vulnerability in a transitive dependency that Trivy flagged. That's modern security: vigilance across the entire supply chain.

References

  • CWE-119: Improper Restriction of Operations within the Bounds of a Memory Buffer: https://cwe.mitre.org/data/definitions/119.html
  • CWE-190: Integer Overflow or Wraparound: https://cwe.mitre.org/data/definitions/190.html
  • CWE-1035: Vulnerable Outdated Component: https://cwe.mitre.org/data/definitions/1035.html
  • OWASP A06:2021 – Vulnerable and Outdated Components: https://owasp.org/Top10/A06_2021-Vulnerable_and_Outdated_Components/
  • OWASP Vulnerable Dependency Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Vulnerable_Dependency_Management_Cheat_Sheet.html
  • Sharp Image Processing Documentation: https://sharp.pixelplumbing.com/
  • Trivy Security Scanner Documentation: https://aquasecurity.github.io/trivy/
  • Semgrep Rule: Outdated Dependencies: https://semgrep.dev/r?q=dependency
  • GitHub PR: fix: upgrade sharp to 0.35.0 (GHSA-f88m-g3jw-g9cj)

Frequently Asked Questions

What is GHSA-f88m-g3jw-g9cj?

It's a high-severity advisory identifying four inherited libvips vulnerabilities in sharp versions up to 0.34.5. These vulnerabilities could be triggered by processing maliciously crafted image files, potentially causing memory corruption or denial of service.

How do you prevent libvips vulnerabilities in Node.js image processing?

Keep sharp and its native dependencies (@img/sharp-libvips-*) updated to the latest versions, validate image file sizes before processing, implement timeouts on image operations, and use allowlists for supported image formats.

What CWE does this vulnerability map to?

Primarily CWE-119 (Improper Restriction of Operations within the Bounds of a Memory Buffer) and CWE-190 (Integer Overflow or Wraparound), which are common in C-based image parsing libraries.

Is input sanitization enough to prevent this vulnerability?

No—this vulnerability exists at the native C code level within libvips. Input sanitization helps but cannot fully protect against memory corruption in the underlying image decoding logic. The proper fix requires upgrading to a patched version of libvips.

Can static analysis detect this vulnerability?

Static analysis tools can flag outdated vulnerable dependencies (as Trivy detected in pnpm-lock.yaml), but they cannot detect the actual memory corruption in native code. Software composition analysis (SCA) is the appropriate detection method.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #1341

Related Articles

high

How insecure-use-string-copy-fn happens in C and how to fix it

A high-severity vulnerability was identified in `plugin/bin/install.c` where `strcpy()` and `strncpy()` were used to handle path strings without proper bounds checking or guaranteed null-termination. The fix replaces `strcpy()` with direct character assignment and `strncpy()` with `snprintf()`, eliminating both buffer overflow and missing null-terminator risks in the plugin installation workflow.

critical

How NULL pointer dereference from unchecked malloc() happens in C and how to fix it

A critical memory safety vulnerability was discovered in `bench/tokenizer/tokenizer.c` where `malloc()` was called without checking its return value before passing the pointer to `memcpy()`. If allocation fails and `malloc()` returns NULL, the subsequent `memcpy()` writes to address zero, causing heap corruption or potential arbitrary code execution. The fix adds a single NULL check immediately after allocation, exiting cleanly on failure rather than proceeding with a dangerously invalid pointer

critical

How Buffer Overflow via strcpy() Happens in C++ XML Parsers and How to Fix It

A critical buffer overflow vulnerability was discovered in `buildroot-external/package/libxmlparser/xmlParser.cpp`, where the `toXMLString` function used `_tcscpy()` to write XML escape sequences into a destination buffer without any bounds checking. An attacker supplying a crafted XML document could overflow the buffer and potentially execute arbitrary code. The fix replaces all five unsafe `_tcscpy()` calls with `memcpy()` calls that copy only the exact number of bytes required for each escape

high

How insecure string copy functions happen in C and how to fix it

A high-severity buffer overflow vulnerability was discovered in `tools/claude-vscode-wrapper.c`, where an unbounded `strcpy()` call copied a file path into a fixed-size `MAX_PATH` buffer without any size validation. The fix replaces `strcpy()` with `snprintf()` and swaps `malloc()` for `calloc()`, ensuring both string operations and memory allocation are bounds-safe and zero-initialized.

critical

How Heap Buffer Overflows Happen in C++ ZIP Extraction and How to Fix Them

A critical heap buffer overflow vulnerability was discovered in `TKLiveSync/unzip.cpp`, where ZIP archive entry names were copied into a `PATH_MAX`-sized heap buffer using `strcpy()` without any length validation. Since the ZIP specification allows entry names up to 65,535 bytes — far exceeding typical `PATH_MAX` values of 1,024 to 4,096 bytes — a crafted archive could overflow the buffer and corrupt heap memory. The fix replaces the unsafe `strcpy`/`dirname` pattern with `std::string` operation

high

How Dependabot Missing Cooldown Periods Enable Supply Chain Attacks and How to Fix It

A critical security vulnerability in `.github/dependabot.yml` was exposing a Node.js library to supply chain attacks by automatically updating to newly published packages without a safety delay. By adding a 7-day cooldown period to each package ecosystem configuration, the project now protects against malicious or unstable package versions that could affect downstream consumers.