A propos Compétences Expérience Services Blog Contact

Mon agent IA a voulu supprimer mes secrets. Il n'a pas pu. My AI agent tried to delete my secrets. It couldn't.

Un jour, mon agent IA a voulu supprimer les secrets de mon infra.

Il a écrit la commande Terraform. Il l'a lancée. Rien ne s'est passé.

Pas parce qu'il s'est ravisé. Parce que mon Terraform ne s'applique que par le pipeline, jamais en local. Et parce qu'il n'a pas les accès pour le faire.

C'est le meilleur moment de tout mon setup. Un agent a fait une bêtise dangereuse, et ça n'a rien coûté. Voilà toute l'idée. Ne parie pas sur un agent sage. Construis un agent qui ne peut pas casser.

TL;DR : un agent IA, c'est un nouveau dev. Rapide, sans fatigue, sans jugement. En local, je lui donne tout, pour aller vite. En prod, presque rien : il lit les logs, et il répare par pull request. Les garde-fous ne sont pas dans le modèle. Ils sont dans les hooks, les pre-commit, les règles GitHub et le RBAC. Tu ne fais pas confiance à l'agent. Tu rends la casse impossible.

Cet article est pour les devs et les DevSecOps qui laissent un agent de code agir dans leur repo et leur infra. Claude Code, Cursor, peu importe l'outil.

Le contexte

Un agent de code, c'est un LLM qui lit ton repo et lance des commandes à ta place.

Je fais tourner ces agents tous les jours, sur mon propre SaaS. La stack est classique. Go pour le backend. Un cluster k3s pour l'infra. Un k3s est un Kubernetes léger, qui orchestre des conteneurs. Terraform pour tout provisionner. Terraform, c'est de l'IaC : tu décris ton infra dans du code. Grafana et Jaeger pour voir ce qui se passe.

Je viens de la sécurité, et je suis un peu parano. Donner les clés de la prod à un agent, sans filet, c'était non.

Un agent IA, c'est un nouveau dev

Repense à l'arrivée d'un dev junior. Tu ne lui donnes pas les accès prod et les clés SSH le premier jour. Tu lui donnes un laptop, une base de dev jetable, et une règle : tout passe par une revue de code.

Un agent IA, c'est le même onboarding. Sauf qu'il tape cent commandes par minute, sans fatigue et sans peur. C'est le moindre privilège : donner juste les droits nécessaires, rien de plus. Un agent trop permissif, c'est le vrai risque.

Ce n'est pas théorique. En 2025, l'agent IA de Replit a supprimé une base de production entière, en plein gel de code. Il a ensuite inventé de faux enregistrements et menti sur ce qu'il avait fait. Le PDG a reconnu une erreur qui « ne devrait jamais être possible ». Sa correction ? Séparer dev et prod, et brider l'agent. Exactement le sujet de cet article.

Le sujet est si sérieux que les standards bougent. Le Zero Trust est une approche de sécurité : ne fais confiance à personne par défaut, vérifie à chaque accès. L'OWASP a publié un Top 10 des risques pour les applications à base d'agents. Le NIST, dans son texte sur le Zero Trust, parle déjà de « sujets », pas seulement d'utilisateurs. Un agent est un sujet comme un autre. Et la Cloud Security Alliance a sorti un cadre Zero Trust pour les agents. Il est co-écrit avec l'inventeur du Zero Trust.

La bonne question n'est pas « combien de droits ». C'est « combien d'autonomie ». Moins de droits, oui. Mais surtout moins de pouvoir d'agir sans contrôle.

En local, donne-lui les pleins pouvoirs

En local, mon agent a tout. Tous les secrets de dev, toutes les commandes, tout l'accès. Je veux qu'il aille vite.

Dans mon cluster, il navigue librement dans l'espace de dev. Un namespace, c'est un espace isolé dans le cluster. Il déploie, casse des pods, recommence.

Le niveau d'accès vient du contexte kubeconfig. Un kubeconfig, c'est le fichier qui dit à kubectl à quel cluster parler, et avec quels droits. J'ai un contexte par environnement.

# En local, l'agent travaille sur le contexte dev : lecture-écriture
kubectl config use-context dev

# Le contexte prod existe aussi, mais il est en lecture seule (voir plus bas)
kubectl config use-context prod

Pourquoi ces pleins pouvoirs en dev sont acceptables ? Parce que la zone de casse est petite. Le blast radius, c'est ce que ça peut casser si ça tourne mal. En dev, les données sont jetables. Une erreur coûte cinq minutes, pas un client.

Une matrice de permissions par environnement

Un dev n'a pas les mêmes droits en local et en prod. Ton agent non plus. Voici la carte que je suis, du plus permissif au plus verrouillé.

Capacitédevstagingprod
Commandes shelltoutallowlistaucune
Lire logs / tracesouiouioui (read-only)
Lister ressources k8souiouioui
Write / deleteouinonnon
Lire les secretsouinonnon
Appliquer Terraformpipelinepipelinepipeline
Merger sur maintoujours par PR + review + CI verte

Le reste de l'article, c'est comment je tiens chaque case de cette matrice. Sans jamais compter sur la sagesse de l'agent.

Mets les garde-fous hors du modèle

Voici l'idée qui tient tout l'article. Tu ne fais pas confiance au modèle pour bien se tenir. Tu rends la bêtise impossible, depuis l'extérieur. Quatre barrières : trois sur ma machine, une hors de portée de l'agent.

1. La liste des commandes autorisées

Mon fichier de config donne à l'agent une allowlist courte. Et il refuse l'irréversible.

{
  "permissions": {
    "allow": [
      "Bash(go build:*)", "Bash(go test:*)", "Bash(golangci-lint:*)",
      "Bash(git commit:*)", "Bash(git push:*)"
    ],
    "deny": [
      "Bash(git push:*--force*origin main*)",
      "Bash(gh pr merge:* main*)"
    ]
  }
}

L'agent peut compiler, tester, committer. Il ne peut pas forcer un push sur main. Il ne peut pas merger une PR sur main. Ces deux lignes de deny valent de l'or.

2. Le hook qui demande avant d'agir

Une allowlist ne prévoit pas tout. J'ajoute un hook lancé avant chaque commande. Un hook, c'est un script qui tourne avant une action. Le mien repère les commandes dangereuses.

// Hook PreToolUse : inspecte chaque commande Bash avant exécution.
const DANGEROUS = [
  /rm\s+.*-rf\s*\//,                     // rm -rf sur un chemin absolu
  /\bsudo\b/, /\bmkfs\./, /\bdd\s+.*of=\/dev\//,
  /\b(curl|wget)\s+.*\|\s*(sh|bash)\b/,  // curl | bash
  /\/etc\/(passwd|shadow)/,              // lecture de fichiers sensibles
];

// Pas de blocage aveugle. On demande à un humain, et on logge tout.
if (DANGEROUS.some((re) => re.test(cmd))) {
  console.log(JSON.stringify({ hookSpecificOutput: {
    hookEventName: "PreToolUse",
    permissionDecision: "ask",
    permissionDecisionReason: `Commande risquée : ${cmd}`,
  }}));
}

Il ne bloque pas bêtement. Il demande. Un humain tranche. Et chaque décision part dans un log.

3. Le pre-commit qui garde le repo

Un pre-commit, c'est un script qui tourne avant chaque commit. Le mien lance gitleaks, le lint et les tests, et refuse les fichiers sensibles.

# lefthook.yml : tourne avant chaque commit
pre-commit:
  commands:
    block-secrets:                 # refuse .env, .pem, les clés
      glob: ["*.env", "*.pem", "*.key", "id_rsa*"]
      run: 'echo "fichier sensible : {staged_files}"; exit 1'
    gitleaks:
      run: gitleaks protect --staged --redact
    lint: { glob: "*.go", run: golangci-lint run }
    test: { glob: "*.go", run: go test ./... }

Le point clé tient en une phrase. Ces barrières se fichent qu'un LLM ait écrit la commande. Elles vérifient le résultat, pas l'intention. C'est ça, la sécurité déterministe.

4. La règle GitHub, hors de portée de l'agent

Un hook local, on peut le sauter. La règle côté serveur, non. Sur GitHub, la branche main est protégée par un ruleset. Un ruleset, c'est un jeu de règles appliqué à la branche.

# Protection de la branche main (ruleset GitHub)
required_pull_request_reviews:
  required_approving_review_count: 1   # au moins une revue humaine
required_status_checks:
  strict: true                         # la CI doit être verte et à jour
  checks: ["lint", "test", "gitleaks"]
allow_force_pushes: false              # pas de réécriture d'historique
allow_deletions: false
# aucun bypass : la règle vaut aussi pour les admins

La nuance compte. Les trois premières barrières vivent sur ma machine. Celle-ci vit sur GitHub. L'agent ne peut pas y toucher. C'est la ligne que rien ne franchit.

Les secrets ne passent jamais par l'agent

Un agent qui voit tes secrets de prod, c'est une fuite qui attend son heure. Alors chez moi, il ne les voit pas. Personne ne les voit.

Retour au tout début de mon repo d'infra. Deuxième commit. Un secret part en clair, dans l'historique Git, à la vue de tous. Deux commits. Il a fallu deux commits pour qu'un secret fuite. C'est pour ça que gitleaks tourne à chaque commit aujourd'hui. gitleaks est un outil qui repère les secrets dans le code.

Mais gitleaks n'est que le filet. Le vrai fix, c'est que plus aucun secret ne se tape à la main. Terraform les génère, puis les pousse dans un gestionnaire de secrets. Un gestionnaire de secrets, c'est un coffre qui stocke et distribue les mots de passe et les clés. Chez moi, c'est Infisical.

De là, chaque secret est distribué là où il faut, selon l'environnement. La prod reçoit les secrets de prod, le staging les siens. L'agent, lui, ne reçoit rien.

Et le vrai point : personne ne lit ces secrets en clair. Pas l'agent. Pas moi non plus. Je ne connais aucun mot de passe de ma prod. On ne peut pas fuiter ce qu'on ne connaît pas.

En prod, l'agent hérite de ton contexte, et de rien d'autre

Une précision honnête. Le local tourne déjà. La prod, je la monte en ce moment. Le cluster se met en place, et je pose ces règles au fur et à mesure. Ce que je décris ici, c'est le modèle que je déploie.

En prod, l'agent bascule sur le contexte prod du kubeconfig. Ce contexte est lié à un rôle en lecture seule. Le RBAC, c'est le système Kubernetes qui dit qui a le droit de faire quoi.

# Le contexte prod est lié à ce rôle. Lecture seule, rien d'autre.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: prod
  name: agent-readonly
rules:
  - apiGroups: [""]
    resources: ["pods", "pods/log", "events", "services"]
    verbs: ["get", "list", "watch"]   # ni create, ni update, ni delete, ni exec

Pas d'écriture. Pas de suppression. Pas de shell dans un pod. L'agent peut lister et observer, c'est tout.

Pour comprendre un bug, il lit les logs et les traces. Mais il ne tape pas dans le cluster. Il interroge Grafana et Loki, en lecture seule. Loki, c'est le moteur de logs derrière Grafana. Il ne touche ni la base, ni les secrets.

Le principe est celui du Zero Trust. Tu ne fais confiance à personne par défaut. Tu vérifies à chaque accès. Un agent n'est pas une exception. C'est un sujet à privilèges, comme un compte de service.

La prod se répare en pull request, pas à la main

Reviens à l'agent qui a voulu supprimer mes secrets. Pourquoi ça n'a rien cassé ? Parce que rien ne s'applique en local. La prod ne change que par le pipeline, derrière une pull request.

C'est le principe du GitOps : tu changes ton infra en modifiant du code, pas à la main. L'agent lit les logs, comprend le problème, et propose un correctif en IaC. La review et la CI font barrage. Puis le pipeline applique. Jamais l'agent, jamais à la main.

Concrètement, la boucle est simple. Une trace Jaeger montre une requête lente. L'agent repère l'index qui manque. Il ouvre une PR avec la migration. Je relis, la CI passe, ça part. Il a trouvé le bug sans jamais toucher la base.

Parfois, il faut une lecture plus profonde en prod. Là, je donne un accès en lecture, temporaire et exceptionnel. On appelle ça le juste-à-temps : le droit arrive quand il faut, et repart après. On regarde, puis on retire.

Audite tout : un agent est un utilisateur à privilèges

Un dernier réflexe de sécurité : garde une trace de tout. Le Zero Trust a un troisième principe. Pars du principe qu'une brèche arrivera un jour.

Chaque commande de l'agent part dans un log structuré. Chaque décision de mes hooks aussi. Voici la ligne qu'a laissée l'agent qui a voulu supprimer mes secrets.

{"ts":"2026-07-23T14:02:11Z","tool":"Bash",
 "command":"terraform destroy -target=module.secrets",
 "decision":"ask","severity":"HIGH",
 "violations":["destructive terraform on secrets"],
 "session":"a3f9c1","source":"claude-code-hook"}

Un flux d'événements suit ce que font tous mes agents, en temps réel. Le jour où quelque chose tourne mal, je réponds à une seule question. Qu'a fait l'agent, exactement ? Sans ce log, tu ne peux pas.

La checklist par environnement

Avant de laisser un agent de code toucher ton infra, cale ses droits sur ces règles.

  • En local, accès large : l'agent va vite, la zone de casse est petite
  • En prod, contexte kubeconfig read-only : RBAC en get/list/watch, pas d'écriture, pas de suppression, pas d'exec
  • Une allowlist de commandes, et un deny sur l'irréversible (force push, merge sur main)
  • Un hook avant chaque commande, qui demande confirmation sur le dangereux
  • Des hooks de pre-commit : gitleaks, lint, tests
  • Sur GitHub, main protégée côté serveur : PR obligatoire, review requise, CI verte, force-push bloqué
  • Les logs et traces de prod lus en read-only (Grafana/Loki), jamais un shell dans le cluster
  • Les secrets générés par l'IaC, jamais lus par l'agent
  • La prod se change par pull request, appliquée par le pipeline, jamais à la main
  • Un accès en lecture prod juste-à-temps, temporaire, puis retiré
  • Chaque action de l'agent est loggée et auditable

Ce qu'il faut retenir

Un agent IA n'est pas un outil magique. C'est un collègue non-humain, très rapide, sans jugement. Traite-le comme un dev. Large en local, verrouillé en prod.

Et ne compte pas sur sa sagesse. Mets les garde-fous dehors : les hooks, les pre-commit, les règles GitHub, le RBAC.

Et il y a un effet de bord que je n'attendais pas. Ces barrières ne freinent pas l'agent, elles lui donnent de la marge. Comme il ne peut rien casser, je le laisse travailler seul : ouvrir des PR, enchaîner les tâches, bosser pendant que je fais autre chose. Sans elles, je relirais chaque commande. Bien encadré, un agent va plus loin.

Souviens-toi de l'agent qui a voulu supprimer mes secrets. Il n'a pas pu. Ne parie pas sur un agent sage. Construis un agent qui ne peut pas casser.

Tu déploies des agents de code sur une vraie infra ? Tu veux cadrer leurs droits sans les rendre inutiles ? C'est exactement ce que je fais. Écris-moi.

One day, my AI agent tried to delete the secrets of my infra.

It wrote the Terraform command. It ran it. Nothing happened.

Not because it changed its mind. Because my Terraform only applies through the pipeline, never locally. And because it has no access to do it.

That is the best moment in my whole setup. An agent did something dangerous, and it cost nothing. That is the whole idea. Don't bet on a well-behaved agent. Build an agent that can't break things.

TL;DR: an AI agent is a new dev. Fast, tireless, no judgment. Locally, I give it everything, so it moves fast. In prod, almost nothing: it reads the logs, and it fixes things by pull request. The guardrails do not live in the model. They live in hooks, pre-commit checks, GitHub rules and RBAC. You don't trust the agent. You make breaking things impossible.

This article is for developers and DevSecOps folks who let a coding agent act in their repo and their infra. Claude Code, Cursor, the tool does not matter.

The setup

A coding agent is an LLM that reads your repo and runs commands for you.

I run these agents every day, on my own SaaS. The stack is ordinary. Go for the backend. A k3s cluster for the infra. A k3s is a lightweight Kubernetes, which orchestrates containers. Terraform to provision everything. Terraform is IaC: you describe your infra in code. Grafana and Jaeger to see what happens.

I come from security, and I am a bit paranoid. Handing prod keys to an agent, with no safety net, was a no.

An AI agent is a new dev

Think back to onboarding a junior dev. You do not hand them prod access and the SSH keys on day one. You give them a laptop, a throwaway dev database, and one rule: everything goes through code review.

An AI agent is the same onboarding. Except it types a hundred commands a minute, with no fatigue and no fear. That is least privilege: grant only the rights that are needed, nothing more. An over-permissioned agent is the real risk.

This is not theoretical. In 2025, Replit's AI agent deleted an entire production database, in the middle of a code freeze. It then made up fake records and lied about what it had done. The CEO admitted an error that "should never be possible". His fix? Separate dev and prod, and rein the agent in. Exactly the topic of this article.

The topic is serious enough that standards are moving. Zero Trust is a security approach: trust no one by default, verify on every access. OWASP published a Top 10 of risks for agentic applications. NIST, in its Zero Trust text, already talks about "subjects", not just users. An agent is a subject like any other. And the Cloud Security Alliance shipped a Zero Trust framework for agents. It is co-written with the inventor of Zero Trust.

The right question is not "how many rights". It is "how much autonomy". Fewer rights, yes. But above all, less power to act unchecked.

Locally, give it full power

Locally, my agent has everything. All the dev secrets, all the commands, all the access. I want it to move fast.

In my cluster, it roams freely in the dev space. A namespace is an isolated space inside the cluster. It deploys, breaks pods, tries again.

The access level comes from the kubeconfig context. A kubeconfig is the file that tells kubectl which cluster to talk to, and with what rights. I keep one context per environment.

# Locally, the agent works on the dev context: read-write
kubectl config use-context dev

# The prod context exists too, but it is read-only (see below)
kubectl config use-context prod

Why is that full power fine in dev? Because the blast radius is small. The blast radius is what it can break if things go wrong. In dev, the data is throwaway. A mistake costs five minutes, not a customer.

A per-environment permission matrix

A dev does not have the same rights locally and in prod. Neither does your agent. Here is the map I follow, from most open to most locked down.

Capabilitydevstagingprod
Shell commandsallallowlistnone
Read logs / tracesyesyesyes (read-only)
List k8s resourcesyesyesyes
Write / deleteyesnono
Read secretsyesnono
Apply Terraformpipelinepipelinepipeline
Merge to mainalways via PR + review + green CI

The rest of this article is how I hold each cell of that matrix. Without ever counting on the agent's good sense.

Put the guardrails outside the model

Here is the idea that holds the whole article. You do not trust the model to behave. You make the mistake impossible, from the outside. Four barriers: three on my machine, one out of the agent's reach.

1. The list of allowed commands

My config file gives the agent a short allowlist. And it refuses the irreversible.

{
  "permissions": {
    "allow": [
      "Bash(go build:*)", "Bash(go test:*)", "Bash(golangci-lint:*)",
      "Bash(git commit:*)", "Bash(git push:*)"
    ],
    "deny": [
      "Bash(git push:*--force*origin main*)",
      "Bash(gh pr merge:* main*)"
    ]
  }
}

The agent can build, test, commit. It cannot force-push to main. It cannot merge a PR into main. Those two deny lines are worth gold.

2. The hook that asks before acting

An allowlist does not cover everything. I add a hook that runs before every command. A hook is a script that runs before an action. Mine spots dangerous commands.

// PreToolUse hook: inspect every Bash command before it runs.
const DANGEROUS = [
  /rm\s+.*-rf\s*\//,                     // rm -rf on an absolute path
  /\bsudo\b/, /\bmkfs\./, /\bdd\s+.*of=\/dev\//,
  /\b(curl|wget)\s+.*\|\s*(sh|bash)\b/,  // curl | bash
  /\/etc\/(passwd|shadow)/,              // reading sensitive files
];

// No blind block. Ask a human, and log everything.
if (DANGEROUS.some((re) => re.test(cmd))) {
  console.log(JSON.stringify({ hookSpecificOutput: {
    hookEventName: "PreToolUse",
    permissionDecision: "ask",
    permissionDecisionReason: `Risky command: ${cmd}`,
  }}));
}

It does not block blindly. It asks. A human decides. And every decision goes to a log.

3. The pre-commit that guards the repo

A pre-commit is a script that runs before each commit. Mine runs gitleaks, the linter and the tests, and rejects sensitive files.

# lefthook.yml: runs before every commit
pre-commit:
  commands:
    block-secrets:                 # reject .env, .pem, keys
      glob: ["*.env", "*.pem", "*.key", "id_rsa*"]
      run: 'echo "sensitive file: {staged_files}"; exit 1'
    gitleaks:
      run: gitleaks protect --staged --redact
    lint: { glob: "*.go", run: golangci-lint run }
    test: { glob: "*.go", run: go test ./... }

The key point fits in one sentence. These barriers do not care that an LLM wrote the command. They check the result, not the intent. That is deterministic security.

4. The GitHub rule, out of the agent's reach

A local hook can be skipped. A server-side rule cannot. On GitHub, the main branch is protected by a ruleset. A ruleset is a set of rules applied to the branch.

# Protection on the main branch (GitHub ruleset)
required_pull_request_reviews:
  required_approving_review_count: 1   # at least one human review
required_status_checks:
  strict: true                         # CI must be green and up to date
  checks: ["lint", "test", "gitleaks"]
allow_force_pushes: false              # no history rewrite
allow_deletions: false
# no bypass: the rule applies to admins too

The nuance matters. The first three barriers live on my machine. This one lives on GitHub. The agent cannot touch it. It is the line nothing crosses.

Secrets never pass through the agent

An agent that sees your prod secrets is a leak waiting to happen. So on my stack, it does not see them. Nobody sees them.

Back to the very start of my infra repo. Second commit. A secret lands in clear text, in the Git history, in plain sight. Two commits. It took two commits for a secret to leak. That is why gitleaks runs on every commit today. gitleaks is a tool that spots secrets in code.

But gitleaks is only the safety net. The real fix is that no secret is typed by hand anymore. Terraform generates them, then pushes them into a secret manager. A secret manager is a vault that stores and hands out passwords and keys. Mine is Infisical.

From there, each secret is delivered where it belongs, per environment. Prod gets the prod secrets, staging gets its own. The agent gets none.

And here is the real point: nobody reads these secrets in clear text. Not the agent. Not me either. I do not know a single production password. You cannot leak what you do not know.

In prod, the agent inherits your context, and nothing else

One honest caveat. The local side already runs. The prod side, I am building right now. The cluster is coming up, and I put these rules in place as it does. So what I describe here is the model I am deploying.

In prod, the agent switches to the prod kubeconfig context. That context is bound to a read-only role. RBAC is the Kubernetes system that says who is allowed to do what.

# The prod context is bound to this role. Read-only, nothing else.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: prod
  name: agent-readonly
rules:
  - apiGroups: [""]
    resources: ["pods", "pods/log", "events", "services"]
    verbs: ["get", "list", "watch"]   # no create, update, delete, or exec

No writes. No deletes. No shell inside a pod. The agent can list and observe, and that is all.

To understand a bug, it reads logs and traces. But it does not poke the cluster. It queries Grafana and Loki, read-only. Loki is the log engine behind Grafana. It touches neither the database nor the secrets.

This is the Zero Trust principle. You trust no one by default. You verify on every access. An agent is no exception. It is a privileged subject, like a service account.

Prod gets fixed by pull request, not by hand

Go back to the agent that tried to delete my secrets. Why did nothing break? Because nothing applies locally. Prod only changes through the pipeline, behind a pull request.

That is the GitOps idea: you change your infra by editing code, not by hand. The agent reads the logs, understands the problem, and proposes an IaC fix. Review and CI stand in the way. Then the pipeline applies it. Never the agent, never by hand.

Here is the loop, in practice. A Jaeger trace shows a slow query. The agent spots the missing index. It opens a PR with the migration. I review, CI passes, it ships. It found the bug without ever touching the database.

Sometimes you need a deeper read in prod. Then I grant read access, temporary and exceptional. This is just-in-time access: the right arrives when needed, and leaves after. You look, then you take it back.

Audit everything: an agent is a privileged user

One last security reflex: keep a trace of everything. Zero Trust has a third principle. Assume a breach will happen one day.

Every agent command goes to a structured log. Every decision from my hooks too. Here is the line left by the agent that tried to delete my secrets.

{"ts":"2026-07-23T14:02:11Z","tool":"Bash",
 "command":"terraform destroy -target=module.secrets",
 "decision":"ask","severity":"HIGH",
 "violations":["destructive terraform on secrets"],
 "session":"a3f9c1","source":"claude-code-hook"}

An event stream follows what all my agents do, in real time. The day something goes wrong, I answer one question. What did the agent do, exactly? Without that log, you cannot.

The per-environment checklist

Before you let a coding agent touch your infra, set its rights against these rules.

  • Locally, broad access: the agent moves fast, the blast radius is small
  • In prod, a read-only kubeconfig context: RBAC get/list/watch, no write, no delete, no exec
  • An allowlist of commands, and a deny on the irreversible (force push, merge to main)
  • A hook before every command, asking for confirmation on the dangerous ones
  • Pre-commit hooks: gitleaks, lint, tests
  • On GitHub, main protected server-side: PR required, review required, CI green, force-push blocked
  • Prod logs and traces read read-only (Grafana/Loki), never a shell in the cluster
  • Secrets generated by IaC, never read by the agent
  • Prod changes go through a pull request, applied by the pipeline, never by hand
  • Just-in-time prod read access, temporary, then revoked
  • Every agent action is logged and auditable

The takeaway

An AI agent is not a magic tool. It is a non-human colleague, very fast, with no judgment. Treat it like a dev. Broad locally, locked down in prod.

And do not count on its good sense. Put the guardrails outside: hooks, pre-commit checks, GitHub rules, RBAC.

And there is a side effect I did not expect. These barriers do not slow the agent down, they give it room. Since it cannot break anything, I let it work on its own: open PRs, chain tasks, keep going while I do something else. Without them, I would review every command. Boxed in, an agent goes further.

Remember the agent that tried to delete my secrets. It couldn't. Don't bet on a well-behaved agent. Build an agent that can't break things.

Are you deploying coding agents on real infra? Do you want to scope their rights without making them useless? That is exactly what I do. Get in touch.