Security.
Scrubber MCP keeps every customer’s data strictly isolated to the org the request was authenticated against, logs every tool call to an append-only audit trail, and gives admins a one-click path to revoke any key the moment it’s suspected. Below is the full security model a buyer can take to their team.
Bearer-token API keys, scoped to one Salesforce org.
Every Scrubber MCP request must carry an Authorization: Bearer header with an API key issued from /api-keys. Each key is bound to a single Salesforce org at the moment of issuance and cannot be used to query any other org — the binding is recorded server-side and is the authoritative scope for every downstream check, regardless of what the caller claims in the request body, headers, or path.
Keys carry a short, human-readable prefix that ties them visually back to the issuing org, plus a long, high-entropy random portion generated from the platform’s hardware-backed random source. Both halves are required at validation time; only the long random portion is treated as the secret.
Keys are one-way hashed at rest using a memory-hard, industry-standard password-hashing function with per-key salts. The raw key is shown exactly once, at creation, and is not retrievable thereafter — if a key is lost, it must be rotated rather than recovered. The verification step uses constant-time comparison so the time taken to reject an invalid key cannot be used to infer how many leading characters matched.
Rotation and revocation are one-click actions in the admin UI. Revocation takes effect immediately on the next request — no cache flush, no propagation delay, no grace period — because the lookup query that authenticates a request filters revoked keys out at the database level. Tokens are accepted only in the Authorization header; any token sent in the URI query string is rejected, both to prevent accidental capture in server logs and to comply with the MCP specification’s token-transmission rule.
Each key carries a fixed allowlist of toolsets, set at issuance. Requests can narrow that allowlist on a per-call basis using the X-Scrubber-Toolsets header but can never widen it: a request that names a toolset the key wasn’t issued for is rejected with HTTP 403 before the tool body runs.
Every query is gated by org id at the database layer.
Every Scrubber MCP tool resolves the caller’s org id from the API key on the way in, binds that org id to the request scope, and filters every database read by org id at the SQL level. There is no cross-org join in any tool implementation, and no tool reads the org id from request input rather than from the authenticated token.
Routes that take an org id in the URL path enforce a second check: the path org id and the auth-token org id must match exactly. Mismatches return HTTP 403 org_mismatch before the tool body runs — an API key for org A invoked against a path scoped to org B is rejected by construction, not by downstream check, so the failure mode is uniform across every route rather than dependent on each tool author remembering to re-check.
The same scope guard applies to administrative views: list endpoints reject empty org ids outright, and helper queries that load lists of resources require an explicit org id parameter rather than accepting an implicit default. A misconfigured caller cannot issue an unscoped query that returns rows from multiple tenants — the helper functions raise rather than fall back.
Each customer’s data lives in its own logical partition keyed by org id. There is no shared scratch space, no unscoped queue, and no background job that operates over multiple tenants’ rows in the same transaction.
Per-key, per-minute, configurable.
Each API key has its own rate-limit counter. The default ceiling is 60 calls per minuteand is configurable per key between 1 and 1000 calls per minute at issuance. One customer’s traffic cannot consume another customer’s quota; the counters are isolated by key, so a noisy automation in one tenant cannot starve interactive callers in another.
Requests that exceed the ceiling are rejected with HTTP 429 and a Retry-After response header indicating how many seconds to wait before retrying. Rejected requests do not consume quota — only accepted calls are counted — so a client backing off correctly does not get punished further. The response body carries the limit, the remaining headroom, the seconds until the window resets, and a machine-readable error code so an agent can adapt without parsing prose.
Limits can be raised on request through the admin path; the upper bound is set per key, not per org, so a customer who needs a higher-throughput key for a batch workflow can have one without loosening their interactive keys.
Every tool call is logged.
Every tool invocation produces an audit row, success or failure. Each row records the tool id, the toolset, the resolved org id and key id, the timestamp, the duration, the outcome (ok, error, rate_limited, or unauthorized), an error code if the call failed, the size of the response, and a request id that ties it back to the calling client.
Tool inputs are recorded too, but sensitive fields are stripped before storage. Any input field whose name matches apiKey, bearer, authorization, password, secret, or token (case-insensitive, at any nesting depth) is replaced with [REDACTED] before the row is written. The raw key is never persisted in the audit record.
The audit log is append-only. Audit history outlives the keys that produced it — if a key is hard-deleted, the rows it generated remain attributable to the customer’s org for billing reconciliation and abuse investigation. The audit-log writer is contracted not to throw: a logging outage cannot cascade into a tool-call failure, and a tool-call failure cannot leave a half-written audit record.
Failures are logged with the same fidelity as successes. A request that gets rate-limited, an unauthenticated probe, and a tool that throws on bad input all leave matching audit rows, so a security reviewer reading the log can reconstruct exactly what each caller attempted, not only what they completed.
Admins review the log at /admin/api-keys. The list view is itself org-scoped: an admin can only see audit rows for orgs they have permission to administer.
Issuance, rotation, and revocation.
Issuance
Two issuance paths share the same hash format and the same audit decoration. Self-serve users issue keys at /api-keys for their own org, capped at 5 active keys per user, 60 calls per minute, and the read-only intelligence toolsets. Admins issue broader-scoped keys at /admin/api-keys for any org they administer, with rate limits up to 1000 calls per minute and any toolset on the catalogue.
Display
The raw key is shown once at the moment of creation, with a copy button. After that, only the eight-character prefix and the human-readable label are ever surfaced — in the admin UI, in audit rows, and over the API. The raw key is never logged.
Rotation
Rotation runs as a single atomic operation: the new key is issued with the same org binding, label, toolsets, and rate limit as the predecessor; the old key is revoked; the new raw key is returned once. There is no window where both keys are active and no window where neither is — rotation either succeeds completely or rolls back. Admins who need a longer overlap window for a phased-migration deployment can issue a fresh key with a different label and revoke the predecessor manually once the rollout is confirmed.
Revocation
Revocation is idempotent and immediate. The lookup query that authenticates a request filters revoked keys out at the database level, so the very next call after a revoke fails on auth without any cache flush. Calling revoke a second time on a previously-revoked key is a safe no-op rather than an error, so incident-response automation can replay the action without having to track per-key state.
Ownership
Self-serve mutations require a double check: both the org id and the user that originally created the key must match the current session, so one user cannot rotate or revoke another user’s self-issued key, even within the same org. Admin actions are gated by a separate session check and are themselves audited.
Cached scan data only — no live Salesforce calls.
Scrubber MCP serves data exclusively from the scan cache that Scrubber maintains for your org. No tool invocation issues a live Salesforce API call in response to the request — your data stays in the database between scans, and Salesforce never sees the MCP call at all.
Cache refresh is handled by Scrubber’s own scan pipeline, which uses the OAuth credentials granted at org-connection time — not the MCP key. The MCP surface is read-only with respect to Salesforce.
The natural-language semantic search tool sends the query string through the same redaction rules as any other tool input before it lands in the audit row, so sensitive terms in a query never get persisted alongside the request.
Tool responses include user names and ids where the underlying scan signal does — for example, when reporting on permission set assignments. Scrubber does not redact those identifiers at the MCP boundary: the calling agent is operating with the customer’s own MCP key, gated to the customer’s own org id, and any data the agent retrieves from Salesforce directly via its own permissions already includes those identifiers. Redaction at the MCP layer would not change the agent’s effective access and would degrade tool utility.
HTTPS only.
The MCP endpoint is https://mcp.scrubber.io/api/mcp. Plain-HTTP requests are redirected to HTTPS at the edge; the redirect cannot be disabled. TLS 1.2 and 1.3 are accepted with AEAD ciphers and forward secrecy. HSTS is enforced with a two-year max-age on custom domains.
The local-development stdio transport speaks the same JSON-RPC dialect over standard streams and has no network exposure of its own — the API key is read from the process environment, not from a network header.
How to report a security issue.
Email security@scrubber.io with a description of the issue and the steps to reproduce it. We aim to acknowledge new reports within one business day and will coordinate a fix and a disclosure timeline with the reporter. Researchers can also reach the project owner directly at brett@cumulusvision.com.