
Aplica delegación de privilegios mínimos para agentes de IA con credenciales firmadas y con alcance definido. Otorga a los subagentes capacidades y recursos limitados, verifica las acciones antes de su ejecución y previene la escalada de privilegios.
Tu agente generó un sub-agente y le entregó la misma clave API. Ese sub-agente ahora puede desplegar en producción, leer la base de datos de pagos y fusionar cambios en la rama principal.
Pigeon lo impide. Le entregas al hijo un Pigeon Pass: una credencial reducida y firmada que define lo que puede hacer, no una copia de todo lo que tú puedes hacer.
Python 3.12 o superior.
git clone https://github.com/pigeonlabsHQ/pigeon.git
cd pigeon
pip install .
from pigeon import grant, verify
authority = grant(
subject="agent:deployer",
capabilities=["deploy"],
resources=["environment:staging"],
)
allowed = verify(authority, action="deploy", resource="environment:staging")
assert allowed.allowed
denied = verify(authority, action="deploy", resource="environment:production")
assert not denied.allowed
assert denied.reason_code == "RESOURCE_NOT_ALLOWED"
print(denied.reason_code, denied.message, denied.details)
verify nunca devuelve un booleano simple. Una denegación incluye un código de motivo, un mensaje y la comparación que falló (requested vs allowed).
Pruébalo sin escribirlo tú mismo:
python examples/01_infrastructure.py
python demo/agent.py
No hay ningún servidor Pigeon al que conectarse. Cambias dos lugares que ya tienes:
delegate(...) y entrégale al hijo un Pass.verify(...) y no ejecutes la herramienta si se deniega.Mantén el secreto real en el runner. El hijo lleva el Pass.
from pigeon import delegate, grant, verify, DelegationError
parent = grant(
subject="agent:orchestrator",
capabilities=["deploy", "open_pr"],
resources=["environment:staging", "repo:acme/api"],
constraints={"max_deploys_per_hour": 3},
)
worker = delegate(
parent,
subject="agent:pr-bot",
capabilities=["open_pr"],
resources=["repo:acme/api"],
constraints={"max_deploys_per_hour": 3}, # cannot drop a parent constraint
)
result = verify(worker, action="open_pr", resource="repo:acme/api")
assert result.allowed
denied = verify(worker, action="deploy", resource="environment:staging")
assert denied.reason_code == "CAPABILITY_NOT_GRANTED"
try:
delegate(worker, "agent:rogue", ["open_pr", "deploy"], ["repo:acme/api"])
except DelegationError as exc:
assert exc.reason_code == "PRIVILEGE_ESCALATION"
Un hijo no puede añadir capacidades, ampliar recursos, elevar un límite ni eliminar una restricción del padre. Si Pigeon no puede demostrar que el hijo es más restringido, lo rechaza.
Si el runner nunca llama a verify, el Pass es solo decoración.
Este es un punto de aplicación de políticas, no parte del protocolo. El cliente genera un Pass más restringido por cada llamada a herramienta. El servidor lo verifica antes de que la herramienta se ejecute.
from pigeon import grant
from pigeon.integrations.mcp import execute_tool, pass_for_tool
parent = grant(
subject="agent:github",
capabilities=["create_issue", "merge_pr"],
resources=["mcp:github"],
)
tool_pass = pass_for_tool(parent, "create_issue", "mcp:github")
def create_issue(*, title, body):
return {"created": True, "title": title}
ok = execute_tool(tool_pass, "create_issue", "mcp:github",
{"title": "bump deps", "body": "automated"}, create_issue)
assert ok["allowed"]
no = execute_tool(tool_pass, "merge_pr", "mcp:github",
{"title": "nope", "body": "nope"}, create_issue)
assert no["reason_code"] == "CAPABILITY_NOT_GRANTED"
La identidad te dice quién es el agente. La autoridad te dice qué puede hacer.
pigeon keygen
pigeon inspect pass.json
Pigeon es un primitivo pequeño. No es una plataforma, un motor de políticas, un proveedor de identidad ni un custodio de claves. No detiene la inyección de prompts. Limita el radio de explosión a lo largo de las dimensiones que pongas en el Pass, y solo esas.
SPEC.mdSECURITY.mdexamples/ (infraestructura, datos, código y luego pagos)