Trust + safety

Security model

A plain-English explanation of how we keep your Apple Search Ads account safe — including when you point an autonomous AI agent at it.

The blast radius is bounded

Even in the worst case — your API key leaks, your AI agent gets prompt-injected, every defense fails — an attacker can only affect campaigns inside your own connected Apple Search Ads org. They cannot buy ads for arbitrary apps, drain your bank account, exfiltrate other users' data, or impersonate anyone else. The worst they can do is misspend your ad budget on your own ads, up to your envelopes' daily caps. That's not great, but it's finite and reversible.

Apple credentials never leave Cloud Functions

Your Apple Search Ads private key is stored in Firestore under users/{uid}/credentials/apple — a path that's explicitly blocked from all client reads via Firestore rules. Only Cloud Functions running with the Admin SDK can read it. The MCP endpoint loads the credential per-request, signs the Apple OAuth JWT, hits Apple's API, and discards the credential before responding. It is never serialized into a tool result, never logged, never written to the audit log, never accessible to your AI agent.

API keys are scoped, capped, and revocable

Mutating actions require a pre-approved envelope

Before an agent can change a bid, raise a budget, pause a campaign, or deploy a new one, you create a spending envelope in the web UI: "Agents may spend up to $X/day on apps Y in countries Z, valid until ." The MCP endpoint enforces this on every mutating call — it loads your active envelopes, picks the first one that covers the action, and atomically charges the daily-spent counter before letting the tool run. No envelope, no mutation.

Outsized actions require out-of-band approval

If an action exceeds every envelope, the server doesn't just deny it. It creates a pendingApproval doc — describing the exact (tool, args) the agent proposed — and pushes a notification to your browser. You open it, review, click Approve, and the server mints a one-time token (60-second TTL) you paste back to the agent. The agent retries with the token. The server validates the token, confirms (tool, args) matches the SHA-256 digest it stored at approval time, consumes the token (single-use), and runs the tool.

Why this defeats prompt injection: the approval endpoint requires a Firebase Auth browser session — your signed-in browser tab. The agent only holds an API key. Even if an attacker takes over your agent's reasoning loop, they cannot reach the approval endpoint. And the digest binding means a token approved for "pause campaign 12345" cannot be reused to "delete campaign 67890."

Audit log

Every MCP call — successful, denied, errored — writes one entry to users/{uid}/auditLog. The entry records: tool name, redacted args (anything matching /secret|password|token|privateKey|apiKey/i is replaced with [REDACTED]), result, error code, IP, user agent, latency, and which authorization layer let it through (scope_only, envelope, or approval). You can review this live in the app under Settings → API access → View agent activity.

Kill switch

One button in the web UI sets users/{uid}/_meta/agentsBlocked = true and (optionally) revokes every active key in one batch. The MCP endpoint checks this flag right after authentication and refuses every call with HTTP 403 AGENTS_BLOCKED. The chat path does not check this flag, so you can still use the SearchAd web app normally while you investigate.

Anomaly detection

A scheduled function runs every 15 minutes. For each user with active keys, it compares today's MCP call count to the 7-day rolling baseline. If today is > 100 calls AND > 3× baseline, it pushes a notification so you can review. False positives erode trust, so the threshold is intentionally high.

Network surface

What we don't do

Responsible disclosure

Found a vulnerability? Email team@searchad.ai. We'll respond within 48 hours and credit you in the fix's release notes if you'd like. Please don't publish before we've had a chance to patch.