Skip to content

Instantly share code, notes, and snippets.

@marccampbell
Created February 17, 2026 14:57
Show Gist options
  • Select an option

  • Save marccampbell/ea62ce31fa4de2ad4840c4a7824b7186 to your computer and use it in GitHub Desktop.

Select an option

Save marccampbell/ea62ce31fa4de2ad4840c4a7824b7186 to your computer and use it in GitHub Desktop.
Bot Credential Broker - Product Plan

Bot Credential Broker

A service that provides scoped GitHub credentials to autonomous bots, with policy-based auto-approval and human-in-the-loop approval flows.

Problem

When running multiple autonomous bots (coding agents, CI helpers, etc.), each bot needs GitHub access to:

  • Read/write code
  • Comment on issues and PRs
  • Push commits
  • Create branches and PRs

Current approaches have drawbacks:

  • Shared PAT: All bots have same identity, no auditability, overly broad access
  • Per-bot PAT: Manual token management, no central revocation, still broad scopes
  • Per-bot GitHub App: Complex setup, overkill for small teams

Solution

A centralized credential broker that:

  1. Holds a single GitHub App's private key
  2. Issues short-lived, scoped tokens to bots on demand
  3. Enforces policy (which bot can access what)
  4. Supports human approval for sensitive requests
  5. Provides audit trail of all credential grants

Architecture

┌─────────────────┐     ┌─────────────────────┐     ┌─────────────────┐
│  Bot (machina)  │────▶│  Credential Broker  │────▶│  GitHub App     │
│  Bot (runless)  │     │                     │     │  (org-installed)│
│  Bot (slop)     │     │  - Policy engine    │     └─────────────────┘
└─────────────────┘     │  - Approval queue   │
                        │  - Audit log        │
        ▲               └──────────┬──────────┘
        │                          │
        │                          ▼
        │               ┌─────────────────────┐
        └───────────────│  Human (via chat/   │
          approval      │  CLI/notification)  │
                        └─────────────────────┘

Core Concepts

Bot Identity

Each bot has a known identity, verified by one of:

  • API key: Simple pre-shared secret per bot
  • Tailnet IP: If running on Tailscale, trust the source hostname
  • mTLS: Bot presents client certificate

Credential Types

Type Use Case Lifetime
Installation token API calls, HTTPS git 1 hour (GitHub limit)
Refreshable session Long-running work Auto-refreshes
Deploy key SSH git access Configurable

Commit Attribution

Since multiple bots share one GitHub App, commits appear as app-name[bot].

To distinguish which bot made a commit:

  • Commit message prefix: [machina] fix: the bug
  • Co-authored-by trailer: Co-authored-by: machina <machina@example.com>
  • Commit body metadata

The broker can enforce attribution format as part of policy.

Policy System

Structure

bots:
  machina-bot:
    identity:
      type: tailnet
      hostname: machina-bot
    
    auto_approve:
      # Repos this bot can access without asking
      - repo: "myorg/repo-a"
        permissions: [contents:write, issues:write, pull_requests:write]
      - repo: "myorg/repo-b"
        permissions: [contents:read]
    
    requires_approval:
      # Requests matching these need human approval
      - permissions: [admin]
      - repo: "myorg/sensitive-*"
    
    deny:
      # Always deny these
      - repo: "myorg/infrastructure"

defaults:
  # Unknown bots or unmatched requests
  requires_approval: true
  approval_timeout: 24h

Evaluation Order

  1. Check deny rules → reject if match
  2. Check auto_approve rules → issue token if match
  3. Check requires_approval rules → queue for approval if match
  4. Fall back to defaults

Approval Flow

Request Lifecycle

PENDING → APPROVED → ISSUED
    ↓         
  DENIED    
    ↓
  EXPIRED (timeout)

Approval Interfaces

Chat (primary for small teams)

🔐 Credential Request #a3f2

Bot: slopcannon-bot
Repo: myorg/new-project  
Permissions: contents:write, issues:write
Reason: "Implementing feature from issue #45"
Requested: 2 minutes ago

[Approve] [Approve + Remember] [Deny]
  • Approve: One-time approval, issue token
  • Approve + Remember: Add to bot's auto_approve policy
  • Deny: Reject with optional reason

CLI (for bulk/scripting)

# List pending
botcreds pending

# Approve
botcreds approve <request-id>
botcreds approve <request-id> --persist  # add to auto-approve
botcreds approve --bot=machina --all     # approve all pending for bot

# Deny
botcreds deny <request-id> --reason="Not authorized for this repo"

# Review recent grants
botcreds log --since=24h

Webhook (for automation)

  • POST to callback URL on approval/denial
  • Enables bot to wait asynchronously

Notifications

When approval is needed:

  1. Primary: Chat message to configured owner(s)
  2. Optional: Email, Slack, webhook

Include context:

  • Which bot
  • What repo/permissions
  • Why (reason from bot)
  • Quick-action buttons

Bot Integration

SDK / Client Library

from botcreds import BrokerClient

client = BrokerClient(
    broker_url="https://creds.internal",
    bot_id="machina-bot",
    api_key=os.environ["BROKER_API_KEY"]
)

# Request credentials
creds = client.get_credentials(
    repo="myorg/target-repo",
    permissions=["contents:write", "issues:write"],
    reason="Fixing bug #123",
    wait_for_approval=True,  # Block until approved (with timeout)
    ttl="1h"
)

# Use for git
os.environ["GIT_ASKPASS"] = creds.git_askpass_helper
subprocess.run(["git", "clone", creds.repo_url])

# Use for API
github = Github(creds.token)
repo = github.get_repo("myorg/target-repo")

Git Configuration

Broker returns ready-to-use git credentials:

{
  "token": "ghs_xxxx",
  "expires_at": "2026-02-17T15:00:00Z",
  "git": {
    "clone_url": "https://x-access-token:ghs_xxxx@github.com/myorg/repo.git",
    "askpass_script": "#!/bin/sh\necho ghs_xxxx"
  },
  "attribution": {
    "name": "myapp[bot]",
    "email": "123+myapp[bot]@users.noreply.github.com",
    "trailer": "Bot: machina-bot"
  }
}

Credential Refresh

For long-running operations:

# Option 1: Auto-refresh wrapper
with client.session(repo="myorg/repo", permissions=["contents:write"]) as session:
    # Token auto-refreshes before expiry
    do_long_running_work(session.token)

# Option 2: Manual refresh
creds = client.get_credentials(...)
# ... work ...
if creds.expires_soon():
    creds = client.refresh(creds)

Audit & Observability

Audit Log

Every action logged:

{
  "timestamp": "2026-02-17T14:30:00Z",
  "event": "credential_issued",
  "bot": "machina-bot",
  "repo": "myorg/anvil",
  "permissions": ["contents:write", "issues:write"],
  "approval": "auto",
  "token_id": "tok_abc123",
  "expires_at": "2026-02-17T15:30:00Z",
  "client_ip": "100.64.1.5",
  "reason": "Implementing feature #45"
}

Events:

  • credential_requested
  • credential_issued (with approval type: auto/manual)
  • credential_denied
  • credential_refreshed
  • credential_revoked
  • approval_requested
  • approval_granted
  • approval_denied
  • approval_expired

Metrics

  • Requests per bot
  • Auto-approve vs manual approval ratio
  • Approval latency (time to human response)
  • Token usage (API calls made with issued tokens, if trackable)
  • Denial rate

Alerts

  • Bot requesting unusual repos
  • Spike in credential requests
  • High denial rate
  • Credentials unused (issued but never used)

Security Considerations

Token Scope Minimization

  • Only grant permissions actually needed
  • Prefer repo-specific over org-wide
  • Short TTLs by default (1 hour)

Revocation

# Revoke specific token
botcreds revoke <token-id>

# Revoke all tokens for a bot
botcreds revoke --bot=machina-bot

# Revoke all tokens for a repo
botcreds revoke --repo=myorg/compromised

Revocation invalidates at GitHub level (installation token revocation API).

Bot Compromise Response

If a bot is compromised:

  1. Revoke all its active tokens
  2. Remove from policy (deny all)
  3. Audit recent activity
  4. Rotate bot's broker API key

Broker Security

  • Broker is high-value target (holds GitHub App private key)
  • Run on isolated infrastructure
  • mTLS or Tailscale-only access
  • No public internet exposure
  • Regular key rotation

Deployment

Simple (Single Node)

  • Broker runs as a single process
  • SQLite for state (pending approvals, audit log)
  • Config file for policy
  • Tailscale for bot access

Scaled

  • Broker behind load balancer
  • PostgreSQL for state
  • Policy in git (GitOps)
  • Multiple approval channels

Future Ideas

  • Temporary elevated access: "Give machina admin for 1 hour"
  • Access reviews: Periodic review of what bots have access to
  • Cost tracking: Map token usage to API rate limits
  • Multi-provider: Extend to GitLab, Bitbucket
  • Secrets beyond GitHub: AWS credentials, database access, etc.

Open Questions

  1. Approval UX: Chat buttons vs CLI vs web UI — which is primary?
  2. Policy storage: File vs database vs git repo?
  3. Bot registration: Self-service or admin-only?
  4. Token caching: Should broker cache tokens or always mint fresh?
  5. Failure mode: What happens if broker is down? Bots blocked or cached creds?

MVP Scope

Phase 1: Core Broker

  • GitHub App setup + JWT auth
  • Installation token minting
  • Simple file-based policy (auto-approve only)
  • Bot authentication via API key
  • Basic audit log

Phase 2: Approval Flow

  • Pending request queue
  • Chat notification + approval buttons
  • CLI for approval management
  • "Approve + remember" flow

Phase 3: Polish

  • Bot SDK/client library
  • Credential refresh
  • Revocation
  • Metrics/dashboard

This is a living document. Update as the design evolves.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment