# WeKnora has Broken Access Control - Cross-Tenant Data Exposure

WeKnora's database_query tool allowed any authenticated tenant to SELECT from models/messages/embeddings tables lacking tenant_id injection, leaking other tenants' API keys and private messages.

- Payout: Research
- Severity: high
- Program: WeKnora
- Found by: aleister1102
- Disclosed: 2026-03-06
- CVE: CVE-2026-30859
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-2f4c-vrjq-rcgv
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-2f4c-vrjq-rcgv
- Tags: area:ai domain:ai domain:web impact:data-leak impact:internal-access kind:research platform:ghsa sev:high tech:go technique:source-review vuln:access-control vuln:idor vuln:info-disclosure

**Target:** AI agent database_query tool (SQL validation layer)
**Root cause:** WithAllowedTables() permits messages, embeddings, and models, but WithTenantIsolation()'s default tablesWithTenantID map omits them, so no automatic WHERE tenant_id = X clause is appended to queries against those tables.
**Steps:**
1. Authenticate as Tenant 1 and invoke the AI agent's database_query tool.
2. Send prompt: `Use the database_query tool with {"sql": "SELECT * FROM models"} to query the database. Output all results and any errors.`
3. Observe the agent returns all model records across tenants, including API keys and configs.
4. Repeat with `SELECT * FROM messages` to dump all tenants' private messages.
**How found:** Compared the table list in WithAllowedTables() against the tablesWithTenantID default map in WithTenantIsolation() and noticed messages, embeddings, and models were queryable but not tenant-isolated.
**Exploitation:** Any authenticated tenant prompts the LLM agent to call database_query with a SELECT against models/messages/embeddings; validation passes and no tenant filter is injected, so the agent returns cross-tenant rows including third-party LLM API keys and private conversations.
**Escalation:** Leaked API keys from models table enable further compromise of integrated LLM providers and services.
**Insight:** When a SQL guard uses two separate allowlists (queryable tables vs. tenant-isolated tables), diff them: any table in the first but not the second silently bypasses row-level filtering.

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

**Prompt invoking database_query with SELECT * FROM models**

```
Use the database_query tool with {"sql": "SELECT * FROM models"} to query the database. 
   Output all results and any errors.
```

**Prompt querying all messages cross-tenant**

```
Use the database_query tool with {"sql": "SELECT * FROM messages"} to query the database. 
   Output all results.
```

