Back to Blog
critical SEVERITY4 min read

Slim CLI Unverified Remote Fetch in Version Check

The Slim CLI's version check command fetched remote package metadata without integrity verification, enabling attackers to serve malicious responses through repository hijacking or man-in-the-middle attacks. The fix adds input validation, HTTP status checking, and response schema verification to ensure only legitimate version data is processed.

O
By Orbis AppSec
•Published September 30, 2026•Reviewed September 30, 2026

Answer Summary

The Slim CLI's `check` command in versions prior to the fix fetched package.json from raw.githubusercontent.com using a dynamically constructed URL without integrity verification, certificate pinning, or signature validation. An attacker who compromised the GitHub repository, performed DNS spoofing, or positioned as a man-in-the-middle could serve a malicious package.json response that would be parsed and trusted by the CLI. The fix introduces regex validation on the repository identifier, HTTP response status checking, and strict schema validation on the returned version field. CWE status is unknown.

Vulnerability at a Glance

cweN/A
fixAdded input validation, status checking, and schema validation on remote response
riskRepository hijacking or MITM leading to trusted injection of malicious metadata
languageJavaScript (Node.js)
root causefetch() to raw.githubusercontent.com without integrity verification or response validation
vulnerabilityUnverified remote resource fetch

Affected Versions

Affected not applicable (first-party code)
Fixed in not applicable (first-party code) — see PR for fix commit
Ecosystem npm
CVE / GHSA not assigned
CWE unknown

The Vulnerability Explained

The Slim CLI's check command performed an unverified fetch against a user-influenced remote endpoint. When users ran the version check, the tool constructed a URL from the local package's repository metadata and fetched package.json directly from raw.githubusercontent.com:

const githubRepo = pkg.repository.url.split("git+https://github.com/")[1].trim().split(".git")[0]
const res = await fetch("https://raw.githubusercontent.com/" + githubRepo + "/main/package.json")
const githubPkg = await res.json()

This pattern had three critical gaps:

  1. No input validation on the repository identifier: The githubRepo value was extracted via string manipulation without validating it matched expected GitHub repository format (owner/repo).

  2. No HTTP status verification: The response was parsed as JSON regardless of whether the request succeeded (200 OK) or failed (404, 500, etc.).

  3. No response schema validation: The parsed JSON was trusted to contain a valid version field without checking its type or format.

An attacker exploiting this could manipulate the version check output through several vectors:

  • Repository hijacking: If the original repository was renamed or the account compromised, GitHub's redirect or attacker-controlled replacement would be trusted.
  • DNS spoofing or MITM: Without certificate pinning, an attacker on the network path could serve arbitrary JSON.
  • Repository identifier manipulation: If the local package.json repository URL was somehow attacker-controlled, the constructed fetch URL could be manipulated.

The real impact was subtle but serious: the version check's output influenced user trust in their installation. A manipulated "latest version" response could push users toward downloading malicious updates from attacker-controlled sources.

The Fix

The remediation adds defense-in-depth through three layers of validation:

Repository identifier validation: Before constructing any URL, the extracted repository string is validated against a strict regex:

if(!/^[\w.-]+\/[\w.-]+$/.test(githubRepo)) {
    error("Unable to determine a valid GitHub repository for version check")
    return
}

This prevents path traversal, unexpected URL structures, or malformed identifiers from reaching the fetch call.

HTTP status checking: The response is now verified to succeed before parsing:

if(!res.ok) {
    error(`Version check failed: received HTTP ${res.status} from GitHub`)
    return
}

Response schema validation: The parsed JSON is validated to contain a properly formatted semantic version string:

if(typeof githubPkg?.version !== "string" || !/^\d+\.\d+\.\d+/.test(githubPkg.version)) {
    error("Version check failed: unexpected response format")
    return
}

The complete fixed path now wraps the entire operation in try-catch error handling, ensuring network or parsing failures fail safely rather than leaking stack traces or undefined behavior.

Key Takeaways

  • Never trust DNS or TLS alone for remote metadata: Certificate validation prevents passive eavesdropping but not active attacks against the endpoint itself. Always validate the semantic content of security-relevant responses.

  • Validate identifiers before URL construction: User-influenced or parsed values used in URL building need strict format validation to prevent open redirects, SSRF, or unexpected endpoint targeting.

  • HTTP ok status is not implicit: The Fetch API does not throw on 404 or 500 responses. Explicit status checking is required before trusting response bodies.

  • JSON parsing success does not imply schema validity: Always verify that parsed objects contain expected types and formats before using them in security-relevant decisions.

  • Version checks are security boundaries: Users rely on version information to decide when to update. Compromising this channel is a viable software supply chain attack vector.

How Orbis AppSec Detected This

Source: The pkg.repository.url property parsed from the local package.json

Sink: The fetch() call to raw.githubusercontent.com with a dynamically constructed URL containing unvalidated path components

Missing control: No validation of the repository identifier format, no verification of HTTP response status, and no schema validation on the parsed JSON response

CWE: unknown — this pattern spans multiple weakness categories including inadequate input validation (CWE-20) and insufficient verification of data authenticity (CWE-345)

Fix: Added regex validation on repository identifiers, explicit HTTP status checking, and strict schema validation requiring semantic version format on the returned version field

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 vulnerability illustrates how even "simple" version check features can become supply chain attack vectors when they fetch and trust remote content without verification. The Slim CLI's fix demonstrates proper defense-in-depth: validate inputs before use, verify operations succeeded, and confirm outputs match expected schemas before acting on them. For CLI tools that fetch remote metadata, these controls are essential to prevent attackers from manipulating user trust and update decisions.

Prevention and further reading

Frequently Asked Questions

Does the Slim CLI still fetch from raw.githubusercontent.com after the fix, or was the remote check removed entirely?

The remote check is preserved, but now validates the repository identifier format with `/^[\w.-]+\/[\w.-]+$/` before constructing the URL, verifies the HTTP response status, and validates that the returned version is a string matching semantic versioning format.

What specific validation prevents a malicious package.json with a crafted version field from being accepted?

The fix requires `typeof githubPkg?.version === "string"` and validates against `/^\d+\.\d+\.\d+/`, rejecting any response where the version is not a properly formatted semantic version string.

If an attacker returns a 200 OK response with a valid-looking version but malicious other fields, can they still influence downstream behavior?

The fix only validates the version field format, so other fields in the response could still be processed. However, the original code only accessed `githubPkg.version`, limiting the attack surface to version string manipulation rather than arbitrary code execution.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #6

Related Articles

high

image-size 1.2.1 DoS: Zero-Valued Dimensions in Image Buffer Parser

A high-severity denial-of-service vulnerability in image-size 1.2.1 allows attackers to crash Node.js services using malicious image buffers with zero-valued dimensions. The fix removes the vulnerable `queue` dependency and tightens dimension validation in version 2.0.3.

high

linkify-it 5.0.1 mailto: Link Parsing Causes DoS

linkify-it versions up to 5.0.1 can be forced into excessive processing time when autolinking a specially crafted mailto: link, allowing a remote attacker to degrade or stall the parsing thread. Upgrading to linkify-it 5.0.2 closes the issue; any application that runs linkify-it (directly or via markdown-it) against untrusted text should update immediately.

critical

ensureTrivy() CWE-494: Unverified Trivy Binary Download

The `ensureTrivy()` function fetched the Trivy vulnerability scanner from GitHub releases without verifying its integrity, exposing applications to supply-chain attacks. An attacker in a MITM position or a compromised CDN could substitute a malicious binary that executes with the application's privileges. The fix adds cryptographic verification using SHA256 checksums published alongside each release.

high

@xmldom/xmldom 0.9.10 ReDoS: CVE-2026-83606 in PI Parsing

The dependency tree pinned `@xmldom/xmldom` at 0.9.10, a version vulnerable to CVE-2026-83606: a regular expression in the processing-instruction (`<?target data?>`) parsing path backtracks catastrophically on crafted input, so a single XML document can consume CPU for seconds or minutes. Because Node.js runs application code on one thread, that stall blocks every other request in flight. The lockfile was updated to resolve `@xmldom/xmldom` 0.9.12, which carries the fix.

high

Unbounded Map in createLoginRateLimiter Exhausts API Memory

The console API's `createLoginRateLimiter` and `createMutationRateLimiter` stored one `Map` entry per client key with no upper bound and no expiry sweep, so an attacker rotating source addresses or identifiers could grow those maps until the Node process hit an out-of-memory crash. The fix introduces a `maxTrackedKeys` option (default 5000), a `trackedEntryLimit` sanitizer, and an `evictOldestIfFull` helper that drops the oldest tracked key while protecting the shared global counter. The rate li

critical

path.resolve() Path Traversal in Node CLI's Dynamic import()

A Node.js CLI script for validating expression definitions took a file path from `process.argv[2]`, resolved it with `path.resolve()`, and passed the result straight into a dynamic `import()` — with no check that the resolved path stayed inside the working directory. An attacker (or a malicious skill/plugin invocation) could supply traversal sequences to load and execute arbitrary `.js` files from anywhere on disk. The fix adds a boundary check with `path.relative()` and an extension allowlist b