
Appliquez une délégation du moindre privilège pour les agents IA avec des identifiants signés et limités. Accordez aux sous-agents des capacités et ressources restreintes, vérifiez les actions avant leur exécution et empêchez l'élévation de privilèges.
Votre agent a engendré un sous-agent et lui a remis la même clé API. Ce sous-agent peut désormais déployer en production, lire la base de données des paiements et fusionner sur la branche principale.
Pigeon met un terme à cela. Vous remettez à l'enfant un Pigeon Pass : un identifiant restreint et signé pour ce qu'il est autorisé à faire, et non une copie de tout ce que vous pouvez faire.
Python 3.12 ou plus récent.
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 ne renvoie jamais un simple booléen. Un refus inclut un code de raison, un message et la comparaison qui a échoué (requested vs allowed).
Essayez sans écrire cela vous-même :
python examples/01_infrastructure.py
python demo/agent.py
Il n'y a pas de serveur Pigeon auquel se connecter. Vous modifiez deux endroits que vous possédez déjà :
delegate(...) et donnez un Pass à l'enfant.verify(...) et n'exécutez pas l'outil s'il est refusé.Conservez le véritable secret sur l'exécuteur. L'enfant porte le 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}, # ne peut pas supprimer une contrainte parente
)
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 enfant ne peut pas ajouter de capacités, élargir les ressources, augmenter une limite ou supprimer une contrainte parente. Si Pigeon ne peut pas prouver que l'enfant est plus restreint, il rejette.
Si l'exécuteur n'appelle jamais verify, le Pass n'est qu'une décoration.
C'est un point d'application, pas une partie du protocole. Le client génère un Pass plus restreint par appel d'outil. Le serveur le vérifie avant que l'outil ne s'exécute.
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"
L'identité vous dit qui est l'agent. L'autorité vous dit ce qu'il est autorisé à faire.
pigeon keygen
pigeon inspect pass.json
Pigeon est une petite primitive. Ce n'est pas une plateforme, un moteur de politiques, un fournisseur d'identité ou un gardien de clés. Il n'arrête pas l'injection de prompt. Il limite le rayon d'explosion selon les dimensions que vous placez sur le Pass, et uniquement celles-ci.
SPEC.mdSECURITY.mdexamples/ (infrastructure, données, code, puis paiements)