Skip to main content
Glama
kbroughton
by kbroughton

downscoping-mcp

Least-privilege credential injection for Claude Code. Intercepts Bash and MCP tool calls before they execute, swapping in the minimum-privilege token required for each operation based on a declarative YAML policy.

Problem

Claude Code runs with whatever credentials are present in your environment. A model that can read files can also call gh repo delete, gcloud projects delete, or aws iam delete-user — using the same token. A single jailbreak, prompt injection, or confused-deputy attack is enough to cause damage.

Solution

Two complementary enforcement modes:

Mode 1 — Bash hook (CLI tools) A PreToolUse hook intercepts every Bash tool call. If the command starts with a known service binary (gh, gcloud, aws, kubectl), the hook pattern-matches the arguments against your policy rules, selects the appropriate token slot, and rewrites the command to prepend the scoped credential before execution. Claude never sees the rewrite.

Mode 2 — MCP proxy An MCP proxy server wraps an upstream MCP server. Before forwarding each tool call, it injects the scoped token for that specific tool into the subprocess environment. Read-only tools get a read-only token; write tools require an elevated token.

Quick Start

1. Install

pip install -e .

2. Configure credentials

Export scoped tokens in your shell profile or CI environment:

export GITHUB_TOKEN_READONLY=ghp_...      # fine-grained: contents:read, issues:read
export GITHUB_TOKEN_ORG_WRITE=ghp_...     # fine-grained: issues:write, pull_requests:write
export GCLOUD_TOKEN_VIEWER=ya29....
export GCLOUD_TOKEN_EDITOR=ya29....

3. Create a policy file

Copy config.example.yaml to .claude/downscoping.yaml in your project root:

cp config.example.yaml .claude/downscoping.yaml

Edit the rules to match your org's access model. The file is .gitignore-safe for personal token slot names; the structure itself can be committed.

4. Register the hook

Add to your project's .claude/settings.json:

{
  "env": {
    "CLAUDE_PLUGIN_ROOT": "/path/to/downscoping-mcp"
  },
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/pre_tool_use.py",
            "timeout": 5
          }
        ]
      }
    ]
  }
}

5. (Optional) Enable the MCP proxy

Add to .mcp.json in your project root:

{
  "mcpServers": {
    "credential-downscope-proxy": {
      "command": "python3",
      "args": ["-m", "credential_downscope.mcp_proxy"],
      "env": {
        "PYTHONPATH": "${CLAUDE_PLUGIN_ROOT}/src",
        "GITHUB_INTEGRATION_SRC": "/path/to/upstream-mcp-server/src"
      }
    }
  }
}

Policy File Reference

version: 1

services:
  gh:
    token_slots:
      readonly:
        env_var: GITHUB_TOKEN_READONLY   # read from process env
        inject_as: GITHUB_TOKEN          # injected into subprocess
      org-write:
        env_var: GITHUB_TOKEN_ORG_WRITE
        inject_as: GITHUB_TOKEN
    default_slot: readonly               # used when no rule matches
    rules:
      - name: "writes need elevated token"
        match:
          args_pattern: "pr (create|merge|edit)|issue (create|edit)|push"
        slot: org-write

Token resolution order:

  1. Read env_var from the current process environment

  2. If unset, fall back to the inject_as variable (uses the ambient credential)

  3. If neither is set, pass the command through unmodified

Architecture

Claude Code
    │
    ├─ Bash tool call ──► PreToolUse hook (hooks/pre_tool_use.py)
    │                          │
    │                          ├─ load .claude/downscoping.yaml
    │                          ├─ detect service binary
    │                          ├─ match args against rules
    │                          ├─ resolve token slot
    │                          └─ rewrite command: TOKEN=value <original command>
    │
    └─ MCP tool call ──► credential-downscope-proxy (mcp_proxy.py)
                              │
                              ├─ receive tool call from Claude
                              ├─ match tool name against MCP rules
                              ├─ inject scoped token into env
                              └─ forward to upstream MCP server

Supported Services

Service

Binary

Token env var pattern

GitHub CLI

gh

GITHUB_TOKEN

Google Cloud

gcloud

CLOUDSDK_AUTH_ACCESS_TOKEN

AWS CLI

aws

AWS_ACCESS_KEY_ID

Kubernetes

kubectl

KUBE_TOKEN

MCP servers

via proxy

configurable per server

Additional services can be added by extending config.yaml — no code changes required.

Security Notes

  • Token values are shlex.quote-escaped before shell injection to prevent command injection via crafted token values.

  • Prepending TOKEN=value before a command makes the token visible in process listings (ps aux). For higher-security environments, use a credential helper that injects tokens via a file descriptor or secrets manager.

  • The hook never blocks commands (exit 0 always); it only rewrites them. Blocking logic can be added by returning {"continue": false} from the hook.

  • .claude/settings.json containing local paths should be gitignored — see .gitignore in this repo for an example.

Development

pip install -e ".[dev]"
pytest tests/

License

MIT

-
security - not tested
F
license - not found
-
quality - not tested

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/kbroughton/downscoping-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server