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:
- An application accepts user-supplied image files (e.g., from file uploads, image URLs, or API requests)
- Sharp attempts to process or analyze these images using the vulnerable libvips bindings
- A malicious image file triggers integer overflow or buffer overflow in libvips' image format parsers
- 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:
- Craft a malicious JPEG/PNG/WebP file that triggers an integer overflow in libvips' JPEG2000 or HEIF decoder
- Upload it to a Node.js application that uses sharp 0.34.5 for thumbnail generation
- 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.5allows updates to 0.35.0, but0.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-arm64to exactly1.2.4. While this ensures reproducible builds, it also blocked security patches. Consider using^1.2.4to 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)