|8 min de lecture|Yvann Lièvre

Garder l'humain dans la boucle : gates d'approbation Rego avant la remédiation par agent IA

OPA Rego encadre la validation humaine avant la remédiation par agent IA : isolation automatique, approbation obligatoire pour les actions destructrices.

OPA RegoValidation humaineRemédiation
Garder l'humain dans la boucle : gates d'approbation Rego avant la remédiation par agent IA

Les éditeurs 2026 communiquent volontiers sur des temps de remédiation automatique inférieurs à dix secondes. Aqua Compass, Elastic Workflows et une poignée d'autres promettent qu'un agent détecte, décide et corrige avant que l'analyste n'ait fini de lire l'alerte. Sur le papier, c'est la promesse ultime de l'automatisation de la réponse à incident. Sur le terrain, c'est exactement ce qui inquiète un RSSI : un agent qui décide seul de désactiver un compte de service critique, de chiffrer un disque ou de couper un accès réseau ne fait pas gagner du temps, il déplace le risque ailleurs.

La question n'est donc pas « faut-il automatiser la remédiation ? » mais « comment poser un point de contrôle humain avant qu'un agent ne touche la production, sans pour autant renoncer à la vitesse sur ce qui ne le mérite pas ? ». La réponse tient dans un mécanisme précis : une policy as code qui classe chaque action par son rayon d'impact, et un point d'application qui interroge cette policy avant toute exécution.

Une distinction à faire : ce n'est pas du scoping d'outils

Ce sujet est souvent confondu avec le scoping statique des permissions d'outils (quels appels un agent a le droit de faire, définis une fois pour toutes dans sa configuration). Ici, il s'agit d'autre chose : une décision par action, au moment de l'exécution, qui tient compte de la cible réelle et du contexte. Un agent peut avoir techniquement le droit d'appeler l'API de désactivation de compte ; la question posée à chaque appel est de savoir si cette désactivation, sur ce compte, dans ce contexte, peut s'exécuter seule ou doit attendre un humain.

Classer les actions par rayon d'impact

Le point de départ n'est pas technique, il est doctrinal : toutes les actions de remédiation ne se valent pas. Deux axes suffisent à les classer.

Le premier axe est la réversibilité. Isoler un poste du réseau se défait en un clic. Chiffrer un disque, supprimer un compte ou révoquer des certificats ne se défait pas, ou seulement avec des heures de restauration. Le second axe est le périmètre : une action qui touche un seul actif isolé n'a pas le même poids qu'une action qui touche un compte de domaine, un groupe d'utilisateurs ou une plage réseau entière.

Ces deux axes ne restent pas dans la tête d'un architecte, ils sont encodés en données de policy, versionnées comme du code :

# policy/data/actions.yaml
low_risk:
  - action: isolate_host
    reversible: true
    scope: single_asset
  - action: block_ip
    reversible: true
    scope: single_asset
  - action: quarantine_file
    reversible: true
    scope: single_asset
 
requires_approval:
  - action: disable_account
    reversible: false
    scope: identity
  - action: delete_account
    reversible: false
    scope: identity
  - action: encrypt_disk
    reversible: false
    scope: single_asset
  - action: revoke_all_sessions
    reversible: false
    scope: domain

Cette classification est la vraie décision métier. Tout ce qui suit n'est que de la mécanique pour la faire respecter.

OPA comme moteur de décision (PDP)

Open Policy Agent joue ici le rôle de PDP (Policy Decision Point) : l'agent lui soumet une requête structurée (l'action, sa cible, le contexte) et reçoit en retour une décision parmi trois : allow, require_approval, ou deny. Le moteur ne connaît rien de la logique métier de l'agent, il évalue une policy Rego indépendante :

package threatclaw.remediation
 
default decision := {"result": "deny", "reason": "action non classifiée"}
 
low_risk_actions := {"isolate_host", "block_ip", "quarantine_file"}
approval_required_actions := {"disable_account", "delete_account", "encrypt_disk", "revoke_all_sessions"}
 
decision := {"result": "allow", "reason": "action réversible, périmètre unitaire"} if {
    input.action in low_risk_actions
    input.target.scope == "single_asset"
}
 
decision := {"result": "require_approval", "reason": "action irréversible ou à impact étendu"} if {
    input.action in approval_required_actions
}

Le point le plus important tient dans le default de la première ligne : si l'action n'apparaît dans aucune des deux listes, la décision par défaut est deny. Une action non classifiée n'est jamais assimilée à une action à faible risque par omission. C'est un principe de sécurité positive classique (deny by default) appliqué à l'autonomie d'un agent.

Le point d'application : l'agent interroge, il ne décide plus seul

Concrètement, avant chaque appel de remédiation, l'agent interroge le PDP via une requête HTTP au serveur OPA :

curl -s -X POST http://opa.internal:8181/v1/data/threatclaw/remediation/decision \
  -H "Content-Type: application/json" \
  -d '{
        "input": {
          "action": "disable_account",
          "target": {"type": "user", "id": "j.dupont", "scope": "identity"},
          "context": {"initiator": "agent", "case_id": "INC-4821"}
        }
      }'

La réponse est déterministe et lisible par l'orchestrateur de l'agent :

{"result": {"result": "require_approval", "reason": "action irréversible ou à impact étendu"}}

Avant ce garde-fou, la logique était codée en dur dans l'agent, un simple aiguillage sans nuance :

# avant : décision codée en dur, aucun point de contrôle externe
if action == "isolate_host":
    execute(action)
elif action == "disable_account":
    execute(action)   # aucune distinction de risque

Après intégration du PDP, l'agent ne décide plus rien seul : il exécute, met en file d'attente pour approbation, ou rejette, en fonction d'une réponse externe et auditable :

decision=$(curl -s -X POST http://opa.internal:8181/v1/data/threatclaw/remediation/decision \
  -d @input.json | jq -r '.result.result')
 
case "$decision" in
  allow)            execute_action ;;
  require_approval) queue_for_human_review ;;
  *)                reject_action ;;
esac

Ce changement de quelques lignes déplace la responsabilité : la logique de risque quitte le code de l'agent pour vivre dans une policy versionnée, relue en revue de code, et testable indépendamment.

L'anti-piège : une policy non testée est une régression silencieuse

Une policy Rego est du code. Non testée, elle casse en silence : un renommage d'action, une correction de typo dans une liste, un refactor de la structure input, et une action qui devait exiger une approbation se retrouve auto-approuvée sans que personne ne s'en aperçoive avant l'incident. C'est le piège le plus coûteux de cette approche, et le plus facile à éviter.

La parade est un jeu de tests unitaires sur la policy elle-même, exécutés par conftest, avec un fichier policy_test.rego dédié :

package threatclaw.remediation
 
test_isolate_host_est_autonome if {
    decision.result == "allow" with input as {
        "action": "isolate_host",
        "target": {"type": "host", "id": "WKS-042", "scope": "single_asset"}
    }
}
 
test_disable_account_exige_approbation if {
    decision.result == "require_approval" with input as {
        "action": "disable_account",
        "target": {"type": "user", "id": "test", "scope": "identity"}
    }
}
 
test_action_non_classifiee_est_refusee if {
    decision.result == "deny" with input as {
        "action": "wipe_disk",
        "target": {"type": "host", "id": "WKS-042", "scope": "single_asset"}
    }
}

Ces tests s'intègrent directement à la CI, à côté des tests applicatifs classiques :

policy-tests:
  stage: test
  script:
    - conftest verify --policy policy/

Toute modification de policy qui casserait la classification (par exemple faire passer disable_account du côté des actions autonomes par erreur) fait échouer le pipeline avant merge, exactement comme un test unitaire cassé sur du code applicatif. La policy cesse d'être un fichier de configuration qu'on modifie à la main en production ; elle devient un artefact versionné, relu, testé.

Aligné sur la doctrine du contrôle humain

Cette architecture n'est pas un compromis technique isolé, elle traduit un principe simple : l'humain valide toute action d'écriture qui modifie un état de production, l'automatisation ne couvre que ce qui est à la fois réversible et de faible portée. Isoler un poste compromis peut se défaire en un geste ; désactiver un compte, chiffrer un volume ou révoquer des accès à l'échelle d'un domaine ne se rattrape pas de la même façon. Tant qu'une action n'a pas été explicitement classée comme réversible et unitaire par la policy, elle reste bloquée en attente d'un humain, jamais exécutée par défaut.

C'est cette classification par rayon d'impact, testée et appliquée à chaque décision de remédiation, que nous outillons dans le pack de policies ThreatClaw : des gates d'approbation prêts à l'emploi, couvrant les actions de remédiation les plus courantes, testés en CI avant chaque publication.

Articles liés