the short answer
Yes. On-call engineers hold elevated, long-lived permission sets, so an agent in their shell has the largest blast radius in the company. Let the agent diagnose freely with read-only calls, and deny every production change made with on-call or admin credentials, with a reason that tells the agent to post the command and its rollback for the engineer to run. Apply it by credential, so it follows whoever holds the pager.
- Why stricter
- Elevated permissions held for hours, during high-pressure work
- Agent may
- Read-only diagnosis: describe, get, list, logs
- Agent may not
- Any change made with an elevated profile or production context
- Keyed on
- The credentials named in the command, not the machine
- Audit
- Every elevated call recorded in the session, by machine
Why on-Call Is the Riskiest Place to Run an Agent
On-call permission sets tend to be broad because nobody wants an incident blocked on access. They are also long-lived: IAM Identity Center sessions can last up to 12 hours, and many teams set the maximum for on-call roles. Put that next to an agent asked to “fix the failing deploy” at 3 a.m., and you have the highest blast radius, the least review and the most ambiguous instructions in the same shell.
Agents are genuinely useful during incidents: reading logs, correlating metrics, drafting a rollback. The goal is not to keep them out of on-call work. It is to keep diagnosis fast and keep a human hand on every production change.
Two Ways to Define the on-Call Persona
Most teams use both: the standing admin group’s machines always get the strict set, and the credential rule catches anyone who assumes the on-call role.
| Approach | How | Strength | Weakness |
|---|---|---|---|
| By machine | Deploy a stricter policy set to the on-call and admin machines (per machine in Admin → enforcement; persona assignment in managed deployments) | Simple, visible in coverage | Rotations change weekly; machines do not |
| By credential | A policy that applies stricter rules whenever a command names an on-call or admin profile, or a production context | Follows the access, not the person | Depends on profiles being named (see AWS profile enforcement) |
Policy: Agents Diagnose, Humans Change Production
Every AWS, kubectl, Terraform or Helm segment in the command has to start with a read-only call for the command to pass, so aws ssm get-parameter … && aws ec2 run-instances … is denied. Command substitution, backticks, comments and nested shells are denied outright under elevated access, because they hide what actually runs. The deny reason is written as a runbook step: the agent posts the command and rollback, and the engineer runs it.
import { customPolicies, allow, deny } from "failproofai";
const ELEVATED = /(?:--profile[=\s]+|AWS_PROFILE=)(oncall|admin|breakglass)[\w.-]*|--(?:kube-)?context[=\s]+\S*prod/i;
// Substitutions, comments, newlines and nested shells hide what runs; with
// elevated access they are denied rather than parsed.
const OPAQUE = /\$\(|`|#|\n|\b(?:bash|sh|zsh)\s+-c\b|\beval\b/;
// Read-only shapes per tool, anchored to the start of the segment (after
// sudo/env/time and VAR=value). Flags may come before the subcommand.
const START = String.raw`^(?:(?:sudo|env|time)\s+)?(?:[A-Z_][A-Z0-9_]*=\S+\s+)*`;
const FLAGS = String.raw`(?:\s+--?[\w-]+(?:=\S+|\s+[^-\s]\S*)?)*`;
const READ_ONLY = [
new RegExp(START + "aws" + FLAGS + String.raw`\s+[\w-]+\s+(describe-|get-|list-|lookup-events|filter-log-events|tail\b)`),
new RegExp(START + "aws" + FLAGS + String.raw`\s+(sts\s+get-caller-identity|s3\s+ls)\b`),
new RegExp(START + "kubectl" + FLAGS + String.raw`\s+(get|describe|logs|top|events|explain|version|api-resources)\b`),
new RegExp(START + "helm" + FLAGS + String.raw`\s+(list|ls|status|get|history|show|template|version)\b`),
new RegExp(START + String.raw`terraform(?:\s+-[\w-]+(?:=\S+)?)*\s+(plan|show|output|validate)\b`),
];
customPolicies.add({
name: "oncall-production-guard",
description: "Elevated access: agents diagnose, humans change production",
match: { events: ["PreToolUse"] },
fn: async (ctx) => {
if (ctx.toolName !== "Bash") return allow();
const cmd = String(ctx.toolInput?.command ?? "");
if (!ELEVATED.test(cmd)) return allow();
if (OPAQUE.test(cmd)) {
return deny("With elevated access, run one plain command at a time: no $( ), backticks, comments or nested shells.");
}
const segments = cmd.split(/&&|\|\||;|\|/).map((s) => s.trim()).filter(Boolean);
const tools = segments.filter((s) => /\b(aws|kubectl|terraform|helm)\b/.test(s));
if (tools.length > 0 && tools.every((s) => READ_ONLY.some((re) => re.test(s)))) return allow();
return deny("This changes production with elevated access. Post the exact command and its rollback in the incident channel; the on-call engineer runs it.");
},
});Test Cases
| Command | Expected |
|---|---|
aws ec2 describe-instances --profile oncall-prod | allow |
kubectl --context prod-eu get pods | allow |
aws logs filter-log-events --log-group-name api --profile oncall-prod | allow |
aws s3 rm s3://prod-bucket --recursive --profile oncall-prod | deny |
kubectl --context prod-eu scale deploy api --replicas=0 | deny |
aws ssm get-parameter --name x --profile admin && aws ec2 run-instances --profile admin | deny |
aws ec2 describe-instances --profile agent-dev | allow |
aws ec2 terminate-instances --instance-ids $(aws ec2 describe-instances --query x --output text) --profile oncall-prod | deny |
aws s3 cp x s3://prod-bucket/x --profile oncall-prod # aws s3 ls | deny |
aws s3 rm s3://prod-bucket/x --profile oncall-prod && aws s3 ls --profile oncall-prod | deny |
helm uninstall api --kube-context prod-eu | deny |
helm status api --kube-context prod-eu | allow |
With a Decision Model
The coding agent pack’s opt-in block-kubectl denies the whole CLI and is reviewable by the production-infra-change check. With Jev on and the FailproofAI/jev-policies pack installed, read-only calls like kubectl get are cleared while production mutations stay denied. Each review is a model call, so expect a short pause on reviewed calls.
Make It Robust with a Decision-Model Check
A pattern only catches the spellings it lists, so every new tool, wrapper or script needs another pattern. A decision-model check asks what the call does and whether the person asked for it, so it holds up when the wording changes. Keep the regex policy as a hard floor and add the check on top.
Keep the regex policy hard, so its denies are final, and add this check beside it for what the pattern cannot see. It is not user-overridable, so a prompt asking the agent to “just restart prod” does not clear it.
We write custom checks like this one for your own rules: talk to us and tell us what should never happen on your machines. Jev comes with Failproof; turn it on whenever you choose, with failproofai jev setup or a Cloud machine key that carries jev:evaluate.
import { semanticPolicies } from "failproofai";
semanticPolicies.add({
name: "oncall-production-change",
title: "Changed production from an agent session",
appliesTo: ["shell", "network", "other"],
mode: "deny",
userCanOverride: false,
probes: [
{
id: "changes_production",
instructions: "Does this call change live production resources (deploy, scale, restart, fail over, delete, or modify data or configuration), rather than only reading them?",
criteria: { true: "It changes production.", false: "It only reads, or targets a non-production environment." },
},
],
guidance: "Agents diagnose; humans change production. Post the exact command and its rollback in the incident channel for the on-call engineer to run.",
});python scripts/failover.py --region eu-west-1ormake rollback ENV=prod: no AWS, kubectl, Terraform or Helm segment for the pattern to inspect.- MCP tool calls to a cloud or deployment service, which never pass through the shell.
- A production change written into a script the agent then runs.
Keeping Incidents Fast
- Write the deny reason as a runbook pointer. “Post the command and rollback; the engineer runs it” keeps the incident moving.
- Add narrow exceptions after observing. If agents routinely propose the same safe command, such as a rollout restart, allow that exact shape after a week in observe mode.
- Keep a break-glass path that is not an agent. The human can always run the command directly; the policy only gates the agent.
- Review after every incident. Open the session from Observe → Sessions and read which calls were denied. Adjust the policy, publish a new version, roll forward.
FAQ
Does this block the on-call engineer from running commands?
No. Policies gate the agent’s tool calls. Commands the engineer types in their own terminal are unaffected.
How do we handle rotation changes?
Use the credential-based rule so protection follows whoever assumes the on-call role.
Can we alert when an agent tries a production change?
Yes: a per_event or custom_sql alert over this policy’s decisions, routed to the incident channel. Check field names with fp query schema first.
Sources
Checked against the sources below on October 21, 2026. Harness settings, hook capabilities and cloud features change between versions.
- AWS docs: Set session duration for AWS accounts (IAM Identity Center)
- Failproof AI docs: Deploy a policy
- Failproof AI docs: Custom policies (policy SDK)
- Failproof AI docs: Policy authority
- Failproof AI docs: Sessions overview
- Failproof AI docs: Alerts
- Failproof AI docs: Jev policies