# Identity, scopes, custody and revocation

Who each connection signs in as, what it can reach, how scopes are chosen or fixed, how to prove what was granted, where credentials live and how to take access away. Checked on 2026-11-01, Google Ads, GA4 and Amplitude again on 2026-11-06. Revocation menu paths were not all verified: confirm each one in the vendor UI at run time.

## Identity rules

- Connect as the person who does the job, with the lowest role that passes T3. Never use an admin to make it work; when T3 fails for access reasons, add one missing permission at a time.
- Count reachable accounts in T1. More than the job needs is WARN: narrow where the vendor allows (project-scoped keys, property-level access, project roles, asset assignment). Another client's production account reachable from an agency identity is FAIL.
- A shared login for a team hides who did what and makes revocation all-or-nothing. Prefer per-user OAuth.
- Service identities only for unattended work: read-only, named after the job, with an owner and a rotation date. Meta rejects Admin-role system user tokens; system users must have the Employee role.
- A scope or connector reinstall changes the tool list: rerun T2 and save a new baseline.

## Three scope models

Scope chosen at consent. Request the read scope only.

- Customer.io: `read` (the default, no sensitive data), `read:sensitive`, `write` (drafts), `write:live` (sends, subscriptions, suppressions) and `configure` (integrations, webhooks, settings) are independent; grant less than you hold.
- Google OAuth clients you own: mint with `analytics.readonly`, `webmasters.readonly` or `tagmanager.readonly` instead of the edit scopes.
- `oauth.scopes` in a Claude Code JSON entry, and `codex mcp login --scopes`, narrow what the client requests.
- open-gsc-mcp: `auth login --read-only` requests the read-only Search Console scope.

Scope fixed by the vendor. Enforce with role, IAM or client blocks, and record the credential as write-capable.

- HubSpot: scopes follow the tools at install and change as tools change; users reinstall to get new scopes.
- Google Ads: the `adwords` scope, which can write through any other client or script. A dedicated or unattended identity with Read-only access on the account cannot edit that account.
- BigQuery MCP: the `bigquery` scope.
- Stape's hosted GTM endpoint: all seven Tag Manager scopes, including publish, delete and user management.

Permission-based. The role or permission set at the source is the real ceiling.

- Meta: permissions granted in Facebook Login for Business. Minimum `ads_mcp_management` plus `ads_read` or `ads_management`; read-only means never `ads_management`. A dedicated identity also gets a reporting-only ad account role (confirm the role names at run time).
- Amplitude: Use MCP (read) `USE_MCP_READ` and Use MCP (write) `USE_MCP_WRITE` per project, checked on every call and overriding admin status. Every role grants read; Member, Manager and Admin also grant write. Amplitude names only tracking-plan changes under Use MCP (write), and custom roles need an Enterprise plan, so the layer is Viewer on the project, or on Enterprise a custom role with Use MCP (read) only, plus a client allowlist of read tools, or blocks on `use_amp*`, `share_amp_entities`, `manage_*` and `run_amp`.
- Mixpanel: project roles; no read-only switch on the server.
- Braze: dashboard permissions plus the Use MCP Server permission, which is off by default and granted per user. Team-scoped access can break some tools.
- Salesforce: profile, permission sets, field-level security and sharing, plus the endpoint you choose (sobject-reads).
- Snowflake: OAuth sessions run as the connecting user's `DEFAULT_ROLE` (clients such as Claude request `session:role:all`, which means the default role), so `MCP_READER` must be that user's default role, with a `DEFAULT_WAREHOUSE` set, or sessions fail to initialise. `ALLOWED_ROLES_LIST` only limits which roles may connect; keep it to `MCP_READER` with `OAUTH_USE_SECONDARY_ROLES = NONE`. Server access does not grant tool access: each tool needs its own grant.

Roles that are set per project or workspace (Mixpanel, Amplitude, Braze) mean a sandbox T4 proves production only when the identity holds the same role in both: record both.

## Evidence of what was granted

Record granted scopes from these calls or from the consent screen, never from what was requested.

- HubSpot: `get_user_details`, then `get_organization_details`.
- Stape GTM: `gtm_auth_status` returns `grantedScopes` and `missingScopes` without showing the token.
- Customer.io: `cio_auth_status` returns accessible workspaces and granted scopes.
- Amplitude: `get_amplitude_context` returns user, organization and accessible projects.
- Braze: `get_workspaces` returns workspaces with their `app_group_id`.
- Klaviyo: `get_account_details`.
- GA4: `get_account_summaries` lists every property the identity reaches.
- Google Ads: `customers_list_accessible_customers`.
- BigQuery: `list_dataset_ids` lists every dataset the identity reaches.
- open-gsc-mcp: `auth status` and `list_sites`.
- PostHog: the pinned project, with switch-project tools absent in tools mode.
- Snowflake: `SELECT CURRENT_ROLE(), CURRENT_WAREHOUSE()` through the read-only SQL tool returns `MCP_READER` and the MCP warehouse.

## Custody

- OAuth tokens stay in the client's secure storage. Nobody pastes them into chat, config or tickets.
- API keys go in only through env or input references, never in URLs. Some vendor setups put a key in the URL query: treat that whole URL as a secret (scanner S04).
- An environment variable is a delivery mechanism, not protection: subprocesses can read it. Prefer the client's secure input or OAuth.
- Google application default credentials: owner-only permissions, created with `gcloud auth application-default login --scopes ...` or service account impersonation. Avoid service account key files.
- Application default credentials are one file per machine, and each `gcloud auth application-default login` overwrites it. A login for another tool replaces the `analytics.readonly` grant and GA4 calls fail with insufficient scopes. Detect: insufficient-scope errors right after someone else's login. Fix: a dedicated credential file per server through `GOOGLE_APPLICATION_CREDENTIALS`, kept outside the repository, or impersonation.
- A team-created Google OAuth client (the GA4 `--client-id-file` login, open-gsc-mcp's client file, a Google Ads OAuth client) whose consent screen is External with publishing status Testing gets refresh tokens that expire after 7 days. Detect: `invalid_grant` about 7 days after setup. Fix: the Internal user type for a Workspace organisation, or a published app.
- Record `adwords` and `cloud-platform` credentials as write-capable or sensitive, whatever the MCP server can do.
- Credential files live outside repositories (scanner S12). If one was committed, its owner rotates it; deleting the file does not revoke it.

## Data exposure

- Read-only limits changes; it does not limit what the agent can read and repeat. Prefer aggregate tools; enable person-level tools (contacts, profiles, conversations, `read:sensitive`) only when the job needs them.
- HubSpot Sensitive Data blocks calls, emails, meetings, notes, tasks and conversations through MCP, though not through the standard CRM APIs.
- Braze MCP tools return no user-profile data.
- Klaviyo's `disable-tools-with-user-generated-content=true` removes tools that read customer-written content.
- CRM, lifecycle and support tools connect only to a client or workspace that `decisions.md` approves for customer data (SKILL.md step 4); otherwise draft the request for the privacy owner and stop for that tool.

## Revocation per vendor

Verify each path in the vendor UI at run time and record the steps in the inventory's Revoke by line.

- Claude Code: `claude mcp logout <name>`, then `claude mcp remove <name>`. Remove alone deletes local tokens without revoking them.
- Chat apps: disconnect the connector; owners can remove it for the organization or disable the plugin.
- HubSpot: uninstall the connected app in the portal.
- Salesforce: revoke the app's OAuth tokens and deactivate the External Client App if it is no longer needed.
- Google (GA4, Google Ads, Search Console): `gcloud auth application-default revoke` for application default credentials; remove the app's access in the Google account for OAuth clients.
- BigQuery: remove the roles or tighten the deny policy.
- Snowflake: disable the security integration, revoke the role, or reset the user's default role.
- Meta: remove the app's business integration.
- Braze: remove the Use MCP Server permission; a company admin can revoke every MCP OAuth token at once or turn MCP OAuth access off. Successful connections appear in the security event report.
- Klaviyo: remove the connector and the app authorisation in Klaviyo; rotate any private key that sat in a config file.
- Intercom: the user disconnects the connector and clears the client's saved authentication; an admin removes the app's access in Intercom (path not on Intercom's MCP page: verify it at run time).
- Customer.io: each user revokes their own sessions at Settings > Personal Settings > View sessions; admins cannot see or revoke other users' sessions, so offboarding means deactivating the user or turning MCP off in Settings > AI.
- Amplitude: block MCP at Settings > Content Access > MCP.
- Mixpanel: the organization's AI settings toggle.
- PostHog: revoke the MCP Server key, or sign out of the OAuth connection.
- open-gsc-mcp: `auth logout`.
- Offboarding an employee or agency runs every revoke step for their identities, then T1 to T4 on whatever replaces the connection.
