SLOPSHOPPER

live-guard-gate

Asks before live cloud mutations and names the guard agent that should review them.

guard
A shopper browsing a rack in a slop shop
README

Live Guard Gate

Asks before a tool call that would mutate live cloud infrastructure, and names the guard agents that should review it first. It covers Terraform and Pulumi, the AWS, Azure, Google Cloud and OCI CLIs, kubectl, Helm, Argo CD, Velero and Snowflake role changes.

Reach and declared footprint

L0 (no declared file writes, subprocess spawns or network egress). All four manifest footprint arrays (env_reads, file_writes, spawns and egress_hosts) are empty. This is capability disclosure, not enforced isolation.

How agent names are chosen

Each pattern in patterns.json carries an agents list. When several patterns match, the reason lists the union of their agents in first-seen order. lib/live-agents.mjs lists the live guard agents the patterns cover (covered) and those with no pattern yet (uncovered). It is regenerated by npm run mods:write.

Per-harness behavior

HarnessSurfaceBehavior
Claude Codenative moduletool.check asks; the engine's own deny is never loosened
Claude Codeclassic hookPreToolUse asks
KiroNode hookthe whole payload is scanned; a hit is denied (no confirmation protocol)
CodexNode hooka hit is denied with a reason (Codex rejects ask)

The mod reads no environment variables, writes no files and makes no network calls.

Limitations

  • It matches shell text in every string of the tool input, so a file whose content mentions such a command also asks.
  • It does not know which cloud account or context is live; a command against a sandbox asks too.
  • An ask may be auto-approved under a bypass permission mode.
  • Input over the size limit is not evaluated and asks.
  • The native API was verified against Claude Code 2.1.296 (early access). Other harnesses need Node 22+.

Install

See mods/README.md.

Source 4 files
hooks/register.ts 12 lines
1import type { Register } from 'claude-code'
2import { checkToolUse } from '../lib/core.mjs'
3
4export const register: Register = (on) => {
5  // Never loosens the engine: its deny stays deny; a live mutation raises allow to ask.
6  on('tool.check', async ($, e, next) => {
7    const v = await next(e)
8    if (v.decision === 'deny') return v
9    return checkToolUse({ input: e.input }) ?? v
10  }).catch(() => ({ decision: 'deny', reason: 'live-guard-gate: check failed; denied' }))
11}
12
lib/core.mjs 25 lines
1// Pure decision logic for live-guard-gate. The native module and the Node hook both import
2// it, so every harness reaches the same verdict on the same input.
3import { compilePatterns, candidates, matchPatterns, TOO_LARGE_HIT } from './common.mjs'
4import raw from './patterns.mjs'
5
6const PATTERNS = compilePatterns(raw, 'patterns.json')
7
8// Asks when the tool input holds a live cloud mutation, naming the guard agents to consult.
9// The runtime renders ask as deny where a harness has no confirmation protocol.
10export function checkToolUse({ input }) {
11  const hits = matchPatterns(PATTERNS, candidates(input), 'bash')
12  if (hits.length === 0) return undefined
13  if (hits.includes(TOO_LARGE_HIT)) return { decision: 'ask', reason: 'live-guard-gate: input too large to evaluate.' }
14  const agents = [...new Set(hits.flatMap((p) => p.agents ?? []))]
15  const names = hits.map((p) => p.name).join('; ')
16  return { decision: 'ask', reason: `live-guard-gate: live mutation (${names}). Review with ${agents.join(', ')} before running.` }
17}
18
19// Kiro's stdin field names are undocumented: without a tool_input object, scan the whole
20// payload so the guard fails closed.
21export function toolUseFromPayload(payload) {
22  const ti = payload?.tool_input
23  return { input: ti !== null && typeof ti === 'object' && !Array.isArray(ti) ? ti : payload }
24}
25
lib/common.mjs 214 lines
1// Shared helpers for the mods under mods/. scripts/generate-mods.mjs copies this file
2// byte-for-byte to mods/<id>/lib/common.mjs, so edit it here and run `npm run mods:write`.
3//
4// Pure on purpose: no imports, no environment, no I/O. Claude Code's native mod runtime
5// imports it and has no Node, and every harness must reach the same verdict on the same
6// input. Regexes compile without the `u` flag so Node and Python `re.ASCII`
7// (tests/validate-mods.py) read them the same way.
8
9export const LIMITS = Object.freeze({
10  // A redaction that still changes after this many passes is withheld as a whole.
11  maxPasses: 8,
12  // A guard candidate longer than this is not evaluated and counts as a hit.
13  maxCandidateChars: 65536,
14  // Guard candidates are not evaluated past this combined length; the call counts as a hit.
15  maxTotalChars: 262144,
16  // Only the tail of a leaf's context (key or previous sibling) is prefixed to it.
17  maxContextChars: 256,
18})
19
20const MARKER_PREFIX = '[REDACTED:'
21export const OVERFLOW_MARKER = '[REDACTED:overflow]'
22const OVERFLOW_HIT = Object.freeze({ id: 'overflow', name: 'redaction did not converge' })
23export const TOO_LARGE_HIT = Object.freeze({ id: 'too-large', name: 'input too large to evaluate' })
24
25const ID_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/
26const PATTERN_KEYS = new Set(['id', 'name', 'description', 'regex', 'flags', 'context', 'severity', 'agents'])
27const isStringArray = (v) => Array.isArray(v) && v.every((s) => typeof s === 'string' && s !== '')
28
29// Parser diagnostics can quote credential-bearing data; never expose it in hook logs.
30export function parseJson(text, source) {
31  try {
32    return JSON.parse(text)
33  } catch {
34    throw new Error(`${source}: invalid JSON`)
35  }
36}
37
38// Validates a patterns.json-shaped list and compiles it. Throws on anything malformed so a
39// bad extension file fails closed in the caller instead of silently matching nothing.
40export function compilePatterns(list, source = 'patterns') {
41  if (!Array.isArray(list)) throw new Error(`${source}: expected an array of patterns`)
42  const seen = new Set()
43  return list.map((p, i) => {
44    const where = `${source}[${i}]`
45    if (p === null || typeof p !== 'object' || Array.isArray(p)) throw new Error(`${where}: expected an object`)
46    for (const k of Object.keys(p)) if (!PATTERN_KEYS.has(k)) throw new Error(`${where}: unknown key ${k}`)
47    if (typeof p.id !== 'string' || !ID_RE.test(p.id)) throw new Error(`${where}: id must be kebab-case`)
48    if (seen.has(p.id)) throw new Error(`${where}: duplicate id ${p.id}`)
49    seen.add(p.id)
50    if (typeof p.name !== 'string' || p.name === '') throw new Error(`${where}: name must be a non-empty string`)
51    if (typeof p.regex !== 'string' || p.regex === '') throw new Error(`${where}: regex must be a non-empty string`)
52    if (p.flags !== '' && p.flags !== 'i') throw new Error(`${where}: flags must be "" or "i"`)
53    if (!isStringArray(p.context) || p.context.length === 0) throw new Error(`${where}: context must be a non-empty string array`)
54    for (const k of ['description', 'severity']) {
55      if (p[k] !== undefined && typeof p[k] !== 'string') throw new Error(`${where}: ${k} must be a string`)
56    }
57    if (p.agents !== undefined && !isStringArray(p.agents)) throw new Error(`${where}: agents must be a string array`)
58    let rx, rxg
59    try {
60      rx = new RegExp(p.regex, p.flags)
61      rxg = new RegExp(p.regex, 'g' + p.flags)
62    } catch {
63      throw new Error(`${where}: regex does not compile`)
64    }
65    return Object.freeze({
66      id: p.id,
67      name: p.name,
68      severity: p.severity,
69      context: Object.freeze([...p.context]),
70      agents: p.agents ? Object.freeze([...p.agents]) : undefined,
71      rx,
72      rxg,
73      marker: `${MARKER_PREFIX}${p.id}]`,
74    })
75  })
76}
77
78// Every string value (not key) in depth-first order. The order follows the input, so the
79// same payload always yields the same list.
80export function stringLeaves(value, out = []) {
81  if (typeof value === 'string') out.push(value)
82  else if (Array.isArray(value)) for (const v of value) stringLeaves(v, out)
83  else if (value !== null && typeof value === 'object') for (const v of Object.values(value)) stringLeaves(v, out)
84  return out
85}
86
87// Guard candidates: every string leaf, plus every all-string array joined with spaces so
88// an argv-style command (["rm", "-rf", "/"]) reads like the shell line it runs.
89export function candidates(value) {
90  const out = stringLeaves(value)
91  const joinArrays = (v) => {
92    if (Array.isArray(v)) {
93      if (v.length > 1 && v.every((s) => typeof s === 'string')) out.push(v.join(' '))
94      v.forEach(joinArrays)
95    } else if (v !== null && typeof v === 'object') {
96      Object.values(v).forEach(joinArrays)
97    }
98  }
99  joinArrays(value)
100  return out
101}
102
103// The patterns tagged with `context` that match any text, in catalog order. Oversized input
104// is not evaluated (regex cost is unbounded there) and reports TOO_LARGE_HIT instead.
105export function matchPatterns(patterns, texts, context) {
106  let total = 0
107  for (const t of texts) {
108    total += t.length
109    if (t.length > LIMITS.maxCandidateChars || total > LIMITS.maxTotalChars) return [TOO_LARGE_HIT]
110  }
111  return patterns.filter((p) => p.context.includes(context) && texts.some((t) => p.rx.test(t)))
112}
113
114// One pattern pass after another until the text stops changing. Each pass can expose a new
115// match (password-in-url can swallow an earlier marker), so a single pass is not enough.
116function fixedPoint(text, patterns, hits) {
117  let x = text
118  for (let pass = 0; pass <= LIMITS.maxPasses; pass++) {
119    let y = x
120    for (const p of patterns) {
121      y = y.replace(p.rxg, (m) => {
122        if (m === '') return m
123        hits.add(p)
124        return p.marker
125      })
126    }
127    if (y === x) return x
128    x = y
129  }
130  hits.add(OVERFLOW_HIT)
131  return OVERFLOW_MARKER
132}
133
134// Redacts `s` with `ctx` in front so a secret split across a key and its value, or across
135// argv elements ("Bearer", "<token>"), still matches; then strips the context back off.
136function redactLeaf(s, ctx, patterns, hits) {
137  if (ctx === '') return fixedPoint(s, patterns, hits)
138  const pre = ctx.slice(-LIMITS.maxContextChars) + ' '
139  const y = fixedPoint(pre + s, patterns, hits)
140  if (y === OVERFLOW_MARKER) return y
141  const r = fixedPoint(pre, patterns, new Set())
142  if (y.startsWith(r)) return fixedPoint(y.slice(r.length), patterns, hits)
143  // A match spans the boundary: keep from the marker that covers it. Slicing changes
144  // anchors and boundaries, so also check the standalone leaf after contextual matching.
145  let d = 0
146  while (d < r.length && y[d] === r[d]) d++
147  const i = y.lastIndexOf(MARKER_PREFIX, d)
148  return fixedPoint(i >= 0 ? y.slice(i) : y, patterns, hits)
149}
150
151function walk(v, ctx, patterns, hits) {
152  if (typeof v === 'string') {
153    const local = new Set()
154    const out = redactLeaf(v, ctx, patterns, local)
155    if (out !== v) for (const h of local) hits.add(h)
156    return out
157  }
158  if (Array.isArray(v)) {
159    let prev = null
160    return v.map((el) => {
161      const out = walk(el, typeof prev === 'string' ? prev : ctx, patterns, hits)
162      prev = el
163      return out
164    })
165  }
166  if (v !== null && typeof v === 'object') {
167    const named = typeof v.name === 'string' ? v.name : typeof v.key === 'string' ? v.key : null
168    // fromEntries, not assignment: `out[k] = v` would drop an own "__proto__" key.
169    return Object.fromEntries(
170      Object.entries(v).map(([k, x]) => [
171        walk(k, '', patterns, hits),
172        walk(x, k === 'value' && named !== null ? named : k, patterns, hits),
173      ]),
174    )
175  }
176  return v
177}
178
179// Returns a copy of `value` with every match replaced by `[REDACTED:<id>]`, plus the
180// patterns that fired (catalog order, unique). Keys are redacted too; two keys that redact
181// to the same marker collapse into one, which loses data but never leaks it.
182export function redactValue(value, patterns) {
183  const hits = new Set()
184  const out = walk(value, '', patterns, hits)
185  const order = [...patterns, OVERFLOW_HIT]
186  return { value: out, hits: order.filter((p) => hits.has(p)) }
187}
188
189// Same shape as `value`, every string replaced by `text`. The fallback when redaction
190// itself failed: withhold everything rather than pass anything through.
191export function maskValue(value, text) {
192  if (typeof value === 'string') return text
193  if (Array.isArray(value)) return value.map((v) => maskValue(v, text))
194  if (value !== null && typeof value === 'object') {
195    return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, maskValue(v, text)]))
196  }
197  return value
198}
199
200// JSON with object keys sorted at every level, so equal values hash equally.
201export function canonicalJson(value) {
202  if (Array.isArray(value)) return '[' + value.map((v) => (v === undefined ? 'null' : canonicalJson(v))).join(',') + ']'
203  if (value !== null && typeof value === 'object') {
204    const keys = Object.keys(value).filter((k) => value[k] !== undefined).sort()
205    return '{' + keys.map((k) => JSON.stringify(k) + ':' + canonicalJson(value[k])).join(',') + '}'
206  }
207  return JSON.stringify(value) ?? 'null'
208}
209
210export async function sha256Hex(text) {
211  const digest = await globalThis.crypto.subtle.digest('SHA-256', new TextEncoder().encode(text))
212  return Array.from(new Uint8Array(digest), (b) => b.toString(16).padStart(2, '0')).join('')
213}
214
lib/patterns.mjs 524 lines
1// Generated by scripts/generate-mods.mjs from patterns.json. Do not edit.
2export default [
3  {
4    "id": "live-iac-terraform-pulumi",
5    "name": "Terraform/OpenTofu/Terragrunt/Pulumi state-changing command",
6    "description": "terraform|tofu|terragrunt apply|destroy|import|taint|untaint|force-unlock|state rm|mv|push; pulumi up|destroy|refresh|import|cancel. Cloud is not knowable from the command, so every IaC guard that explicitly covers Terraform/stacks is listed; consult the one for the target cloud.",
7    "regex": "(?:^|[^A-Za-z0-9_.-])(?:(?:terraform|tofu|terragrunt)(?![A-Za-z0-9_-])(?:[ \\t]+-[^ \\t\\r\\n;&|]+(?:[ \\t]+[^ \\t\\r\\n;&|-][^ \\t\\r\\n;&|]*)?){0,4}[ \\t]+(?:run-all[ \\t]+)?(?:apply|destroy|import|taint|untaint|force-unlock|state[ \\t]+(?:rm|mv|push))(?![^ \\t\\r\\n;&|)])|pulumi(?![A-Za-z0-9_-])(?:[ \\t]+-[^ \\t\\r\\n;&|]+(?:[ \\t]+[^ \\t\\r\\n;&|-][^ \\t\\r\\n;&|]*)?){0,4}[ \\t]+(?:up|destroy|refresh|import|cancel)(?![^ \\t\\r\\n;&|)]))",
8    "agents": [
9      "aws-live-iac-change-guard-agent",
10      "oci-live-resource-manager-stack-guard-agent",
11      "azure-live-arm-deployment-stack-guard-agent"
12    ],
13    "flags": "",
14    "context": [
15      "bash"
16    ]
17  },
18  {
19    "id": "live-aws-cloudformation-cdk",
20    "name": "CloudFormation / CDK mutation",
21    "description": "aws cloudformation create/update/delete-stack[-set], deploy, execute-change-set, update-termination-protection; cdk deploy|destroy|bootstrap.",
22    "regex": "(?:^|[^A-Za-z0-9_.-])(?:aws(?![A-Za-z0-9_-])(?=[^;&|\\n]*[ \\t]cloudformation[ \\t]+(?:create-stack|update-stack|delete-stack|create-stack-set|update-stack-set|delete-stack-set|deploy|execute-change-set|update-termination-protection)(?![^ \\t\\r\\n;&|)]))|cdk(?![A-Za-z0-9_-])(?=[^;&|\\n]*[ \\t](?:deploy|destroy|bootstrap)(?![^ \\t\\r\\n;&|)])))",
23    "agents": [
24      "aws-live-iac-change-guard-agent"
25    ],
26    "flags": "",
27    "context": [
28      "bash"
29    ]
30  },
31  {
32    "id": "live-aws-ecs",
33    "name": "aws ecs mutation",
34    "description": "aws ... ecs (create|delete|update|put|run|start|stop|register|deregister)-*.",
35    "regex": "(?:^|[^A-Za-z0-9_.-])aws(?![A-Za-z0-9_-])(?=[^;&|\\n]*[ \\t]ecs[ \\t]+(?:create|delete|update|put|run|start|stop|register|deregister)-[a-z0-9-]+(?![^ \\t\\r\\n;&|)]))",
36    "agents": [
37      "aws-live-ecs-rollout-guard-agent"
38    ],
39    "flags": "",
40    "context": [
41      "bash"
42    ]
43  },
44  {
45    "id": "live-aws-lambda-apigw-serverless",
46    "name": "Lambda / API Gateway / Serverless Framework release",
47    "description": "aws lambda (create|delete|update|publish|put|add|remove)-*; aws apigateway|apigatewayv2 (create|delete|update|put|import)-*; serverless|sls deploy|remove|rollback.",
48    "regex": "(?:^|[^A-Za-z0-9_.-])(?:aws(?![A-Za-z0-9_-])(?=[^;&|\\n]*[ \\t](?:lambda[ \\t]+(?:create|delete|update|publish|put|add|remove)|apigateway(?:v2)?[ \\t]+(?:create|delete|update|put|import))-[a-z0-9-]+(?![^ \\t\\r\\n;&|)]))|(?:serverless|sls)(?![A-Za-z0-9_-])(?=[^;&|\\n]*[ \\t](?:deploy|remove|rollback)(?![^ \\t\\r\\n;&|)])))",
49    "agents": [
50      "aws-live-serverless-release-guard-agent"
51    ],
52    "flags": "",
53    "context": [
54      "bash"
55    ]
56  },
57  {
58    "id": "live-aws-sam",
59    "name": "SAM deploy/delete",
60    "description": "sam deploy|delete. Both the serverless-release and IaC guards apply (SAM is CloudFormation-backed Lambda).",
61    "regex": "(?:^|[^A-Za-z0-9_.-])sam(?![A-Za-z0-9_-])(?=[^;&|\\n]*[ \\t](?:deploy|delete)(?![^ \\t\\r\\n;&|)]))",
62    "agents": [
63      "aws-live-serverless-release-guard-agent",
64      "aws-live-iac-change-guard-agent"
65    ],
66    "flags": "",
67    "context": [
68      "bash"
69    ]
70  },
71  {
72    "id": "live-azure-arm-stack",
73    "name": "az deployment / stack create|delete",
74    "description": "az deployment [sub|group|mg|tenant] create|delete and az stack [sub|group|mg] create|delete. what-if / validate / show / list are excluded.",
75    "regex": "(?:^|[^A-Za-z0-9_.-])az(?![A-Za-z0-9_-])(?=[^;&|\\n]*[ \\t](?:deployment|stack)[ \\t]+(?:(?:sub|group|mg|tenant)[ \\t]+)?(?:create|delete)(?![^ \\t\\r\\n;&|)]))",
76    "agents": [
77      "azure-live-arm-deployment-stack-guard-agent"
78    ],
79    "flags": "",
80    "context": [
81      "bash"
82    ]
83  },
84  {
85    "id": "live-azure-aks",
86    "name": "az aks mutation",
87    "description": "az aks create|delete|update|upgrade|scale|stop|start|rotate-certs|operation-abort and az aks nodepool add|delete|scale|update|upgrade|start|stop.",
88    "regex": "(?:^|[^A-Za-z0-9_.-])az(?![A-Za-z0-9_-])(?=[^;&|\\n]*[ \\t]aks[ \\t]+(?:(?:create|delete|update|upgrade|scale|stop|start|rotate-certs|operation-abort)|nodepool[ \\t]+(?:add|delete|scale|update|upgrade|start|stop))(?![^ \\t\\r\\n;&|)]))",
89    "agents": [
90      "azure-live-aks-rollout-guard-agent"
91    ],
92    "flags": "",
93    "context": [
94      "bash"
95    ]
96  },
97  {
98    "id": "live-azure-slot-swap",
99    "name": "az webapp/functionapp deployment slot swap",
100    "description": "az webapp|functionapp deployment slot swap.",
101    "regex": "(?:^|[^A-Za-z0-9_.-])az(?![A-Za-z0-9_-])(?=[^;&|\\n]*[ \\t](?:webapp|functionapp)[ \\t]+deployment[ \\t]+slot[ \\t]+swap(?![^ \\t\\r\\n;&|)]))",
102    "agents": [
103      "azure-live-app-service-slot-swap-guard-agent"
104    ],
105    "flags": "",
106    "context": [
107      "bash"
108    ]
109  },
110  {
111    "id": "live-azure-keyvault",
112    "name": "az keyvault delete/purge/rotate",
113    "description": "az keyvault delete|purge and az keyvault key|secret|certificate delete|purge|rotate.",
114    "regex": "(?:^|[^A-Za-z0-9_.-])az(?![A-Za-z0-9_-])(?=[^;&|\\n]*[ \\t]keyvault[ \\t]+(?:(?:delete|purge)|(?:key|secret|certificate)[ \\t]+(?:delete|purge|rotate))(?![^ \\t\\r\\n;&|)]))",
115    "agents": [
116      "azure-live-keyvault-rotation-purge-guard-agent"
117    ],
118    "flags": "",
119    "context": [
120      "bash"
121    ]
122  },
123  {
124    "id": "live-azure-role-assignment",
125    "name": "az role assignment/definition write",
126    "description": "az role assignment|definition create|update|delete.",
127    "regex": "(?:^|[^A-Za-z0-9_.-])az(?![A-Za-z0-9_-])(?=[^;&|\\n]*[ \\t]role[ \\t]+(?:assignment|definition)[ \\t]+(?:create|update|delete)(?![^ \\t\\r\\n;&|)]))",
128    "agents": [
129      "azure-live-entra-role-assignment-guard-agent"
130    ],
131    "flags": "",
132    "context": [
133      "bash"
134    ]
135  },
136  {
137    "id": "live-azure-budget",
138    "name": "az consumption budget write",
139    "description": "az consumption budget create|update|delete.",
140    "regex": "(?:^|[^A-Za-z0-9_.-])az(?![A-Za-z0-9_-])(?=[^;&|\\n]*[ \\t]consumption[ \\t]+budget[ \\t]+(?:create|update|delete)(?![^ \\t\\r\\n;&|)]))",
141    "agents": [
142      "azure-live-cost-budget-action-guard-agent"
143    ],
144    "flags": "",
145    "context": [
146      "bash"
147    ]
148  },
149  {
150    "id": "live-gcloud-gke",
151    "name": "gcloud container clusters/node-pools mutation",
152    "description": "gcloud container clusters|node-pools create|delete|update|upgrade|resize|rollback.",
153    "regex": "(?:^|[^A-Za-z0-9_.-])gcloud(?![A-Za-z0-9_-])(?=[^;&|\\n]*[ \\t]container[ \\t]+(?:clusters|node-pools)[ \\t]+(?:create|delete|update|upgrade|resize|rollback)(?![^ \\t\\r\\n;&|)]))",
154    "agents": [
155      "gcp-live-gke-rollout-guard-agent"
156    ],
157    "flags": "",
158    "context": [
159      "bash"
160    ]
161  },
162  {
163    "id": "live-gcloud-run",
164    "name": "gcloud run deploy / traffic / delete",
165    "description": "gcloud run deploy; gcloud run services update|update-traffic|delete|replace; gcloud run revisions delete.",
166    "regex": "(?:^|[^A-Za-z0-9_.-])gcloud(?![A-Za-z0-9_-])(?=[^;&|\\n]*[ \\t]run[ \\t]+(?:deploy|(?:services|revisions)[ \\t]+(?:update|update-traffic|delete|replace))(?![^ \\t\\r\\n;&|)]))",
167    "agents": [
168      "gcp-live-cloud-run-traffic-migration-guard-agent"
169    ],
170    "flags": "",
171    "context": [
172      "bash"
173    ]
174  },
175  {
176    "id": "live-gcloud-iam",
177    "name": "gcloud IAM binding / policy / SA key / org-policy mutation",
178    "description": "*-iam-policy-binding (add|remove|set), set-iam-policy, iam service-accounts keys create, iam roles create|update|delete, org-policies set-policy|delete.",
179    "regex": "(?:^|[^A-Za-z0-9_.-])gcloud(?![A-Za-z0-9_-])(?=[^;&|\\n]*[ \\t](?:(?:add|remove)-iam-policy-binding|set-iam-policy|iam[ \\t]+service-accounts[ \\t]+keys[ \\t]+create|iam[ \\t]+roles[ \\t]+(?:create|update|delete)|(?:resource-manager[ \\t]+)?org-policies[ \\t]+(?:set-policy|delete))(?![^ \\t\\r\\n;&|)]))",
180    "agents": [
181      "gcp-live-iam-policy-change-guard-agent"
182    ],
183    "flags": "",
184    "context": [
185      "bash"
186    ]
187  },
188  {
189    "id": "live-gcloud-kms",
190    "name": "gcloud kms key version destroy/disable",
191    "description": "gcloud kms keys versions destroy|disable.",
192    "regex": "(?:^|[^A-Za-z0-9_.-])gcloud(?![A-Za-z0-9_-])(?=[^;&|\\n]*[ \\t]kms[ \\t]+keys[ \\t]+versions[ \\t]+(?:destroy|disable)(?![^ \\t\\r\\n;&|)]))",
193    "agents": [
194      "gcp-live-kms-key-destruction-guard-agent"
195    ],
196    "flags": "",
197    "context": [
198      "bash"
199    ]
200  },
201  {
202    "id": "live-gcloud-billing-budget",
203    "name": "gcloud billing budgets write",
204    "description": "gcloud billing budgets create|update|delete.",
205    "regex": "(?:^|[^A-Za-z0-9_.-])gcloud(?![A-Za-z0-9_-])(?=[^;&|\\n]*[ \\t]billing[ \\t]+budgets[ \\t]+(?:create|update|delete)(?![^ \\t\\r\\n;&|)]))",
206    "agents": [
207      "gcp-live-cost-budget-action-guard-agent"
208    ],
209    "flags": "",
210    "context": [
211      "bash"
212    ]
213  },
214  {
215    "id": "live-bq-destructive",
216    "name": "bq rm / truncate",
217    "description": "bq rm|truncate (dataset/table deletion).",
218    "regex": "(?:^|[^A-Za-z0-9_.-])bq(?![A-Za-z0-9_-])(?=[^;&|\\n]*[ \\t](?:rm|truncate)(?![^ \\t\\r\\n;&|)]))",
219    "agents": [
220      "gcp-live-bigquery-dataset-deletion-guard-agent"
221    ],
222    "flags": "",
223    "context": [
224      "bash"
225    ]
226  },
227  {
228    "id": "live-k8s-rbac",
229    "name": "kubectl RBAC mutation",
230    "description": "kubectl|oc apply|create|delete|patch|replace|edit on role(s)/rolebinding(s)/clusterrole(s)/clusterrolebinding(s) given inline, or kubectl auth reconcile. Resources hidden inside -f manifests are not visible to a regex.",
231    "regex": "(?:^|[^A-Za-z0-9_.-])(?:kubectl|oc)(?![A-Za-z0-9_-])(?:[ \\t]+-[^ \\t\\r\\n;&|]+(?:[ \\t]+[^ \\t\\r\\n;&|-][^ \\t\\r\\n;&|]*)?){0,8}[ \\t]+(?:(?:apply|create|delete|patch|replace|edit))(?:[ \\t]+-[^ \\t\\r\\n;&|]+(?:[ \\t]+[^ \\t\\r\\n;&|-][^ \\t\\r\\n;&|]*)?){0,6}[ \\t]+(?:roles?|rolebindings?|clusterroles?|clusterrolebindings?)(?:\\.[a-z0-9.-]+)?(?:/|,|(?![^ \\t\\r\\n;&|)]))|(?:^|[^A-Za-z0-9_.-])(?:kubectl|oc)(?![A-Za-z0-9_-])(?:[ \\t]+-[^ \\t\\r\\n;&|]+(?:[ \\t]+[^ \\t\\r\\n;&|-][^ \\t\\r\\n;&|]*)?){0,8}[ \\t]+(?:auth[ \\t]+reconcile)(?![^ \\t\\r\\n;&|)])",
232    "agents": [
233      "kubernetes-live-rbac-mutation-guard-agent"
234    ],
235    "flags": "",
236    "context": [
237      "bash"
238    ]
239  },
240  {
241    "id": "live-k8s-network-policy",
242    "name": "kubectl NetworkPolicy / Cilium policy mutation",
243    "description": "kubectl|oc apply|create|delete|patch|replace|edit on networkpolicy/netpol/ciliumnetworkpolicy/cnp/ciliumclusterwidenetworkpolicy/ccnp/ciliumegressgatewaypolicy/cegp.",
244    "regex": "(?:^|[^A-Za-z0-9_.-])(?:kubectl|oc)(?![A-Za-z0-9_-])(?:[ \\t]+-[^ \\t\\r\\n;&|]+(?:[ \\t]+[^ \\t\\r\\n;&|-][^ \\t\\r\\n;&|]*)?){0,8}[ \\t]+(?:(?:apply|create|delete|patch|replace|edit))(?:[ \\t]+-[^ \\t\\r\\n;&|]+(?:[ \\t]+[^ \\t\\r\\n;&|-][^ \\t\\r\\n;&|]*)?){0,6}[ \\t]+(?:networkpolic(?:y|ies)|netpol|ciliumnetworkpolic(?:y|ies)|cnp|ciliumclusterwidenetworkpolic(?:y|ies)|ccnp|ciliumegressgatewaypolic(?:y|ies)|cegp)(?:\\.[a-z0-9.-]+)?(?:/|,|(?![^ \\t\\r\\n;&|)]))",
245    "agents": [
246      "kubernetes-live-network-policy-guard-agent"
247    ],
248    "flags": "",
249    "context": [
250      "bash"
251    ]
252  },
253  {
254    "id": "live-k8s-admission-policy",
255    "name": "kubectl admission policy / webhook / Kyverno mutation",
256    "description": "kubectl|oc apply|create|delete|patch|replace|edit on validating/mutatingwebhookconfiguration, validating/mutatingadmissionpolicy[binding], clusterpolicy/cpol, policyexception/polex.",
257    "regex": "(?:^|[^A-Za-z0-9_.-])(?:kubectl|oc)(?![A-Za-z0-9_-])(?:[ \\t]+-[^ \\t\\r\\n;&|]+(?:[ \\t]+[^ \\t\\r\\n;&|-][^ \\t\\r\\n;&|]*)?){0,8}[ \\t]+(?:(?:apply|create|delete|patch|replace|edit))(?:[ \\t]+-[^ \\t\\r\\n;&|]+(?:[ \\t]+[^ \\t\\r\\n;&|-][^ \\t\\r\\n;&|]*)?){0,6}[ \\t]+(?:validatingwebhookconfigurations?|mutatingwebhookconfigurations?|validatingadmissionpolic(?:y|ies)|validatingadmissionpolicybindings?|mutatingadmissionpolic(?:y|ies)|mutatingadmissionpolicybindings?|clusterpolic(?:y|ies)|cpol|policyexceptions?|polex)(?:\\.[a-z0-9.-]+)?(?:/|,|(?![^ \\t\\r\\n;&|)]))",
258    "agents": [
259      "kubernetes-live-admission-policy-guard-agent"
260    ],
261    "flags": "",
262    "context": [
263      "bash"
264    ]
265  },
266  {
267    "id": "live-k8s-mesh-policy",
268    "name": "kubectl Istio policy mutation",
269    "description": "kubectl|oc apply|create|delete|patch|replace|edit on virtualservice, destinationrule, peerauthentication, requestauthentication, authorizationpolicy, serviceentry, envoyfilter.",
270    "regex": "(?:^|[^A-Za-z0-9_.-])(?:kubectl|oc)(?![A-Za-z0-9_-])(?:[ \\t]+-[^ \\t\\r\\n;&|]+(?:[ \\t]+[^ \\t\\r\\n;&|-][^ \\t\\r\\n;&|]*)?){0,8}[ \\t]+(?:(?:apply|create|delete|patch|replace|edit))(?:[ \\t]+-[^ \\t\\r\\n;&|]+(?:[ \\t]+[^ \\t\\r\\n;&|-][^ \\t\\r\\n;&|]*)?){0,6}[ \\t]+(?:virtualservices?|destinationrules?|peerauthentications?|requestauthentications?|authorizationpolic(?:y|ies)|serviceentr(?:y|ies)|envoyfilters?)(?:\\.[a-z0-9.-]+)?(?:/|,|(?![^ \\t\\r\\n;&|)]))",
271    "agents": [
272      "kubernetes-live-mesh-policy-guard-agent"
273    ],
274    "flags": "",
275    "context": [
276      "bash"
277    ]
278  },
279  {
280    "id": "live-argocd-sync",
281    "name": "argocd app/proj/appset mutation",
282    "description": "argocd app|proj|appset sync|rollback|delete|create|set|patch|edit|terminate-op, or kubectl on application(s).argoproj.io/appproject(s)/applicationset(s).",
283    "regex": "(?:^|[^A-Za-z0-9_.-])argocd(?![A-Za-z0-9_-])[ \\t]+(?:app|proj|appset)[ \\t]+(?:sync|rollback|delete|create|set|patch|edit|terminate-op)(?![^ \\t\\r\\n;&|)])|(?:^|[^A-Za-z0-9_.-])(?:kubectl|oc)(?![A-Za-z0-9_-])(?:[ \\t]+-[^ \\t\\r\\n;&|]+(?:[ \\t]+[^ \\t\\r\\n;&|-][^ \\t\\r\\n;&|]*)?){0,8}[ \\t]+(?:(?:apply|create|delete|patch|replace|edit))(?:[ \\t]+-[^ \\t\\r\\n;&|]+(?:[ \\t]+[^ \\t\\r\\n;&|-][^ \\t\\r\\n;&|]*)?){0,6}[ \\t]+(?:applications?|appprojects?|applicationsets?|[a-z]*applications?[.]argoproj[.]io)(?:\\.[a-z0-9.-]+)?(?:/|,|(?![^ \\t\\r\\n;&|)]))",
284    "agents": [
285      "kubernetes-live-argocd-sync-guard-agent"
286    ],
287    "flags": "",
288    "context": [
289      "bash"
290    ]
291  },
292  {
293    "id": "live-velero-restore",
294    "name": "velero restore / backup delete",
295    "description": "velero restore create|delete, velero create restore, velero backup|schedule delete, velero delete backup|restore|schedule. Backup create/describe/get excluded.",
296    "regex": "(?:^|[^A-Za-z0-9_.-])velero(?![A-Za-z0-9_-])(?=[^;&|\\n]*[ \\t](?:restore[ \\t]+(?:create|delete)|create[ \\t]+restore|(?:backup|schedule)[ \\t]+delete|delete[ \\t]+(?:backup|restore|schedule))(?![^ \\t\\r\\n;&|)]))",
297    "agents": [
298      "kubernetes-live-velero-restore-guard-agent"
299    ],
300    "flags": "",
301    "context": [
302      "bash"
303    ]
304  },
305  {
306    "id": "live-kubectl-mutate",
307    "name": "kubectl/oc workload-mutating verb",
308    "description": "kubectl|oc apply|create|replace|patch|delete|scale|autoscale|edit|drain|cordon|uncordon|taint|annotate|label|set|expose|rollout restart|undo|pause|resume, unless --dry-run=client|server. Cluster provider unknown, so the managed-Kubernetes rollout guards are listed; consult the one for the cluster. Specific kubernetes-live-* patterns above win (first match).",
309    "regex": "(?:^|[^A-Za-z0-9_.-])(?:kubectl|oc)(?![A-Za-z0-9_-])(?![^;&|\\n]*[ \\t]--dry-run(?:=(?:client|server|true))?(?![^ \\t\\r\\n;&|)]))(?:[ \\t]+-[^ \\t\\r\\n;&|]+(?:[ \\t]+[^ \\t\\r\\n;&|-][^ \\t\\r\\n;&|]*)?){0,8}[ \\t]+(?:apply|create|replace|patch|delete|scale|autoscale|edit|drain|cordon|uncordon|taint|annotate|label|set|expose|rollout[ \\t]+(?:restart|undo|pause|resume))(?![^ \\t\\r\\n;&|)])",
310    "agents": [
311      "azure-live-aks-rollout-guard-agent",
312      "gcp-live-gke-rollout-guard-agent",
313      "oci-live-oke-rollout-guard-agent",
314      "huawei-live-cce-rollout-guard-agent",
315      "alibaba-live-ack-rollout-guard-agent",
316      "scaleway-live-kapsule-rollout-guard-agent"
317    ],
318    "flags": "",
319    "context": [
320      "bash"
321    ]
322  },
323  {
324    "id": "live-helm-mutate",
325    "name": "helm install/upgrade/uninstall/rollback",
326    "description": "helm install|upgrade|uninstall|delete|rollback unless --dry-run. Same managed-Kubernetes rollout guards as live-kubectl-mutate.",
327    "regex": "(?:^|[^A-Za-z0-9_.-])helm(?![A-Za-z0-9_-])(?![^;&|\\n]*[ \\t]--dry-run(?:=(?:client|server|true))?(?![^ \\t\\r\\n;&|)]))(?:[ \\t]+-[^ \\t\\r\\n;&|]+(?:[ \\t]+[^ \\t\\r\\n;&|-][^ \\t\\r\\n;&|]*)?){0,8}[ \\t]+(?:install|upgrade|uninstall|delete|rollback)(?![^ \\t\\r\\n;&|)])",
328    "agents": [
329      "azure-live-aks-rollout-guard-agent",
330      "gcp-live-gke-rollout-guard-agent",
331      "oci-live-oke-rollout-guard-agent",
332      "huawei-live-cce-rollout-guard-agent",
333      "alibaba-live-ack-rollout-guard-agent",
334      "scaleway-live-kapsule-rollout-guard-agent"
335    ],
336    "flags": "",
337    "context": [
338      "bash"
339    ]
340  },
341  {
342    "id": "live-oci-network-security-rule",
343    "name": "oci network security-list / NSG rule mutation",
344    "description": "oci network security-list create|update|delete; oci network nsg rules add|update|remove; oci network nsg create|update|delete. add/update and security-list update are named in the oci-live-network-security-rule-guard-agent docs; the remaining verbs are from OCI CLI knowledge.",
345    "regex": "(?:^|[^A-Za-z0-9_.-])oci(?![A-Za-z0-9_-])(?=[^;&|\\n]*[ \\t]network[ \\t]+(?:security-list[ \\t]+(?:create|update|delete)|nsg[ \\t]+rules[ \\t]+(?:add|update|remove)|nsg[ \\t]+(?:create|update|delete))(?![^ \\t\\r\\n;&|)]))",
346    "agents": [
347      "oci-live-network-security-rule-guard-agent"
348    ],
349    "flags": "",
350    "context": [
351      "bash"
352    ]
353  },
354  {
355    "id": "live-oci-iam-policy-compartment",
356    "name": "oci iam policy / dynamic-group / compartment mutation",
357    "description": "oci iam policy create|update|delete; oci iam dynamic-group create|update|delete; oci iam compartment delete|move. policy update|delete and dynamic-group update are named in the oci-live-iam-policy-compartment-guard-agent docs; the other verbs are from OCI CLI knowledge. Global flags before the service are tolerated by scanning the simple command.",
358    "regex": "(?:^|[^A-Za-z0-9_.-])oci(?![A-Za-z0-9_-])(?=[^;&|\\n]*[ \\t]iam[ \\t]+(?:policy[ \\t]+(?:create|update|delete)|dynamic-group[ \\t]+(?:create|update|delete)|compartment[ \\t]+(?:delete|move))(?![^ \\t\\r\\n;&|)]))",
359    "agents": [
360      "oci-live-iam-policy-compartment-guard-agent"
361    ],
362    "flags": "",
363    "context": [
364      "bash"
365    ]
366  },
367  {
368    "id": "live-oci-autonomous-db",
369    "name": "oci db autonomous-database lifecycle change",
370    "description": "oci db autonomous-database update|restore|start|stop|restart|delete|create-from-clone. update, restore, start and create-from-clone are named in the oci-live-autonomous-db-lifecycle-guard-agent docs; stop, restart and delete are from OCI CLI knowledge.",
371    "regex": "(?:^|[^A-Za-z0-9_.-])oci(?![A-Za-z0-9_-])(?=[^;&|\\n]*[ \\t]db[ \\t]+autonomous-database[ \\t]+(?:update|restore|start|stop|restart|delete|create-from-clone)(?![^ \\t\\r\\n;&|)]))",
372    "agents": [
373      "oci-live-autonomous-db-lifecycle-guard-agent"
374    ],
375    "flags": "",
376    "context": [
377      "bash"
378    ]
379  },
380  {
381    "id": "live-oci-budget-quota",
382    "name": "oci budgets / limits quota mutation",
383    "description": "oci budgets budget|alert-rule|alert create|update|delete; oci limits quota create|update|delete. budgets budget update, budgets alert update and limits quota create are named in the oci-live-cost-budget-runaway-guard-agent docs; the other verbs are from OCI CLI knowledge.",
384    "regex": "(?:^|[^A-Za-z0-9_.-])oci(?![A-Za-z0-9_-])(?=[^;&|\\n]*[ \\t](?:budgets[ \\t]+(?:budget|alert-rule|alert)|limits[ \\t]+quota)[ \\t]+(?:create|update|delete)(?![^ \\t\\r\\n;&|)]))",
385    "agents": [
386      "oci-live-cost-budget-runaway-guard-agent"
387    ],
388    "flags": "",
389    "context": [
390      "bash"
391    ]
392  },
393  {
394    "id": "live-snowflake-rbac",
395    "name": "snowsql / snow sql GRANT, REVOKE, ALTER ROLE",
396    "description": "snowsql or 'snow sql' on the same line as a GRANT|REVOKE statement or ALTER ROLE (flags i). The SQL must be on the command line (-q); statements piped in or read from -f files are invisible. Statements inside string literals match (known false positive).",
397    "regex": "(?:^|[^A-Za-z0-9_.-])(?:snowsql|snow[ \\t]+sql)(?![A-Za-z0-9_-])(?=[^\\n]*[\"' \\t;](?:(?:grant|revoke)[ \\t]+[a-z]|alter[ \\t]+role[ \\t]))",
398    "agents": [
399      "snowflake-live-rbac-grant-guard-agent"
400    ],
401    "flags": "i",
402    "context": [
403      "bash"
404    ]
405  },
406  {
407    "id": "live-snowflake-warehouse",
408    "name": "snowsql / snow sql warehouse change",
409    "description": "snowsql or 'snow sql' with ALTER|DROP|CREATE [OR REPLACE|OR ALTER] WAREHOUSE (flags i). ALTER WAREHOUSE is named in the snowflake-live-warehouse-cost-change-guard-agent docs; CREATE and DROP are from Snowflake SQL knowledge.",
410    "regex": "(?:^|[^A-Za-z0-9_.-])(?:snowsql|snow[ \\t]+sql)(?![A-Za-z0-9_-])(?=[^\\n]*[\"' \\t;](?:alter|drop|create(?:[ \\t]+or[ \\t]+(?:replace|alter))?)[ \\t]+warehouse[ \\t])",
411    "agents": [
412      "snowflake-live-warehouse-cost-change-guard-agent"
413    ],
414    "flags": "i",
415    "context": [
416      "bash"
417    ]
418  },
419  {
420    "id": "live-snowflake-failover",
421    "name": "snowsql / snow sql failover group change",
422    "description": "snowsql or 'snow sql' with ALTER FAILOVER GROUP (flags i), named in the snowflake-live-failover-promotion-guard-agent docs.",
423    "regex": "(?:^|[^A-Za-z0-9_.-])(?:snowsql|snow[ \\t]+sql)(?![A-Za-z0-9_-])(?=[^\\n]*[\"' \\t;]alter[ \\t]+failover[ \\t]+group[ \\t])",
424    "agents": [
425      "snowflake-live-failover-promotion-guard-agent"
426    ],
427    "flags": "i",
428    "context": [
429      "bash"
430    ]
431  },
432  {
433    "id": "live-snowflake-auth-network-policy",
434    "name": "snowsql / snow sql network or authentication policy change",
435    "description": "snowsql or 'snow sql' with ALTER|DROP|CREATE [OR REPLACE] NETWORK|AUTHENTICATION POLICY (flags i). ALTER forms are named in the snowflake-live-auth-network-policy-guard-agent docs; CREATE and DROP are from Snowflake SQL knowledge.",
436    "regex": "(?:^|[^A-Za-z0-9_.-])(?:snowsql|snow[ \\t]+sql)(?![A-Za-z0-9_-])(?=[^\\n]*[\"' \\t;](?:alter|drop|create(?:[ \\t]+or[ \\t]+replace)?)[ \\t]+(?:network|authentication)[ \\t]+policy[ \\t])",
437    "agents": [
438      "snowflake-live-auth-network-policy-guard-agent"
439    ],
440    "flags": "i",
441    "context": [
442      "bash"
443    ]
444  },
445  {
446    "id": "live-snowflake-pipeline",
447    "name": "snowsql / snow sql pipe, task or dynamic table change",
448    "description": "snowsql or 'snow sql' with ALTER PIPE|TASK|DYNAMIC TABLE (flags i), named in the snowflake-live-pipeline-streaming-change-guard-agent docs.",
449    "regex": "(?:^|[^A-Za-z0-9_.-])(?:snowsql|snow[ \\t]+sql)(?![A-Za-z0-9_-])(?=[^\\n]*[\"' \\t;]alter[ \\t]+(?:pipe|task|dynamic[ \\t]+table)[ \\t])",
450    "agents": [
451      "snowflake-live-pipeline-streaming-change-guard-agent"
452    ],
453    "flags": "i",
454    "context": [
455      "bash"
456    ]
457  },
458  {
459    "id": "live-snowflake-data-protection",
460    "name": "snowsql / snow sql masking or row access policy application",
461    "description": "snowsql or 'snow sql' with SET|UNSET|ADD|DROP MASKING POLICY or ROW ACCESS POLICY (flags i), the ALTER TABLE column / row-access forms named in the snowflake-live-data-protection-policy-guard-agent docs. CREATE MASKING POLICY (definition only) is not matched.",
462    "regex": "(?:^|[^A-Za-z0-9_.-])(?:snowsql|snow[ \\t]+sql)(?![A-Za-z0-9_-])(?=[^\\n]*[ \\t](?:set|unset|add|drop)[ \\t]+(?:masking|row[ \\t]+access)[ \\t]+policy(?![A-Za-z0-9_]))",
463    "agents": [
464      "snowflake-live-data-protection-policy-guard-agent"
465    ],
466    "flags": "i",
467    "context": [
468      "bash"
469    ]
470  },
471  {
472    "id": "live-aws-cli-mutation",
473    "name": "aws CLI mutating operation (fallback)",
474    "description": "aws [global flags] <service> <operation> where operation is (create|delete|update|terminate|modify|remove|attach|detach|disable|schedule|revoke|authorize|deregister|associate|disassociate|reboot|stop)-* or put-* (data-plane put-object/put-item/put-metric-data/put-log-events/put-record(s) excluded), plus aws s3 rb|rm|mb. No dedicated guard exists for most services (ec2, rds, iam, s3, ...): all AWS live guards are listed; aws-live-iac-change-guard-agent is the nearest change-control fit. The near-miss aws-live-deployment-guarded-operator-agent is outside the *-live-*-guard-agent glob and therefore not referenced.",
475    "regex": "(?:^|[^A-Za-z0-9_.-])aws(?![A-Za-z0-9_-])(?:[ \\t]+-[^ \\t\\r\\n;&|]+(?:[ \\t]+[^ \\t\\r\\n;&|-][^ \\t\\r\\n;&|]*)?){0,8}[ \\t]+(?:s3[ \\t]+(?:rb|rm|mb)|[a-z0-9][a-z0-9-]*[ \\t]+(?:(?:create|delete|update|terminate|modify|remove|attach|detach|disable|schedule|revoke|authorize|deregister|associate|disassociate|reboot|stop)-[a-z0-9-]+|put-(?!(?:object|item|metric-data|log-events|records?)(?![^ \\t\\r\\n;&|)]))[a-z0-9-]+))(?![^ \\t\\r\\n;&|)])",
476    "agents": [
477      "aws-live-ecs-rollout-guard-agent",
478      "aws-live-iac-change-guard-agent",
479      "aws-live-serverless-release-guard-agent"
480    ],
481    "flags": "",
482    "context": [
483      "bash"
484    ]
485  },
486  {
487    "id": "live-az-cli-mutation",
488    "name": "az CLI mutating verb (fallback)",
489    "description": "az <command path of up to 5 words> create|delete|update|purge|deallocate|scale|upgrade|start|stop|restart; the verb must sit in the command path before the first flag, so values of --query/--name are not mistaken for verbs. All Azure live guards listed (no service-specific guard matched).",
490    "regex": "(?:^|[^A-Za-z0-9_.-])az(?![A-Za-z0-9_-])(?:[ \\t]+[a-z][a-z0-9-]*){0,5}[ \\t]+(?:create|delete|update|purge|deallocate|scale|upgrade|start|stop|restart)(?![^ \\t\\r\\n;&|)])",
491    "agents": [
492      "azure-live-aks-rollout-guard-agent",
493      "azure-live-app-service-slot-swap-guard-agent",
494      "azure-live-arm-deployment-stack-guard-agent",
495      "azure-live-cost-budget-action-guard-agent",
496      "azure-live-entra-role-assignment-guard-agent",
497      "azure-live-keyvault-rotation-purge-guard-agent",
498      "azure-live-pim-jit-activation-guard-agent"
499    ],
500    "flags": "",
501    "context": [
502      "bash"
503    ]
504  },
505  {
506    "id": "live-gcloud-cli-mutation",
507    "name": "gcloud mutating verb (fallback)",
508    "description": "gcloud <command path of up to 5 words> create|delete|update|deploy|patch|resize|restore|import|rm, verb in the path before the first flag. All GCP live guards listed (no service-specific guard matched).",
509    "regex": "(?:^|[^A-Za-z0-9_.-])gcloud(?![A-Za-z0-9_-])(?:[ \\t]+[a-z][a-z0-9-]*){0,5}[ \\t]+(?:create|delete|update|deploy|patch|resize|restore|import|rm)(?![^ \\t\\r\\n;&|)])",
510    "agents": [
511      "gcp-live-bigquery-dataset-deletion-guard-agent",
512      "gcp-live-cloud-run-traffic-migration-guard-agent",
513      "gcp-live-cost-budget-action-guard-agent",
514      "gcp-live-gke-rollout-guard-agent",
515      "gcp-live-iam-policy-change-guard-agent",
516      "gcp-live-kms-key-destruction-guard-agent"
517    ],
518    "flags": "",
519    "context": [
520      "bash"
521    ]
522  }
523];
524