Skip to main content
Glama
kbroughton
by kbroughton

downscoping-mcp

Reduce los privilegios de las credenciales de usuario a un subconjunto configurable para su uso por herramientas de IA. Un usuario ya debería tener un subconjunto de permisos para el trabajo diario que se asigna a servicios en proyectos GCP o cuentas AWS específicos. La reducción de privilegios (downscoping) se refiere a restringir aún más las acciones para cumplir con los estándares de la empresa.

Las concesiones de permisos suelen ser <Acción permitida> en <Recurso>. La reducción de privilegios afecta a la <Acción permitida> al reducir las capacidades, por ejemplo, de lectura/escritura a solo lectura.

Ejemplos

  • Permitir lectura pero no escritura en documentos de Google Drive

  • Permitir lectura de GitHub para PRs pero no fusionar o aprobar

  • Permitir la lectura de registros pero no el despliegue en un proyecto en GCP


Problema

Claude Code se ejecuta con las credenciales presentes en tu entorno. Un modelo que puede leer archivos también puede ejecutar gh repo delete, gcloud projects delete o aws iam delete-user, utilizando el mismo token. Un solo jailbreak, inyección de prompts o ataque de "diputado confundido" es suficiente para causar daños. Lo mismo ocurre con los errores accidentales: que Claude haga un push directamente a una rama de lanzamiento puede activar una canalización de despliegue si las protecciones de rama o las GitHub Actions no están configuradas correctamente.

Related MCP server: MCP Airlock

¿Por qué este enfoque?

La alternativa obvia es crear roles IAM o cuentas de servicio dedicadas de bajos privilegios para el uso de la IA: uno por equipo, por entorno. Esto se topa rápidamente con límites estrictos.

Un ~/.aws/config típico ya tiene más de 60 perfiles que cubren diferentes cuentas y roles. Duplicar eso con contrapartes de IA con privilegios reducidos significa más de 120 perfiles, mantenimiento continuo de IaC y configuración por ingeniero en .claude/settings.local.json para conectar el perfil correcto. AWS tiene una cuota predeterminada de 1000 roles IAM por cuenta (los límites más altos requieren una solicitud de aumento de cuota), y cada nuevo rol es una cosa más que auditar, rotar y mantener sincronizada con el original.

Esta herramienta adopta un enfoque diferente: reduce los privilegios dinámicamente en el momento de la llamada, sin tocar IAM. Funciona de forma análoga a aws sts assume-role --policy-arns, que restringe los permisos efectivos de un rol asumido a la intersección de las políticas del rol y los ARN de política suministrados. Aquí, la intersección se define en un archivo YAML incluido en tu proyecto en lugar de en un documento de política IAM, pero la semántica es la misma. Se utilizan tus credenciales existentes; sus capacidades efectivas se reducen por operación de acuerdo con las reglas que definas.

Se conserva una propiedad importante: esta herramienta solo puede reducir privilegios, nunca aumentarlos. Establece barreras de seguridad para mantener el uso de herramientas de IA seguro y conforme a la política de la empresa, sin requerir cambios en tu configuración de IAM.


Cómo funciona

Las reglas se evalúan de arriba a abajo para cada comando. La primera coincidencia gana. Son posibles tres resultados:

Acción

Comportamiento

allow

Inyecta el token con privilegios reducidos para la ranura coincidente; el comando continúa

review

Bloquea el comando; le dice a Claude que pida al usuario que lo ejecute manualmente

deny

Bloquea el comando; le dice a Claude que no está permitido para uso de IA

Los mensajes de bloqueo incluyen el nombre de la regla y el patrón coincidente para que el motivo sea siempre explícito.

Nivel 1 — Reducción de privilegios dinámica (preferido)

El STS nativo de la nube deriva un token restringido de tu credencial ambiental en el momento de la llamada. No se requieren nuevos roles IAM ni tokens pre-aprovisionados.

  • AWS: sts:GetFederationToken o sts:AssumeRole con una política en línea. Permisos efectivos = intersección de tus políticas de identidad y la política en línea. Ver docs/AWS_DOWNSCOPING.md.

  • GCP: Límite de acceso a credenciales a través de sts.googleapis.com. Restringe el token ambiental a recursos y roles específicos. Soportado solo para Cloud Storage. Para otros servicios de GCP, vuelve a la restricción de alcance OAuth. Ver docs/GCP_DOWNSCOPING.md.

Nivel 2 — Ranuras de token (alternativa)

Se utiliza cuando no existe una API dinámica. Se seleccionan tokens pre-aprovisionados con privilegios limitados por operación basados en reglas YAML.

  • GitHub: PATs de grano fino (no hay API de reducción de privilegios dinámica disponible). Ver docs/GITHUB_DOWNSCOPING.md.

  • Servicios GCP que no son GCS: Restricción de alcance OAuth a través de generateAccessToken. Solo granularidad a nivel de API.

  • kubectl: Tokens de ServiceAccount de Kubernetes vinculados a roles RBAC mínimos. Los clústeres EKS y GKE pueden usar la reducción de privilegios dinámica del proveedor de nube subyacente; ver docs/KUBECTL_DOWNSCOPING.md.

Dos modos de aplicación

Modo 1 — Gancho Bash (herramientas CLI)

Un gancho PreToolUse intercepta cada llamada a la herramienta Bash. Si el comando comienza con un binario de servicio conocido (gh, gcloud, aws, kubectl), el gancho compara los argumentos con tus reglas YAML, evalúa la acción y reescribe el comando con un token con privilegios reducidos o emite un mensaje de bloqueo. Claude nunca ve la reescritura.

Modo 2 — Proxy MCP

Un proxy MCP envuelve un servidor MCP ascendente. Antes de reenviar cada llamada a la herramienta, aplica las mismas reglas YAML para inyectar el token con privilegios reducidos para esa herramienta específica. Actualmente es compatible con el servidor github-pr-issue-analyser; otros servidores son una extensión futura.


Inicio rápido

1. Instalar

pip install -e .

2. Configurar credenciales

Exporta los tokens con privilegios reducidos en tu perfil de shell o entorno CI:

# GitHub (token_slot mode — only option for GitHub)
export GITHUB_TOKEN_READONLY=ghp_...       # fine-grained: contents:read, issues:read
export GITHUB_TOKEN_ORG_WRITE=ghp_...      # fine-grained: issues:write, pull_requests:write

# GCP (token_slot fallback — preferred is CAB via google.auth.downscoped)
export GCLOUD_TOKEN_VIEWER=ya29....
export GCLOUD_TOKEN_EDITOR=ya29....

# AWS (token_slot fallback — preferred is sts:GetFederationToken)
export AWS_ACCESS_KEY_ID_READONLY=AKIA...

3. Crear un archivo de política

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

Edítalo para que coincida con el modelo de acceso de tu organización. El campo downscope_mode selecciona el mecanismo por servicio:

version: 1

services:
  aws:
    downscope_mode: sts_policy      # Tier 1: derive restricted token from ambient creds
    inline_policy:
      Version: "2012-10-17"
      Statement:
        - Effect: Allow
          Action: ["s3:GetObject", "s3:ListBucket", "ec2:Describe*"]
          Resource: "*"
    rules:
      - name: "S3 writes require review"
        match:
          args_pattern: "s3 (cp|mv|rm|sync) .* s3://"
        action: review
      - name: "IAM mutations denied"
        match:
          args_pattern: "iam (create|delete|put|attach|detach)"
        action: deny

  gh:
    downscope_mode: token_slot      # Tier 2: GitHub has no dynamic API
    token_slots:
      readonly:
        env_var: GITHUB_TOKEN_READONLY
        inject_as: GITHUB_TOKEN
      org-write:
        env_var: GITHUB_TOKEN_ORG_WRITE
        inject_as: GITHUB_TOKEN
    default_slot: readonly
    rules:
      - name: "repo deletion denied"
        match:
          args_pattern: "repo delete|repo rename"
        action: deny
      - name: "pr merge requires human review"
        match:
          args_pattern: "pr merge"
        action: review
      - name: "permitted writes use org-write token"
        match:
          args_pattern: "pr (create|edit)|issue (create|edit)|push"
        action: allow
        slot: org-write

4. Registrar el gancho

Añádelo al archivo .claude/settings.json de tu proyecto:

{
  "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. (Opcional) Habilitar el proxy MCP

Añádelo a .mcp.json en la raíz de tu proyecto:

{
  "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"
      }
    }
  }
}

Referencia del archivo de política

Acciones de regla

rules:
  - name: "human-readable name — appears in block messages"
    match:
      args_pattern: "<regex matched against CLI args after the binary>"
      # OR for MCP tools:
      tools: [tool_name_1, tool_name_2]
    action: allow    # inject scoped token (default if action omitted)
    slot: readonly   # which token slot to use (action: allow only)

  - name: "example deny"
    match:
      args_pattern: "iam delete"
    action: deny     # blocked; Claude told it is not permitted for AI use

  - name: "example review"
    match:
      args_pattern: "s3 cp .* s3://"
    action: review   # blocked; Claude told to ask user to run manually

El orden de las reglas importa: las reglas se evalúan de arriba a abajo; la primera coincidencia gana. Coloca las reglas específicas de deny/review antes de las reglas generales de allow.

Orden de resolución de tokens (modo token_slot)

  1. Lee env_var del entorno del proceso actual

  2. Si no está configurado, vuelve a la variable inject_as (utiliza la credencial ambiental)

  3. Si ninguno está configurado, pasa el comando sin modificar


Arquitectura

Claude Code
    │
    ├─ Bash tool call ──► PreToolUse hook (hooks/pre_tool_use.py)
    │                          │
    │                          ├─ load .claude/downscoping.yaml
    │                          ├─ detect service binary
    │                          ├─ match args against rules → RuleDecision
    │                          │
    │                          ├─ action=deny   → {"continue": false, "stopReason": "...denied..."}
    │                          ├─ action=review → {"continue": false, "stopReason": "...run manually..."}
    │                          └─ action=allow  → {"updatedInput": {"command": "TOKEN=value <cmd>"}}
    │
    └─ MCP tool call ──► credential-downscope-proxy (mcp_proxy.py)
                              │
                              ├─ match tool name against MCP rules → RuleDecision
                              ├─ inject scoped token into env
                              └─ forward to upstream MCP server

Servicios soportados

Servicio

Binario / Interfaz

Modo de reducción

Doc

GitHub CLI

gh

token_slot

GITHUB_DOWNSCOPING.md

AWS CLI

aws

sts_policy (preferido), token_slot

AWS_DOWNSCOPING.md

Google Cloud

gcloud

credential_access_boundary (GCS), oauth_scope, token_slot

GCP_DOWNSCOPING.md

Kubernetes

kubectl

token_slot; EKS/GKE dinámico (futuro)

KUBECTL_DOWNSCOPING.md

Servidores MCP

proxy

token_slot

GITHUB_DOWNSCOPING.md

Se pueden añadir servicios adicionales extendiendo config.yaml: no se requieren cambios de código.


Notas de seguridad

  • Los valores de los tokens se escapan con shlex.quote antes de la inyección en el shell para evitar la inyección de comandos a través de valores de token manipulados.

  • Anteponer TOKEN=value antes de un comando hace que el token sea visible en los listados de procesos (ps aux). Para entornos de mayor seguridad, utiliza un ayudante de credenciales que inyecte tokens a través de un descriptor de archivo o un gestor de secretos.

  • Los mensajes de bloqueo incluyen el nombre de la regla y el patrón coincidente para que el motivo sea siempre auditable.

  • La alternativa al token ambiental inject_as significa que si aún no has aprovisionado un token con privilegios reducidos, los comandos pasan utilizando la credencial ambiental. Establece DOWNSCOPE_REQUIRE_SCOPED=1 (futuro) para endurecer esto.

  • .claude/settings.json que contiene rutas locales debe ser ignorado por git: consulta .gitignore en este repositorio.


Desarrollo

pip install -e .
pytest tests/

Licencia

MIT

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    C
    maintenance
    Policy-enforcing MCP proxy that blocks dangerous tool calls before they execute. Protects credentials, filesystem, shell, and databases across Claude Desktop, Cursor, Windsurf, and OpenClaw.
    6 npm
    39
    Apache 2.0
  • A
    license
    C
    quality
    D
    maintenance
    Enables secure, zero-trust access to MCP tools through short-lived, signed capability leases that bind tool execution to specific sessions, intents, and constraints. Prevents prompt injection attacks and privilege escalation with dynamic risk scoring, policy enforcement, and tamper-evident audit logging.
    4
    1
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    Security gateway for MCP tool calls. Sits between your LLM client and MCP servers, enforcing per-tool policies (allow/block/approve/read-only), logging every call, and pausing dangerous operations for human approval in terminal or Slack.
    0
    1
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Runtime proxy that intercepts and blocks MCP tool calls based on YAML-defined policies, enforcing security rules for AI agents like Claude Code or Cursor.
    44 npm
    1
    Apache 2.0