Bot-token credential tenancy
Bot-token credential tenancy
Purpose
Narrow the blast radius of a credential by using a purpose-built identity whose reach is inherently smaller than the acting user’s, rather than constraining what a broader credential can do after the fact. Permission lists are advisory; credential tenancy is structural.
Architecture
- Create a dedicated bot / service / project-scoped account on the target platform (Slack bot, GitHub App, Google service account, purpose-created free Granola account, etc.).
- The account’s own identity determines reach: a Slack bot can only see channels it’s invited to; a GitHub App can only see repos it’s installed on; a project Granola account can only see notes shared with it.
- Use the account’s token or installation credential for agent operations. Never use the operating user’s personal token to proxy agent actions.
- Where available, pair with a second-layer enforcement at the tool / MCP layer (env var pinning, tool registration allowlists) — see the two-layer MCP scoping recipe.
The central property: removing a permission from a broad credential requires ongoing vigilance; keeping a narrow credential narrow is the default. The mechanism inverts the failure mode from “scope creeps up” to “scope stays at ground” until a human explicitly broadens it.
Criteria advanced
PL4-least-privilegeIAM scoped read-only by default — direct level-2 contributor. Strict least-privilege is credible because the credential’s reach is structurally bounded, not because the permission list is currently short. A user OAuth with the same “current” scope is not equivalent — future permission additions to the upstream identity would silently broaden the agent’s reach.PL3-communicationCommunication actions — partial contributor to level-2. The mechanism narrows who the agent can reach, which is one of the five structural-safety layers level-2 requires (recipient allowlist); other layers (content filter, dry-run default, rate limiting, human approval) are independent.PL2-agent-audit-trailAgent action audit trail — partial contributor to level-2. Platform-level action logs are naturally scoped to the bot’s identity, giving clean separation between agent activity and human activity. Doesn’t provide decision-level reasoning on its own — that needs additional instrumentation.
Prerequisites
None structural — the recipe works from a clean start. The platform must expose a bot / service / project-account primitive, which most major platforms do. Where one doesn’t (legacy systems with user-only auth), this recipe is not applicable and a different mechanism is needed.
Failure modes
- Scope creep. The bot gets added to more channels / folders / orgs over time because each addition seems reasonable in isolation. A year later it can reach far more than anyone remembers. Mitigation: record scope rationale in the corresponding
internal/integrations/*entry, and treat every scope broadening as requiring an updated rationale line. Quiet broadening is the signal of mechanism failure. - Credential handling. Bot tokens are still secrets; a leaked token grants whatever the bot can reach. Mitigation: treat bot tokens as first-class secrets (see
CLAUDE.mdsecret handling), store in gitignored.env.localor equivalent, rotate on any exposure. - Platform startup scopes. Some platforms require broader read scopes than the agent’s registered tools use — e.g. Slack’s
users.list/conversations.listcache warm. These enumeration surfaces don’t reflect operational capability but can be mistaken for it during scoring. Mitigation: document the enumeration surface explicitly in the integration entry, distinguishing it from operational reach. - “Bot approves bot” loop collapse. If the bot account can approve PRs, sign releases, or merge changes that affect its own scope, the least-privilege property collapses at the first mistake. Mitigation: keep write-authority on policy artefacts (the PR-elevation template, the Slack app config) behind human credentials, not bot credentials.
Cost estimate
Low. Most of the cost is one-time platform setup: creating the account, installing the app, collecting the token. Ongoing cost is negligible for a single bot; the discipline investment is the scope-rationale convention, which only bites when someone tries to quietly broaden scope.
Case studies
-
Slack — Apptivity Lab workspace (
internal/integrations/slack-apptivity-lab.md). Bot token for#agentic-engineering, scoped to a single channel via two-layer enforcement (bot invitation +SLACK_MCP_ADD_MESSAGE_TOOLenv pin). Contrast: a user OAuth would have exposed every channel Jason sees in the Apptivity Lab workspace; the bot-token mechanism kept the agent’s reach to exactly what was deliberately granted. -
Adjacent: the Granola integration (
internal/integrations/granola.md) applies the same tenancy-narrowing principle on a platform without a formal bot primitive — a dedicated free account becomes the “bot” and collaborators share into it. That mechanism is captured separately as the project-scoped reader account recipe in the same family. Use bot-token where the platform offers a first-class bot primitive; use project-scoped reader account where it doesn’t.
Related recipes
- Same family as: project-scoped reader account — the no-bot-primitive variant of the same tenancy-narrowing principle.
- Composes with: two-layer MCP scoping (credential layer + tool-registration layer). Neither alone is sufficient; the pair is structural.
- Composes with: tool-level enumeration denial (keep
get_by_id, removelist/searchfrom the registered tool surface). Narrows the credential’s effective reach further at the tool layer. - Alternatives to: scoping a user OAuth via permission revocation. Same surface area on paper; structurally weaker because any future permission granted to the user silently flows to the agent.