GHSA-68qg-g8mg-6pr7: paperclip Vulnerable to Unauthenticated Remote Code Execution via Import Authorization Bypass
SummaryAn 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: sagi03581Steps to ReproduceThe attack chains four independent flaws to escalate from zero access to RCE:Step 1: Create an account (no invite, no email verification)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`:And email verification is hardcoded off in `server/src/auth/better-auth.ts:89-93`:The environment variable isn't documented in the deployment guide, so operators don't know it exists.Step 2: Sign inCapture the session cookie from the `Set-Cookie` header.Step 3: Create a CLI auth challenge and self-approve itCreate the challenge (no authentication required at all):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: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: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`:But the import endpoint does not:`server/src/routes/companies.ts:170-176`:`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`).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 agentThe 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 ConceptI have a self-contained bash script that runs the full chain automatically: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.ImpactAn 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 databaseAbility to pivot to internal network servicesAbility to disrupt all agent operationsThe attack is fully automated, requires no user interaction, and works against the default deployment configuration.Suggested FixesCritical: 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 structureApprove/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 exhaustionWait for the next authorization bug to appear**These need to be fixed together:****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.**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.**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)**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`:
CVSS v3.1: 10