This document describes the security architecture of the SupaLedger MCP server. For questions or to report a vulnerability, contact teams@fieldelevate.com.
- Authentication (OAuth 2.0)
- Authorization (Row-Level Security)
- Audit Logging
- Rate Limiting
- HIPAA Exclusion
- Data Minimization
- CORS Policy
- Transport Security
- Error Handling
- Responsible Disclosure
The SupaLedger MCP server authenticates every request via OAuth 2.0 tokens issued by Supabase Auth.
1. Claude redirects the user to SupaLedger's authorization endpoint
2. User signs in with their SupaLedger credentials
3. User selects an organization and reviews permissions on the consent screen
4. User clicks "Allow"
5. SupaLedger issues an authorization code
6. Claude exchanges the code for an access token (PKCE-protected)
7. All subsequent MCP tool calls include the access token in the Authorization header
8. When the token expires, Claude automatically refreshes it using the refresh token
| Property | Details |
|---|---|
| Protocol | OAuth 2.0 with PKCE (Proof Key for Code Exchange) |
| Token type | JWT (JSON Web Token) |
| Token lifetime | 1 hour (auto-refresh) |
| Scope | read:platform, read:accounting, read:crm (default for new connections) |
| Token storage | Managed by the Claude client — not stored by SupaLedger |
| Password exposure | Claude never sees the user's password — authentication happens on SupaLedger's hosted consent page |
A custom JWT hook injects the user's org_id into the token claims. This ensures that every database query is automatically scoped to the authorized organization, even if the Edge Function code has a bug.
SupaLedger uses PostgreSQL Row-Level Security (RLS) to enforce multi-tenant data isolation at the database level.
- Every table in the SupaLedger database has an
org_idcolumn. - RLS policies restrict all SELECT, INSERT, UPDATE, and DELETE operations to rows matching the authenticated user's organization.
- The MCP server creates a Supabase client using the user's OAuth token, which carries the user's identity and org membership.
- Even if the Edge Function code were compromised, RLS prevents cross-tenant data access.
| Layer | Protection |
|---|---|
| Application | Edge Function validates org_id parameter against JWT claims |
| Database | RLS policies enforce org isolation on every query |
| Auth context | AsyncLocalStorage ensures no cross-request auth leakage in the Edge Function runtime |
The MCP server uses AsyncLocalStorage (not module-level variables) to store the authenticated user's context. This prevents a timing vulnerability where one user's auth context could leak into a concurrent request.
Every MCP tool call is logged to platform.mcp_audit_log.
| Field | Description |
|---|---|
tool_name |
Which tool was called (e.g., accounting_get_trial_balance) |
org_id |
The organization the call was made against |
user_id |
The authenticated user who made the call |
timestamp |
When the call was made (UTC) |
duration_ms |
How long the call took |
success |
Whether the call succeeded or failed |
error_code |
The SL-* error code, if the call failed |
- The actual data returned in tool responses
- The user's prompt or Claude's response
- Raw request/response payloads
- The
mcp_audit_logtable is append-only: authenticated users can INSERT and SELECT their own logs, but cannot UPDATE or DELETE. - The
anonrole has no SELECT access to the audit log. - Organization admins can review their own organization's audit log.
Rate limits protect the SupaLedger database from excessive load and prevent abuse.
| Scope | Limit | Window |
|---|---|---|
| Per user (all tools) | 60 requests | 1 minute |
| Per organization (all tools) | 200 requests | 1 minute |
When a rate limit is hit, the server returns:
{
"content": [
{
"type": "text",
"text": "Rate limit exceeded. Please wait a moment before making more requests."
}
],
"isError": true
}Rate limits are enforced using counters derived from the mcp_audit_log table, ensuring accurate per-user and per-org tracking without additional infrastructure.
Organizations flagged as HIPAA-regulated in SupaLedger are automatically excluded from MCP access.
- HIPAA requires a Business Associate Agreement (BAA) with any service that processes Protected Health Information (PHI).
- When you use the MCP connector, your business data passes through Anthropic's infrastructure.
- Anthropic does not currently offer a public BAA for Claude conversations.
- To protect HIPAA-regulated organizations, SupaLedger blocks MCP access at the authorization level.
The verifyOrgAccess() function checks the organization's HIPAA flag before any tool executes. If the organization is HIPAA-flagged, the request is rejected with error code SL-AUTH-605 before any data is queried.
All tools default to summary mode, which returns aggregated totals instead of individual records.
| Mode | What Is Returned | PII Included |
|---|---|---|
| Summary (default) | Totals by account type, bucket totals, contact name and type only | No |
| Detail (on request) | Individual accounts, invoices, bills, full contact records | Yes (email, phone, address) |
- Financial reports: Totals by account type (e.g., "Total assets: $28,500"). No individual account names or transaction details.
- Aging reports: Bucket totals (e.g., "2 invoices, $3,200 total"). No individual customer names or invoice numbers.
- Contacts: Name, type, and company flag only. No email, phone, address, tax IDs, or notes.
Users must explicitly request detail mode to receive individual records. This ensures that the minimum necessary data passes through Anthropic's infrastructure for any given query.
The MCP server restricts cross-origin requests to authorized domains.
| Origin | Purpose |
|---|---|
https://claude.ai |
Claude web application |
https://claude.com |
Claude web application (alternate domain) |
http://localhost:6274 |
Claude Desktop local proxy |
All other origins are rejected. Preflight OPTIONS requests return appropriate CORS headers for the allowed origins only.
| Property | Details |
|---|---|
| Protocol | HTTPS (TLS 1.2+) — all traffic is encrypted in transit |
| Transport | Streamable HTTP (MCP specification 2025-03-26) |
| Hosting | Supabase Edge Functions (Deno runtime, isolated per invocation) |
| Service key protection | Constant-time comparison (timingSafeEqual) prevents timing attacks |
The server exposes a /.well-known/oauth-protected-resource endpoint per RFC 9728, allowing MCP clients to discover the authorization server and required scopes before initiating the auth flow.
Error responses are designed to be informative for users without leaking internal details.
- A human-readable message explaining what went wrong
- An
SL-*error code for traceability and support - Suggested next steps when applicable
- Raw SQL errors or database error messages
- Stack traces or file paths
- Internal table names or column names
- Other users' data or organization details
All errors are returned as successful MCP responses with isError: true, ensuring the Claude client can relay the message to the user gracefully.
If you discover a security vulnerability in the SupaLedger MCP server, please report it privately.
Email: teams@fieldelevate.com
Please include:
- A description of the vulnerability
- Steps to reproduce
- The potential impact
- Your contact information (optional, for follow-up)
We will acknowledge receipt within 48 hours and work to resolve confirmed vulnerabilities promptly. We will not take legal action against researchers who report vulnerabilities in good faith.
Do not publicly disclose vulnerabilities before we have had a chance to investigate and remediate.