Study guides / CCAR-P / Domain 1

Integration · Lesson 2 of 8

1.2 - Authentication and Authorisation Gaps in Claude Integrations

Analyse where credentials live and whose authority each tool call uses in a Claude integration, and identify authentication and authorisation gaps such as shared tokens, token passthrough and prompt-only controls.

An integration is secure or insecure at each hop: user to application, application to Claude, the host to a tool, the tool to a downstream system. At every hop ask three questions. Who is the caller (authentication)? What may that caller do (authorisation)? Where does the secret live, and for how long (credential lifecycle)? Most gaps in Claude integrations are a hop where the answer is "nobody checked", because a shared credential or a prompt instruction stood in for a real control. This lesson is architect-level analysis, not SDK how-to; capability scope is Lesson 1.1 and guardrail layers are Domain 4.

Credentials for the Claude API itself

Anthropic's authentication docs describe three methods: an API key (a static secret sent as a bearer token; the legacy x-api-key header is still accepted), Workload Identity Federation (a short-lived token exchanged from your identity provider, so no static secret is distributed or rotated) and App Attest (short-lived tokens for attested iOS and macOS installations that call the API directly). The design questions:

Credentials and identity for tools and MCP servers

The model must never hold a secret. Credentials are attached by the tool implementation or the MCP host on the outbound call, never placed in the prompt. Some specifics worth knowing:

Per-user or shared identity: the confused deputy

The most consequential decision is whether a tool acts as the end user or as the service. A shared, broadly scoped service account makes the agent a confused deputy: it holds authority the requesting user lacks, and anything that steers it (a user request, or an instruction injected through tool output) can spend that authority. Delegated per-user tokens keep the downstream system's own authorisation in the loop. The MCP specification sets the rules for HTTP servers. The points below hold in revision 2025-11-25 and in the current 2026-07-28 revision; check the current revision before relying on section details:

Key concept: check authority where the data lives

The system that owns the data must decide, with the requesting user's identity, whether the call is allowed. A prompt, a tool description or an agent-side allowlist can reduce what the agent tries; only the resource can refuse what the agent is permitted to do.

Enforcement layers, from weakest to strongest

  1. Prompt or description ("only read"): guidance only; an injected instruction can override it.
  2. Agent-side permission rules: Claude Code evaluates deny, then ask, then allow, first match wins. Its Bash argument patterns are documented as fragile, so use the sandbox or a hook when a restriction must hold.
  3. Tool design: a get_order_status(order_id) tool rather than query_database(sql); an MCP allowlist that fails closed.
  4. Credential scope: a read-only database role or a narrowly scoped token behind the tool, so even a hijacked call cannot write.
  5. Resource-side authorisation with per-user identity, plus audit logs that record the user and the agent action.

For one-off privileged work, grant access for the task or session and revoke it afterwards; standing access granted for a past task is exactly what a later injection exploits.

Common exam distractor

"The key is no longer in the repository, so it is safe" ignores history, logs, tickets and chat, so rotate anything ever exposed. "Forward the user's token to the downstream API for simplicity" is the forbidden passthrough pattern. "MCP is an official protocol, so its servers are trusted" confuses the protocol with a particular server: a third-party server's tool results are untrusted input. And "log every call" is detective, not preventive, so it never closes an authorisation gap.

Exam traps

Practice question

A customer-support assistant uses an internal MCP server that fronts the company's CRM. The server authenticates to the CRM with a single service-account token that has read, update and export permissions. Users sign in to the chat application, but the CRM only ever sees the service account. A red team plants text in a support ticket that convinces the assistant to call the export tool for a different customer's records, and the call succeeds. Which change most directly closes this gap?

  • A Propagate the signed-in user's identity to the CRM with audience-bound, least-privilege delegated tokens so each call is authorised as that user, and remove the export tool from this assistant Correct

    It fixes both halves: authority is checked at the resource with the requester's identity rather than a shared deputy, and the unneeded high-impact capability is removed from the agent's toolset. Neither depends on the model behaving.

  • B Add a stronger instruction to the system prompt telling the assistant never to export data unless the user explicitly asks, repeat it in the export tool's description, and add a test that checks obvious export requests are refused

    It is still guidance. The same injection channel that changed the assistant's behaviour can override the instruction, and the service account would still permit the export.

  • C Rotate the service-account token weekly, store it in a secrets manager and restrict the network ranges that may present it, so a stolen token is less useful

    Good hygiene, but it does not change what the token is allowed to do. The confused-deputy path (an over-privileged shared identity steered by untrusted text) remains open between rotations.

  • D Log every tool call with its parameters, retain the logs centrally and alert the security team whenever an export occurs, so abuse is detected within minutes

    Logging and alerting are detective controls. They can help investigate after the fact but do not prevent the unauthorised export, so they do not close the authorisation gap.

Build exercise: Threat-model the credential flow of a support agent

Intermediate · 75 minutes

You'll practice:

  1. Draw the architecture for a support agent that uses the Claude API, one MCP server for a CRM and one for a ticketing tool. Label every hop and, for each, write which credential is used, who holds it and how long it lives.

    Gaps hide at hops nobody labelled. Writing the credential and its lifetime at each hop forces you to find the shared, static and long-lived ones.

    You should see: A diagram plus a table with columns: hop, caller identity, credential, lifetime, where stored.

    Hints
    1. Which hops carry the end user's identity, and which replace it with a service identity?
    2. Hops to include: browser to backend, backend to Claude API, MCP host to each MCP server, each MCP server to its downstream system. Mark every hop where the downstream system cannot see the end user.
    3. Example rows: backend to Claude API | service account key | held in secrets manager | expires in 30 days; MCP host to CRM server | bearer token from OAuth | refreshed hourly | held by host; CRM server to CRM | service-account token | static | env var (GAP: end user invisible).
  2. For each tool, decide whether it should act as the end user or as the service, and write the worst-case call an attacker could trigger with attacker-chosen parameters.

    This is the confused-deputy analysis: the answer determines whether authorisation can be enforced downstream.

    You should see: A list of tools, each with an identity model and a one-line blast-radius statement.

    Hints
    1. What could a hijacked call do that the requesting user could not do directly?
    2. Read-only lookups scoped to the user's own records can act as the user. Cross-customer operations (export, bulk update) must be either removed from this agent or gated behind a separate, approved path.
    3. Example: export_customers | service | can dump all customers | remove from support agent. get_ticket(ticket_id) | user-delegated | only tickets the user may see | keep.
  3. Audit the design against this checklist and record each finding with a severity: static long-lived keys; one shared broad token; token passthrough in the MCP server; access restricted only by prompt text; MCP server scoped globally instead of per project; secrets in prompts or logs.

    The checklist turns the abstract rule 'least privilege' into specific, checkable gaps that you can rank.

    You should see: A gap register with at least five rows: gap, hop, exploit story in one sentence, severity.

    Hints
    1. For each item, what would an attacker who controls one ticket's text be able to do?
    2. Rank by whether the gap lets untrusted content reach high-impact authority. Shared broad tokens and passthrough rank above missing rotation.
    3. Example row: CRM server holds one admin token | hop MCP server to CRM | injected ticket makes the agent export other customers' data | severity high.
  4. Design the fixes and name the enforcement layer for each. Write the MCP allowlist and the .mcp.json fragment that keeps credentials out of the file.

    Each fix must live at a layer the model cannot override. Naming the layer prevents 'add a prompt line' from sneaking in as a control.

    You should see: A fix table (gap, fix, enforcement layer) and two configuration fragments.

    Hints
    1. Which layer would still hold if the model followed every instruction in the ticket?
    2. Use resource-side authorisation with delegated per-user tokens, a read-only credential for lookups, and a fail-closed allowlist for the tools the agent needs.
    3. {"mcpServers":{"crm":{"type":"http","url":"https://crm-mcp.internal.example/mcp","headers":{"Authorization":"Bearer ${CRM_TOKEN}"}}}}
      Connector-style allowlist: {"type":"mcp_toolset","mcp_server_name":"crm","default_config":{"enabled":false},"configs":{"get_ticket":{"enabled":true},"get_order_status":{"enabled":true}}}
  5. Write the verification: an injection test that plants an instruction in ticket text asking for another customer's data, the expected result for the old and new design, and the audit evidence you expect to see.

    A fix is only credible when a test that used to succeed now fails at the resource, for the right reason.

    You should see: A test case with input, expected old behaviour (data returned), expected new behaviour (403 from the CRM for the user's identity, or tool absent), and the log fields that show user and action.

    Hints
    1. Where should the refusal come from, the model or the CRM?
    2. The refusal must come from the CRM (or from the tool being absent), not from the model declining. Also log user id, tool name and result status for each call.
    3. Pass criteria: with the injected ticket, the assistant may still try, but the CRM returns 403 for that user, no data is returned, and the audit log records the user id, the tool and the denied status.

Sources