Stop Giving AI Agents Permanent Permission: Build a Task-to-Scope Contract
A founder framework for turning an AI agent task into the smallest OAuth grant, handling partial consent, step-up access, revocation, and verifiable action receipts.
Cloudflare has introduced optional OAuth scopes for third-party clients. A client owner can now mark some requested permissions as optional, and a user can deselect them on the consent screen. The access token contains only the scopes the user actually approved. Cloudflare also makes an important distinction: required and optional status is evaluated against the scopes included in that particular authorization request, not every scope the client has ever registered.
That sounds like a small consent-screen improvement. For founders building AI products, it exposes a much larger design problem. An agent may be connected to email, calendars, files, deployment systems, customer records, or an MCP server with dozens of tools. The user usually asks for one bounded outcome: “find three available times and draft an invitation,” “summarize these invoices,” or “publish this approved landing-page change.” Yet many products obtain one broad, durable grant at onboarding and silently reuse it for future tasks.
This guide is for nontechnical founders, AI app-builder users, and small product teams shipping connected agents. You will leave with a task-to-scope contract, a permission matrix, a worked scheduling scenario, a partial-consent policy, an action receipt, failure tests, and a 48-hour rollout plan. The central judgment is: do not treat a connected account as permanent permission for every action the agent can technically perform. Compile each task into the smallest resource, scope, duration, and approval set that can complete it; then verify the granted set again at execution time.
This is a product and security framework, not a claim that optional scopes prevent prompt injection or misuse. Scope design can reduce the consequences of a mistake. It cannot decide whether an action is correct, whether a user understood a vague label, or whether the model interpreted the task properly.
What changed in OAuth consent, and what did not
Cloudflare's August 20 release adds a useful control at two layers. The developer may mark scopes as required or optional when configuring a client. During a specific authorization flow, the user may deselect optional scopes that the client actually requested. If the client requests only two scopes out of a larger registered catalog, only those two are evaluated and shown. Existing clients keep the previous behavior unless their owners opt in.The issued token reflects the approved subset. That means a client must inspect the granted scopes after exchanging the authorization code. It may not assume that every requested permission survived consent. Google's separate granular-permissions guidance makes the same implementation requirement: check what the user granted and disable or adapt the affected function when a scope is missing. Cloudflare says optional scopes are selected by default, so the feature does not automatically produce least privilege. The client still chooses the requested set, the developer still chooses defaults and labels, and the user may approve the entire bundle without studying it.
The release therefore creates an opportunity, not a guarantee. A good agent can request only what the current job needs, operate safely with a narrower grant, and explain why another permission is needed only when the task reaches that point. A poor agent can continue asking for every available permission at onboarding and label nearly all of them “required.”
This distinction matters beyond Cloudflare. Google's current OAuth guidance recommends incremental authorization: request a scope when a user invokes the feature that needs it, handle denial by disabling the affected function, and ask again only when the user clearly returns to that function. The underlying product lesson is stable across providers: consent should follow an understandable user intention, not the maximum capability of the integration.
Define the permission layers before designing the screen
Teams often use “permission” to describe several different controls. Keeping them separate prevents a polished consent flow from hiding an unsafe execution path.
- Task is the outcome the user asked the product to achieve. “Prepare a scheduling proposal” is a task; “use the calendar connector” is an implementation choice.
- Action is one external operation, such as listing calendar events, creating a draft, sending a message, deploying a build, or deleting a file.
- Scope is an OAuth permission label associated with a token, such as calendar read or event write. A scope is only useful if the resource server enforces it.
- Resource is the service or protected target for which the token is intended. It might be a particular MCP server, API, tenant, account, folder, project, or environment.
- Grant is what the user actually authorized. It can be narrower than what the client requested.
- Step-up authorization is a new consent event triggered when the current task reaches an action that needs additional permission.
- Approval is a product decision about a particular proposed action. OAuth consent to create calendar events is not approval to invite a particular customer at a particular time.
- Receipt is the durable evidence of what was requested, granted, attempted, and changed.
Why “the account is connected” is the wrong product state
A connection badge compresses too much information. It may mean the user authorized a read-only scope months ago. It may mean an administrator granted organization-wide access. It may mean a token can write but only to one tenant. It may mean the refresh token was revoked while the app still displays “connected.” It says nothing about whether the current task needs the permission or whether the proposed effect was approved.
Broad onboarding consent creates four predictable problems.
First, the user cannot connect a request to its consequences. “Connect workspace” may later authorize reading private records, changing infrastructure, or sending messages. Second, a compromised prompt or planning error inherits the entire grant. Third, partial consent becomes an edge case; the product may crash, repeatedly reprompt, or silently skip work. Fourth, the team cannot explain an incident because the only historical fact is that the user once clicked Allow.
The MCP ecosystem makes the mismatch visible. An MCP server can expose a broad catalog, while a general-purpose client may not understand the business meaning of every tool. The current MCP authorization specification says clients should request only necessary scopes, use protected-resource metadata and resource indicators, and add scopes incrementally through step-up flows. It also acknowledges a difficult reality: a general-purpose MCP client may lack the domain knowledge needed to select individual scopes intelligently. That domain knowledge must therefore live somewhere explicit—inside the product's task policy, not inside a model's improvised plan.
The task-to-scope contract
A task-to-scope contract is a versioned mapping from a user-visible goal to allowed resources, actions, scopes, duration, approvals, evidence, and failure behavior. It is a compiler in the product sense: it converts a high-level request into an executable permission plan that deterministic systems can check.
Use this seven-step flow.
1. Normalize the task without expanding it
Restate the requested outcome, target, and boundary. “Schedule the meeting” might mean propose times, create an event, invite participants, attach a document, and send a follow-up. Do not infer those sub-actions silently. Separate required actions from convenient additions.
2. Resolve the exact resource
Identify the provider, account, tenant, workspace, project, folder, and environment. OAuth scopes often describe action classes, not the object or tenant. The OAuth resource-indicator standard lets a client say which target service a token is intended for; the authorization server can downscope the token for that resource. Resource binding prevents “calendar write” for one service from becoming a bearer token accepted by another, but the application may still need finer server-side checks for a particular team or folder.
3. Expand the task into atomic actions
List reads, drafts, writes, sends, deletes, purchases, publishes, and administrative changes separately. A combined tool named prepare_and_send_campaign hides two different decisions. Prefer tools whose names and schemas reveal the effect.
4. Map each action to the narrowest enforceable scope
Use the provider's real scope taxonomy. Do not invent a fine-grained label if the API only provides a broad one. When a broad provider scope is unavoidable, compensate with server-side resource restrictions, short token lifetime, action allowlists, exact previews, and approval.
5. Divide initial, optional, and step-up access
Initial scopes are necessary to start the task. Optional scopes improve the result but are not necessary. Step-up scopes enable a later, higher-impact phase. Requesting write access only after the user approves an exact draft is often clearer than requesting it alongside read access at onboarding.
6. Bind approval to the proposed effect
Show the recipient, object, values, destination, cost, or diff immediately before a consequential action. The approval record should include a hash or stable identifier for the proposed payload and its base revision. If the payload changes, approval expires.
7. Verify and record the effective grant
After token exchange, compare requested and granted scopes. Immediately before each tool call, recheck identity, resource, scope, token status, task state, and approval. Record the provider's result identifier and the observed postcondition. A successful HTTP response is not enough when the product promised that a meeting exists, a message reached the right thread, or a deployment serves the approved revision.
A reusable permission matrix for product decisions
Use one row per atomic action. This matrix belongs in product requirements and acceptance tests, not only in security documentation.
| Task phase | External action | Resource boundary | Minimum scope | Consent timing | Separate approval? | Evidence | Safe denial behavior |
|---|---|---|---|---|---|---|---|
| Discover | Read free/busy windows | One calendar account | Calendar availability read | When scheduling starts | No | Query time, account ID, result count | Ask for times manually |
| Draft | Create local invitation preview | Product workspace only | None | None | No | Draft version | Continue locally |
| Commit | Create event | Selected calendar | Event write | After time is chosen | Yes | Exact title, time, guests, token grant, event ID | Export an .ics file |
| Notify | Send external message | Selected conversation/account | Message send | When user selects “send” | Yes | Recipient, body hash, provider message ID | Copy draft to clipboard |
| Attach | Read source document | Selected folder/file | File read | When user adds attachment | Maybe | File ID, version, classification | Continue without attachment |
| Recover | Delete mistaken event | Created event only | Event delete or manage | At recovery time | Yes | Original event ID, deletion result | Give manual recovery instructions |
The matrix exposes a frequent design error: write permission is treated as “core” because the full workflow eventually writes. In reality, much of the task can often proceed with no connector or read-only access. A useful preview lets the user evaluate value before granting the permission that creates an external effect.
Do not confuse more rows with better security. If a provider offers only calendar.manage, write the broad scope honestly. The matrix should show the gap between the desired permission and the enforceable permission so the team can decide whether compensating controls are sufficient.
Worked scenario: a scheduling agent with partial consent
Consider a two-person startup building a customer-success assistant. A founder asks: “Find three times next week for Maya and me, draft an invite about renewal planning, and schedule it after I choose.” The product has calendar, email, CRM, and file connectors.
An unsafe implementation treats all four connected accounts as one capability pool. It reads the CRM for context, searches email, reads the full calendar, looks for a renewal deck, creates an event, attaches the deck, and sends a separate message. The original sentence did not authorize several of those actions. A malicious note in the CRM could also influence a plan with durable write access.
The task compiler produces a narrower plan:
- Ask which Maya if identity is ambiguous.
- Request availability-read access for the selected calendar account.
- Compute three options locally and display the calendar account used.
- Draft an invitation locally using only the user's words. CRM and file access remain absent because they are not necessary.
- After the founder selects a time, request event-write access if it is not already granted.
- Present the exact title, guests, timezone, start/end time, description, conferencing setting, and reminders.
- Create one event only after payload-bound approval.
- Return the provider event ID and a direct link, then read the event back to verify guests and time.
.ics file, or let the user authorize event creation later. This is graceful partial consent: the product preserves useful work without crossing the denied boundary.
Suppose the user grants write access but denies calendar read. The app should not infer availability from unrelated email or silently switch accounts. It can ask the user for candidate times, then create the approved event. The granted scope set changes the executable plan; it does not change the user's intent.
The action receipt: prove permission and effect separately
OAuth logs answer part of an audit question. Product receipts should connect authorization to the exact task and external result without storing token secrets.
task_receipt:
task_id: task_8f31
task_policy_version: scheduling_v3
user_intent: "propose times, then create one approved event"
resource:
provider: calendar.example
account_id: acct_142
tenant_id: team_7
authorization:
requested_scopes: [availability.read, events.write]
granted_scopes: [availability.read, events.write]
token_fingerprint: sha256:7b1...
issued_at: 2026-08-21T01:14:09Z
expires_at: 2026-08-21T02:14:09Z
action:
tool: events.create
payload_hash: sha256:9ad...
approval_id: approval_233
approved_by: user_81
approved_at: 2026-08-21T01:18:42Z
result:
provider_object_id: evt_901
observed_state: guests_and_time_match
verified_at: 2026-08-21T01:18:45Z
recovery:
method: delete_event_by_id
status: available
Keep the receipt's retention proportionate to risk and user expectations. Store token fingerprints or internal references, never raw access or refresh tokens. Treat recipients, filenames, message bodies, and account identifiers as sensitive. The receipt is for accountability, not a shadow archive of every connected workspace.
For higher-risk actions, ordinary scope strings may be too coarse. OAuth Rich Authorization Requests defines structured authorization_details that can express a type of access plus action, location, or other domain-specific fields. Availability varies by provider, and structured authorization is not a substitute for server enforcement. It is useful evidence that the standards stack can represent more than a flat list of labels when a product and authorization server support it.
Failure modes that optional scopes do not solve
Everything is optional but preselected. Users still approve the bundle by inertia. Start with the smallest requested set; do not rely on people to subtract risk from a long checklist. Scopes describe tools, not tasks.files.write does not say which file, folder, tenant, or change. Add resource constraints and exact action approval.
The client ignores partial grants. It assumes requested equals granted, then fails mid-task or calls a forbidden tool. Compare the token response and build explicit fallback paths. RFC 8707 reiterates that an authorization server must return the effective scope when it differs from the request.
A model chooses its own permissions. A compromised document can persuade the planner that sending data is “necessary.” The model may propose actions; deterministic policy must map approved task types to maximum scopes and resources.
A durable refresh token outlives the task. A task-scoped UI with a long-lived broad refresh token preserves the old risk underneath. The OAuth security BCP says refresh tokens must remain bound to the scopes and resources consented to, and public clients need rotation or sender constraint. Product policy should also decide whether this task needs offline access at all.
Revocation is mistaken for undo. Revoking a token stops future authorized calls when systems enforce it. It does not recall an email, reverse a completed trade, restore a deleted record, or remove a published deployment. Define a compensating action and an escalation route for each effect.
The resource server does not enforce the label. A beautiful consent screen cannot repair an API that accepts out-of-scope operations. Run negative tests against the real backend.
Scope names are incomprehensible. Users cannot make a meaningful decision from objects.manage. Explain the task effect in product language while preserving the provider's exact scope in expandable technical details.
Eight tests before a connected agent can launch
- Minimum-set test: For every supported task, remove each requested scope in turn. If the task still succeeds, that scope was not minimum.
- Partial-grant test: Deny every optional scope separately and in combinations. The product must continue with a truthful reduced outcome or stop clearly.
- Step-up timing test: Confirm that write, send, publish, purchase, or delete access is requested only when the user reaches that action.
- Wrong-resource test: Present a valid token intended for another MCP server, account, tenant, or environment. The backend must refuse it. OAuth Protected Resource Metadata explicitly warns that a supported-scope list is not an instruction to request every scope and points clients back to operation-level minimization.
- Stale-grant test: Revoke access or remove a scope after planning but before execution. The tool call must stop; cached connector state must not override the current grant.
- Payload-change test: Approve an event, then change a guest, time, amount, branch, or destination. The prior approval must become invalid.
- Injected-expansion test: Place an instruction in retrieved content asking for an unrelated tool or broader scope. Policy must reject the expansion or request a new user-visible task.
- Receipt-and-recovery test: Complete an action, confirm the provider object and postcondition, then exercise the documented undo or compensation path.
Choose an operating mode by consequence
Not every connection needs a per-action consent ceremony. Use consequence, reversibility, and user expectation to choose the mode.
| Mode | Suitable work | Permission pattern | Required evidence |
|---|---|---|---|
| Preview | Public lookup, local drafting, formatting | No OAuth or read-only task scope | Source and draft identity |
| Assisted | Private read, reversible draft creation | Incremental read/create scope, short session | Granted set and created object ID |
| Confirmed action | Send, publish, invite, modify shared state | Step-up scope plus payload-bound approval | Full action receipt and postcondition |
| Restricted automation | Repeated low-variance actions with clear limits | Dedicated resource, allowlist, budget, expiry | Per-action receipts, anomaly alerts, pause control |
| Specialist-controlled | Money movement, privileged administration, regulated or safety-critical effects | Domain controls beyond ordinary consent | Qualified review, separation of duties, incident process |
Use a lower mode when the provider's permission taxonomy is much broader than the task, denial handling is weak, undo is unavailable, or the team cannot verify effects. Product ambition is not measured by how many consent prompts disappear.
When this framework fits, and when it is not enough
The task-to-scope contract fits agents that connect to SaaS APIs, MCP servers, calendars, messaging, content systems, code hosts, support tools, analytics, and deployment platforms. It is especially useful when one connector exposes both read and write operations or when a product serves many tenants.
It may be unnecessarily heavy for anonymous public data, an offline single-user tool with no external effects, or a disposable local transformation. Even there, filesystem and operating-system permissions may matter; OAuth is not the only authority system.
The framework is not sufficient for financial trading, payments, healthcare decisions, employment actions, critical infrastructure, privileged identity administration, or legal commitments. Those domains require limits such as transaction caps, dual control, segregation of duties, regulated records, specialist review, and incident obligations. A narrow OAuth token reduces blast radius; it does not prove the strategy, diagnosis, recipient, price, or decision is correct.
It also cannot rescue a provider with only one extremely broad scope. In that case, choose a dedicated low-privilege account, constrain resources behind your own service, issue short-lived credentials, keep high-impact actions manual, or decline the integration.
A 48-hour implementation plan for a small team
Hours 0–4: inventory real effects. Export the connector's tools and provider scopes. Mark every read, draft, write, send, delete, publish, spend, permission change, and administrative action. Record which tenant and object each tool can touch. Hours 4–8: define three priority tasks. Choose the connected workflows users perform most often. Write the intended outcome, required atomic actions, optional improvements, forbidden expansions, and safe reduced outcome. Hours 8–16: build the matrix and policy. Map each action to resource, minimum enforceable scope, consent timing, approval, evidence, and recovery. Put the maximum allowed plan in code or configuration outside the model prompt. Hours 16–24: implement partial consent. Read the effective granted scope set. Remove unavailable tools from the executable plan. Provide a useful reduced mode and contextual step-up flow. Never loop a denied prompt. Hours 24–32: add action binding and receipts. Hash or version consequential payloads, bind approval to them, record token references rather than secrets, capture provider IDs, and verify postconditions. Hours 32–40: run the eight failure tests. Use disposable tenants and fake records. Include a revoked token, a wrong resource, an injected scope-expansion instruction, and a changed payload. Hours 40–48: release narrowly. Start with one task, one connector, and assisted mode. Review denied scopes, abandoned consent, failed step-ups, forbidden tool attempts, receipt gaps, and recovery events. Expand only after the reduced path and incident path work.Founder launch checklist
- [ ] The user-visible task is defined separately from connector capabilities.
- [ ] Every external action maps to an exact resource and real provider scope.
- [ ] Initial requests contain only scopes needed to start the task.
- [ ] Optional and step-up scopes have honest reduced outcomes.
- [ ] The client checks the effective granted set after authorization and before action.
- [ ] Models cannot expand their own maximum permission policy.
- [ ] Consequential actions require approval bound to recipient, object, values, and revision.
- [ ] Tokens are short-lived or revocable where practical; offline access has a reason.
- [ ] Receipts contain no token secrets and have a proportionate retention policy.
- [ ] Backend negative tests prove wrong-scope and wrong-resource calls are refused.
- [ ] Revocation and recovery are treated as different operations.
- [ ] The product clearly says completed, reduced, waiting for permission, or failed.
References
- Cloudflare: From all-or-nothing to task-based OAuth consent
- Cloudflare: Securing non-human identities with automated revocation, OAuth, and scoped permissions
- RFC 9700: Best Current Practice for OAuth 2.0 Security
- RFC 8707: Resource Indicators for OAuth 2.0
- RFC 9728: OAuth 2.0 Protected Resource Metadata
- RFC 9396: OAuth 2.0 Rich Authorization Requests
- Model Context Protocol: Authorization
- Google OAuth 2.0 best practices
- Google: How to handle granular OAuth permissions