Back to Blog
critical SEVERITY7 min read

How supply chain code injection happens in Node.js build scripts and how to fix it

A critical supply chain vulnerability in the chatgpt-auto-continue extension's build utility allowed arbitrary code execution by fetching JavaScript from a CDN without integrity verification. The fix implements SHA-256 hash validation before executing the downloaded code, preventing potential supply chain attacks through compromised CDN content or man-in-the-middle attacks.

O
By Orbis AppSec
Published July 23, 2026Reviewed July 23, 2026

Answer Summary

Supply chain code injection in Node.js build scripts (CWE-494) occurs when external code is fetched from CDNs and executed without integrity verification. In `chatgpt-auto-continue/utils/bump/extension-manifests.js`, the script used `fs.writeFileSync()` to save fetched CDN content and immediately executed it via dynamic import. The fix adds SHA-256 hash validation using Node.js's `crypto.createHash()` to verify content integrity before execution, blocking malicious code injection from compromised CDNs or MITM attacks.

Vulnerability at a Glance

cweCWE-494 (Download of Code Without Integrity Check)
fixSHA-256 hash validation of CDN content before filesystem write and dynamic import
riskArbitrary code execution in developer environments through compromised CDN or MITM attacks
languageNode.js
root causeNo integrity verification before writing and executing externally fetched JavaScript code
vulnerabilitySupply chain code injection via unverified CDN content

Introduction

In the chatgpt-auto-continue extension repository, we discovered a critical supply chain vulnerability in chatgpt-auto-continue/utils/bump/extension-manifests.js at line 30. The build script was fetching JavaScript code from https://cdn.jsdelivr.net/gh/adamlui/ai-web-extensions@f63b650/utils/bump/lib/bump.min.mjs, writing it directly to the filesystem using fs.writeFileSync(), and then immediately executing it via dynamic import—all without any integrity verification.

This pattern created a dangerous supply chain attack vector where compromised CDN content, DNS poisoning, or a man-in-the-middle attack could inject arbitrary malicious code directly into the developer environment. Since this is a Node.js library, the vulnerability affects every downstream consumer who uses this package and runs the build scripts.

The Vulnerability Explained

Let's examine the vulnerable code from extension-manifests.js:

// Import BUMP UTILS
fs.mkdirSync(path.dirname(cachePaths.bumpUtils), { recursive: true })
fs.writeFileSync(cachePaths.bumpUtils, (await (await fetch(
    'https://cdn.jsdelivr.net/gh/adamlui/ai-web-extensions@f63b650/utils/bump/lib/bump.min.mjs')).text()))
const bump = await import(`file://${cachePaths.bumpUtils}`) ; fs.unlinkSync(cachePaths.bumpUtils)

The problem occurs in this sequence:

  1. Line 32-33: The script fetches JavaScript code from a jsdelivr CDN URL
  2. Line 32: It immediately writes the fetched content to disk using fs.writeFileSync()
  3. Line 34: It dynamically imports and executes the code with await import()
  4. Missing: There's absolutely no verification that the fetched content matches what's expected

How Could This Be Exploited?

Consider these attack scenarios specific to this code:

Scenario 1: Compromised CDN
An attacker who gains access to the jsdelivr CDN infrastructure could replace bump.min.mjs with malicious code. When developers run the build script, they would unknowingly download and execute the attacker's code. The malicious code could:
- Steal environment variables (API keys, tokens)
- Inject backdoors into the built extension
- Exfiltrate source code or credentials
- Modify the build output to include malware

Scenario 2: DNS Poisoning
An attacker performing DNS poisoning in a corporate network could redirect cdn.jsdelivr.net to their own server hosting malicious JavaScript. The build script would fetch and execute the attacker's code, believing it came from the legitimate CDN.

Scenario 3: Man-in-the-Middle Attack
Even with HTTPS, sophisticated attackers with MITM capabilities (compromised proxies, rogue certificates) could intercept the request and replace the content before it reaches the developer's machine.

Real-World Impact

This vulnerability is particularly dangerous because:

  • It executes in the developer environment with full filesystem access and environment variables
  • It runs during the build process, meaning it could inject malicious code into the production extension
  • It's in a library package, so every downstream consumer inherits this vulnerability
  • There's no audit trail of what code was actually executed—the file is deleted immediately after import

The bump utility that's being imported handles version bumping for the extension manifests. A compromised version could manipulate version numbers, inject malicious code into manifests, or steal sensitive build-time data.

The Fix

The fix implements Subresource Integrity (SRI) verification using SHA-256 hashing. Here's the corrected code:

// Import BUMP UTILS
fs.mkdirSync(path.dirname(cachePaths.bumpUtils), { recursive: true })
const bumpUtilsContent = await (await fetch(
    'https://cdn.jsdelivr.net/gh/adamlui/ai-web-extensions@f63b650/utils/bump/lib/bump.min.mjs')).text()
if (createHash('sha256').update(bumpUtilsContent).digest('hex') !==
        'fea2efd9d2bf02cae89e7fa7c2385a4a0910db93236543394cb087b6884b89c7')
    throw new Error('Integrity check failed: unexpected content in bump.min.mjs')
fs.writeFileSync(cachePaths.bumpUtils, bumpUtilsContent)
const bump = await import(`file://${cachePaths.bumpUtils}`) ; fs.unlinkSync(cachePaths.bumpUtils)

Before and After Comparison

Before (Vulnerable):

fs.writeFileSync(cachePaths.bumpUtils, (await (await fetch(
    'https://cdn.jsdelivr.net/gh/adamlui/ai-web-extensions@f63b650/utils/bump/lib/bump.min.mjs')).text()))
const bump = await import(`file://${cachePaths.bumpUtils}`)

After (Secure):

const bumpUtilsContent = await (await fetch(
    'https://cdn.jsdelivr.net/gh/adamlui/ai-web-extensions@f63b650/utils/bump/lib/bump.min.mjs')).text()
if (createHash('sha256').update(bumpUtilsContent).digest('hex') !==
        'fea2efd9d2bf02cae89e7fa7c2385a4a0910db93236543394cb087b6884b89c7')
    throw new Error('Integrity check failed: unexpected content in bump.min.mjs')
fs.writeFileSync(cachePaths.bumpUtils, bumpUtilsContent)
const bump = await import(`file://${cachePaths.bumpUtils}`)

How This Fix Solves the Problem

  1. Line 33: The fetched content is stored in bumpUtilsContent instead of being immediately written
  2. Lines 34-36: A SHA-256 hash is computed using Node.js's built-in crypto.createHash() method
  3. Line 35: The computed hash is compared against the known-good hash fea2efd9d2bf02cae89e7fa7c2385a4a0910db93236543394cb087b6884b89c7
  4. Line 36: If the hashes don't match, an error is thrown and execution stops—preventing malicious code from being written or executed
  5. Line 37: Only if the integrity check passes is the content written to disk

The fix also adds { createHash } = require('crypto') to the imports at line 24, using Node.js's built-in cryptography module without adding external dependencies.

Security Improvement

This change provides cryptographic assurance that the fetched code is exactly what the developer expects. Even if an attacker compromises the CDN, performs DNS poisoning, or executes a MITM attack, the integrity check will fail because the malicious code will produce a different SHA-256 hash. The script will throw an error and halt execution before any malicious code can run.

The hash fea2efd9d2bf02cae89e7fa7c2385a4a0910db93236543394cb087b6884b89c7 acts as a cryptographic fingerprint of the legitimate bump.min.mjs file. Any modification—even a single byte—will produce a completely different hash, making tampering immediately detectable.

Prevention & Best Practices

1. Always Verify External Code Integrity

When fetching code from CDNs or external sources, implement integrity checks:

const crypto = require('crypto');

function verifyIntegrity(content, expectedHash, algorithm = 'sha256') {
    const actualHash = crypto.createHash(algorithm)
        .update(content)
        .digest('hex');

    if (actualHash !== expectedHash) {
        throw new Error(`Integrity check failed: expected ${expectedHash}, got ${actualHash}`);
    }
}

2. Use Subresource Integrity (SRI) in HTML

For browser-based applications, use SRI attributes:

<script src="https://cdn.example.com/library.js"
        integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/ux..."
        crossorigin="anonymous"></script>

3. Pin Specific Versions

The URL in this fix already pins a specific commit hash (f63b650), which is good practice:

https://cdn.jsdelivr.net/gh/adamlui/ai-web-extensions@f63b650/utils/bump/lib/bump.min.mjs

Avoid using @latest or version ranges when fetching executable code.

4. Consider Vendoring Critical Dependencies

For critical build scripts, consider vendoring (copying) dependencies into your repository:

# Download and commit the dependency
curl -o utils/bump.min.mjs https://cdn.jsdelivr.net/gh/...
git add utils/bump.min.mjs
git commit -m "Vendor bump utility"

This eliminates runtime CDN dependencies but requires manual updates.

5. Use Lock Files and Dependency Scanning

For npm packages, use package-lock.json or yarn.lock to pin transitive dependencies. Use tools like:
- npm audit for vulnerability scanning
- Dependabot for automated security updates
- Snyk or Socket.dev for supply chain security

6. Implement Content Security Policy (CSP)

For Node.js applications that might execute in browser contexts, implement strict CSP:

app.use((req, res, next) => {
    res.setHeader("Content-Security-Policy", 
        "script-src 'self' https://trusted-cdn.com");
    next();
});

7. Use Static Analysis Tools

Configure tools to detect this pattern:

Semgrep rule example:

rules:
  - id: unverified-remote-code-execution
    pattern: |
      fs.writeFileSync($PATH, await (await fetch($URL)).text())
    message: Remote code is fetched and written without integrity verification
    severity: ERROR

Key Takeaways

  • Never execute CDN-fetched code without integrity verification in extension-manifests.js or any build script—always use SHA-256 or stronger hashes
  • The fs.writeFileSync() and dynamic import() combination creates immediate code execution risk when fed unverified external content
  • jsdelivr CDN URLs with commit hashes (like @f63b650) provide version pinning but not integrity—hash verification is still essential
  • Node.js's built-in crypto.createHash('sha256') provides zero-dependency integrity checking for supply chain security
  • Supply chain attacks target build scripts specifically because they run with developer privileges and can inject malicious code into production artifacts

How Orbis AppSec Detected This

  • Source: External CDN content fetched from https://cdn.jsdelivr.net/gh/adamlui/ai-web-extensions@f63b650/utils/bump/lib/bump.min.mjs
  • Sink: fs.writeFileSync() followed by dynamic import() at chatgpt-auto-continue/utils/bump/extension-manifests.js:32-34
  • Missing control: No cryptographic integrity verification (hash check, signature validation, or SRI) before writing and executing fetched code
  • CWE: CWE-494 (Download of Code Without Integrity Check)
  • Fix: Added SHA-256 hash validation using crypto.createHash() to verify content integrity before filesystem write and execution

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

This supply chain vulnerability in the chatgpt-auto-continue extension's build script demonstrates how seemingly innocuous utility code can create critical security risks. By fetching and executing JavaScript from a CDN without integrity verification, the script opened a direct path for arbitrary code execution in developer environments.

The fix—implementing SHA-256 hash validation before execution—is a simple yet powerful defense that provides cryptographic assurance of code integrity. This pattern should be applied wherever your code fetches and executes external content, whether in build scripts, runtime dependency loading, or plugin systems.

Supply chain security is increasingly critical as modern development relies heavily on external code. Every fetch-and-execute pattern is a potential attack vector. By adopting integrity verification as a standard practice, you can protect your development pipeline and production systems from compromised dependencies.

Remember: trust but verify. Even code from trusted CDNs should be validated with cryptographic hashes before execution.

References

Frequently Asked Questions

What is supply chain code injection in Node.js build scripts?

It's a vulnerability where build scripts fetch and execute external code (typically from CDNs) without verifying its integrity through cryptographic hashes or signatures. If the CDN is compromised or a man-in-the-middle attack occurs, malicious code can be injected into the build process, affecting all developers and potentially production deployments.

How do you prevent supply chain code injection in Node.js?

Implement Subresource Integrity (SRI) checks using SHA-256, SHA-384, or SHA-512 hashes. In Node.js, use the `crypto` module to compute hashes of fetched content and compare against known-good values before execution. Additionally, pin specific versions in URLs, use HTTPS exclusively, and consider vendoring critical dependencies locally.

What CWE is supply chain code injection?

Supply chain code injection falls under CWE-494 (Download of Code Without Integrity Check), which is part of the broader category of supply chain vulnerabilities. Related CWEs include CWE-829 (Inclusion of Functionality from Untrusted Control Sphere) and CWE-345 (Insufficient Verification of Data Authenticity).

Is HTTPS enough to prevent supply chain code injection?

No. While HTTPS prevents man-in-the-middle attacks during transit, it doesn't protect against compromised CDN servers, DNS poisoning, or malicious CDN operators. An attacker who gains control of the CDN can serve malicious content over valid HTTPS. Cryptographic hash verification (SRI) is essential to ensure content integrity regardless of transport security.

Can static analysis detect supply chain code injection?

Yes. Static analysis tools can detect patterns like fetching remote code followed by `eval()`, `Function()`, or dynamic imports without integrity checks. Tools like Semgrep, CodeQL, and specialized supply chain security scanners can flag these patterns. However, determining the correct hash value requires runtime analysis or manual inspection of the legitimate content.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #502

Related Articles

high

How secrets: inherit over-privilege happens in GitHub Actions reusable workflows and how to fix it

A high-severity security finding was identified in `templates/claude-workflow/workflows/claude.yml` where `secrets: inherit` passed every repository secret to a reusable workflow, violating the principle of least privilege. The fix explicitly passes only `CLAUDE_CODE_OAUTH_TOKEN`—the single secret the called workflow actually needs—drastically reducing the blast radius if the reusable workflow is ever compromised.

high

How missing Dependabot cooldown configuration happens in GitHub Actions and how to fix it

A high-severity vulnerability was discovered in the `.github/dependabot.yml` configuration file where no cooldown period was set for dependency updates. This exposed the Node.js library and its downstream consumers to potentially malicious or unstable packages immediately after publication. The fix adds a 7-day cooldown period to all package ecosystem entries, providing a critical safety buffer before accepting newly published package versions.

critical

How arbitrary code execution via injected protobuf definition type fields happens in Node.js and how to fix it

A critical arbitrary code execution vulnerability (CVE-2026-41242) was discovered in protobufjs versions prior to 7.5.5 and 8.0.1, allowing attackers to inject malicious code through crafted protobuf definition type fields. The fix upgrades the dependency from the vulnerable version 6.11.4 to 7.5.5, which properly sanitizes type field inputs during protobuf parsing. This vulnerability is especially dangerous because protobufjs is widely used in Node.js applications for serialization, meaning a s

high

How missing Dependabot cooldown happens in GitHub Actions CI/CD and how to fix it

A high-severity configuration gap was discovered in a Node.js library's `.github/dependabot.yml` file where no cooldown period was set for dependency updates. Without a cooldown, Dependabot could propose updates to newly published (and potentially malicious or unstable) package versions immediately after release, exposing the project and all downstream consumers to supply chain attacks. The fix adds a `cooldown` block with `default-days: 7` to each package ecosystem entry.

high

How missing Dependabot cooldown happens in GitHub Actions CI/CD and how to fix it

A Dependabot configuration file in a Node.js library was missing cooldown periods for both its `npm` and `github-actions` package ecosystem entries, meaning newly published (and potentially malicious) package versions could be proposed for immediate adoption. The fix adds a `cooldown` block with `default-days: 7` to each ecosystem entry, creating a critical 7-day buffer that allows the community to identify and flag compromised packages before they reach downstream consumers.

critical

How Server-Side Request Forgery (SSRF) happens in JavaScript fetch() and how to fix it

A critical Server-Side Request Forgery vulnerability in `popup.js` allowed attackers to inject malicious URLs from scraped webpages directly into fetch() calls, potentially accessing internal network resources and AWS metadata endpoints. The fix adds URL validation to ensure only HTTP/HTTPS protocols are used and blocks requests to private IP ranges and localhost addresses.