flawopen.com/Référence/Sécurité des bots de PR
Bots de revue de PR : prévenir la SSRF par prompt injection
Comment le texte non fiable d’une pull request peut influencer les outils d’IA, et comment limiter les permissions du bot et l’accès réseau.
ELI5 — Un assistant de revue muni d’un accès réseau privé
Une pull request ressemble à un colis remis à l’équipe de revue : son contenu peut être utile, mais ne devient pas une politique de l’entreprise. Si l’assistant IA peut aussi récupérer des URL arbitraires depuis un réseau privé, une instruction trompeuse peut influencer la destination consultée. Le risque dépend des permissions de l’outil et du réseau ; lire un texte hostile ne donne pas à lui seul un accès réseau.
Concepts clés et terminologie
- Entrée non fiable :
- Titres, descriptions, commentaires, diffs et liens de pull requests envoyés par des contributeurs.
- Prompt injection indirecte :
- Un texte non fiable influence l’agent parce qu’il est inclus dans le contexte du modèle.
- Frontière SSRF :
- Un outil de récupération côté serveur peut atteindre des destinations inaccessibles directement au contributeur.
- Conditions d’impact :
- Il faut aussi un service interne accessible, des identifiants disponibles et des contrôles d’autorisation ou de sortie réseau insuffisants.
Mécanisme et cause racine
Un bot de revue peut combiner du texte contrôlé par le contributeur et des consignes de revue fiables. Le modèle peut proposer un appel d’outil sur la base de l’un ou l’autre. Les prompts et délimiteurs n’autorisent pas l’accès réseau. Une SSRF n’est possible que si le code accepte une destination choisie par le modèle et si l’outil réseau peut l’atteindre. L’accès aux métadonnées cloud ou aux identifiants est un impact conditionnel supplémentaire, pas un résultat automatique de la prompt injection.
Parcours d’attaque conditionnel
Le contributeur contrôle le contenu de la PR
Un contributeur externe peut influencer les titres, descriptions, commentaires, textes modifiés et liens.
L’agent lit ce contenu
Le service place certains champs non fiables dans le contexte du modèle ; ils peuvent influencer la réponse ou une proposition d’outil.
L’outil détermine la portée
Si l’agent peut récupérer des URL arbitraires, le runner peut contacter les destinations autorisées par le réseau. Sans cet outil, cette voie SSRF n’existe pas.
L’impact dépend du déploiement
Des données internes ou des identifiants ne sont exposés que si les services accessibles, les paramètres de métadonnées, les permissions d’identité et les contrôles de sortie le permettent.
Code : dispatch sans contrôle et lecture autorisée
# INERT DEMO: model-selected URL reaches a mock tool without policy checks
from dataclasses import dataclass
@dataclass
class ToolCall:
name: str
arguments: dict[str, str]
class MockFetch:
def __init__(self):
self.calls = []
def __call__(self, url: str) -> str:
self.calls.append(url)
return "fixture response"
def dispatch_unchecked(call: ToolCall, tools: dict):
# The mock records a string; it makes no network request.
return tools[call.name](**call.arguments)
mock_fetch = MockFetch()
call = ToolCall("fetch_url", {"url": "https://docs.example.invalid/"})
assert dispatch_unchecked(call, {"fetch_url": mock_fetch}) == "fixture response"
assert mock_fetch.calls == ["https://docs.example.invalid/"]
# SAFE DEMO: the agent receives only a read-only PR capability
from dataclasses import dataclass
@dataclass(frozen=True)
class ReviewContext:
repository: str
pull_request: int
@dataclass
class ToolCall:
name: str
arguments: dict[str, object]
READ_ONLY_TOOLS = {"read_pr_diff"}
def dispatch_review_call(call: ToolCall, context: ReviewContext, policy, tools):
if call.name not in READ_ONLY_TOOLS:
raise PermissionError("Tool is not available to this review")
expected = {"repository": context.repository,
"pull_request": context.pull_request}
if call.arguments != expected:
raise ValueError("Arguments must match the authorized review")
if not policy.can_read_pr(context.repository, context.pull_request):
raise PermissionError("Review principal cannot read this pull request")
# The injected read_pr_diff function is trusted app code, not model output.
return tools["read_pr_diff"](**expected)
Liste de contrôle de durcissement
- Ne présentez pas ce scénario comme un incident confirmé de bot de PR. La page décrit un risque de conception conditionnel.
- Ne reliez pas un outil de récupération d’URL arbitraire à un agent qui doit seulement lire un diff.
- Si des aperçus de liens externes sont nécessaires, utilisez un service de sortie dédié avec liste d’autorisation, validation DNS/IP à la connexion, revalidation des redirections, limites strictes de durée et de taille, sans identifiants ambiants.
- Bloquez l’accès du runner aux réseaux privés et aux métadonnées cloud. Désactivez les métadonnées si la charge n’en a pas besoin ; sinon, imposez IMDSv2 et vérifiez le hop limit requis en conteneur.
- Attribuez au bot une identité en lecture seule et limitée au dépôt. N’exposez pas de secrets dans les jobs exécutant des PR non fiables et séparez tout workflow privilégié de l’exécution de code externe.
- Dans GitHub Actions, préférez `pull_request` avec les permissions minimales pour du code non fiable. Considérez `pull_request_target` comme privilégié ; n’exécutez jamais le code d’un fork avec ses secrets ou son jeton en écriture.
- Journalisez les appels refusés et les décisions de destination sans conserver de jetons, de diffs privés complets ni de corps de réponse sensibles.