From f4bb7cf0bf0b79fe3e032b0cbda74f5f3b9a5449 Mon Sep 17 00:00:00 2001 From: NiveditJain Date: Mon, 27 Apr 2026 14:43:17 -0700 Subject: [PATCH] [luv-201] feat: add Infra Commands category with 7 opt-in policies + cut 0.0.7 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a new built-in policy category that prevents coding agents from running infrastructure CLIs or triggering CI/CD pipelines without explicit opt-in: - block-kubectl, block-terraform (terraform/tofu), block-aws-cli, block-gcloud, block-az-cli, block-helm - block-gh-pipeline targets only mutating gh subcommands (workflow run, pr merge, release create, etc.) — read-only forms used by other failproofai workflow policies remain allowed All seven default to opt-in (defaultEnabled: false) and accept an allowPatterns param that reuses the existing matchesAllowedPattern helper, inheriting its shell-operator-injection defenses (verified by the block-sudo allowPattern test suite). Built-in policy count: 32 → 39. Bumps version 0.0.7-beta.0 → 0.0.7 and cuts the 0.0.7 changelog section. Co-Authored-By: Claude Opus 4.7 --- CHANGELOG.md | 5 + __tests__/hooks/builtin-policies.test.ts | 275 ++++++++++++++++++++++- docs/built-in-policies.mdx | 197 +++++++++++++++- package.json | 2 +- src/hooks/builtin-policies.ts | 162 +++++++++++++ 5 files changed, 636 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b4cde35..a0762042 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## Unreleased +## 0.0.7 — 2026-04-27 + +### Features +- Add `Infra Commands` category with seven opt-in policies: `block-kubectl`, `block-terraform`, `block-aws-cli`, `block-gcloud`, `block-az-cli`, `block-helm`, and `block-gh-pipeline`. Each denies invocations of its CLI by default and supports an `allowPatterns` param so users can carve out read-only subcommands (e.g. `kubectl get *`, `terraform plan`, `aws s3 ls *`). `block-gh-pipeline` only matches mutating subcommands (`workflow run`, `pr merge`, `release create`, etc.) so read-only `gh` calls used by other policies continue to work (#202). + ### Fixes - Skip `require-no-conflicts-before-stop` entirely when no OPEN PR exists for the current branch (or when `gh` CLI is unavailable to check). The policy no longer runs Layer 1's local `git merge-tree` probe in those cases — without a confirmable merge target there is nothing to enforce (#198). - Resolve project policy config (`.failproofai/`) by walking up from the live CWD to find the nearest project root, instead of looking only at the exact session cwd. Stop-gating policies (`require-pr-before-stop`, `block-read-outside-cwd`, etc.) no longer silently disable when Claude `cd`s into a subdirectory. Also covers `customPoliciesPath` and project convention discovery in `custom-hooks-loader.ts` (#200). diff --git a/__tests__/hooks/builtin-policies.test.ts b/__tests__/hooks/builtin-policies.test.ts index ba18ea75..0f209515 100644 --- a/__tests__/hooks/builtin-policies.test.ts +++ b/__tests__/hooks/builtin-policies.test.ts @@ -34,8 +34,8 @@ describe("hooks/builtin-policies", () => { }); describe("BUILTIN_POLICIES", () => { - it("has 32 built-in policies", () => { - expect(BUILTIN_POLICIES).toHaveLength(32); + it("has 39 built-in policies", () => { + expect(BUILTIN_POLICIES).toHaveLength(39); }); it("has 11 default-enabled policies", () => { @@ -1039,6 +1039,277 @@ describe("hooks/builtin-policies", () => { }); }); + describe("block-kubectl", () => { + const policy = BUILTIN_POLICIES.find((p) => p.name === "block-kubectl")!; + + it("blocks kubectl apply", async () => { + const ctx = makeCtx({ toolName: "Bash", toolInput: { command: "kubectl apply -f deploy.yaml" } }); + expect((await policy.fn(ctx)).decision).toBe("deny"); + }); + + it("blocks kubectl delete", async () => { + const ctx = makeCtx({ toolName: "Bash", toolInput: { command: "kubectl delete pod my-pod" } }); + expect((await policy.fn(ctx)).decision).toBe("deny"); + }); + + it("blocks kubectl after && chain", async () => { + const ctx = makeCtx({ toolName: "Bash", toolInput: { command: "echo hi && kubectl apply -f x.yaml" } }); + expect((await policy.fn(ctx)).decision).toBe("deny"); + }); + + it("allows unrelated commands", async () => { + const ctx = makeCtx({ toolName: "Bash", toolInput: { command: "echo hello" } }); + expect((await policy.fn(ctx)).decision).toBe("allow"); + }); + + it("does not match commands that merely contain 'kubectl' as a substring", async () => { + const ctx = makeCtx({ toolName: "Bash", toolInput: { command: "echo kubectlx is not a real binary" } }); + expect((await policy.fn(ctx)).decision).toBe("allow"); + }); + + it("allows kubectl get with allowPatterns", async () => { + const ctx = makeCtx({ + toolName: "Bash", + toolInput: { command: "kubectl get pods" }, + params: { allowPatterns: ["kubectl get *"] }, + }); + expect((await policy.fn(ctx)).decision).toBe("allow"); + }); + + it("blocks shell-injection bypass via ; even when allowPattern would match prefix", async () => { + const ctx = makeCtx({ + toolName: "Bash", + toolInput: { command: "kubectl get pods; rm -rf /" }, + params: { allowPatterns: ["kubectl get *"] }, + }); + expect((await policy.fn(ctx)).decision).toBe("deny"); + }); + + it("allows non-Bash tools", async () => { + const ctx = makeCtx({ toolName: "Read", toolInput: { command: "kubectl apply -f x.yaml" } }); + expect((await policy.fn(ctx)).decision).toBe("allow"); + }); + }); + + describe("block-terraform", () => { + const policy = BUILTIN_POLICIES.find((p) => p.name === "block-terraform")!; + + it("blocks terraform apply", async () => { + const ctx = makeCtx({ toolName: "Bash", toolInput: { command: "terraform apply -auto-approve" } }); + expect((await policy.fn(ctx)).decision).toBe("deny"); + }); + + it("blocks terraform destroy", async () => { + const ctx = makeCtx({ toolName: "Bash", toolInput: { command: "terraform destroy" } }); + expect((await policy.fn(ctx)).decision).toBe("deny"); + }); + + it("also blocks tofu (OpenTofu)", async () => { + const ctx = makeCtx({ toolName: "Bash", toolInput: { command: "tofu apply" } }); + expect((await policy.fn(ctx)).decision).toBe("deny"); + }); + + it("allows terraform plan with allowPatterns", async () => { + const ctx = makeCtx({ + toolName: "Bash", + toolInput: { command: "terraform plan" }, + params: { allowPatterns: ["terraform plan", "terraform validate"] }, + }); + expect((await policy.fn(ctx)).decision).toBe("allow"); + }); + + it("allows unrelated commands", async () => { + const ctx = makeCtx({ toolName: "Bash", toolInput: { command: "ls -la" } }); + expect((await policy.fn(ctx)).decision).toBe("allow"); + }); + }); + + describe("block-aws-cli", () => { + const policy = BUILTIN_POLICIES.find((p) => p.name === "block-aws-cli")!; + + it("blocks aws s3 cp", async () => { + const ctx = makeCtx({ toolName: "Bash", toolInput: { command: "aws s3 cp ./local s3://bucket/" } }); + expect((await policy.fn(ctx)).decision).toBe("deny"); + }); + + it("blocks aws ec2 terminate-instances", async () => { + const ctx = makeCtx({ toolName: "Bash", toolInput: { command: "aws ec2 terminate-instances --instance-ids i-123" } }); + expect((await policy.fn(ctx)).decision).toBe("deny"); + }); + + it("allows aws sts get-caller-identity with allowPatterns", async () => { + const ctx = makeCtx({ + toolName: "Bash", + toolInput: { command: "aws sts get-caller-identity" }, + params: { allowPatterns: ["aws sts get-caller-identity"] }, + }); + expect((await policy.fn(ctx)).decision).toBe("allow"); + }); + + it("does not match 'awscli' or 'awsctl'", async () => { + const ctx = makeCtx({ toolName: "Bash", toolInput: { command: "echo awscli" } }); + expect((await policy.fn(ctx)).decision).toBe("allow"); + }); + }); + + describe("block-gcloud", () => { + const policy = BUILTIN_POLICIES.find((p) => p.name === "block-gcloud")!; + + it("blocks gcloud compute instances delete", async () => { + const ctx = makeCtx({ toolName: "Bash", toolInput: { command: "gcloud compute instances delete my-vm" } }); + expect((await policy.fn(ctx)).decision).toBe("deny"); + }); + + it("allows gcloud auth list with allowPatterns", async () => { + const ctx = makeCtx({ + toolName: "Bash", + toolInput: { command: "gcloud auth list" }, + params: { allowPatterns: ["gcloud auth list", "gcloud config list"] }, + }); + expect((await policy.fn(ctx)).decision).toBe("allow"); + }); + + it("allows unrelated commands", async () => { + const ctx = makeCtx({ toolName: "Bash", toolInput: { command: "echo hi" } }); + expect((await policy.fn(ctx)).decision).toBe("allow"); + }); + }); + + describe("block-az-cli", () => { + const policy = BUILTIN_POLICIES.find((p) => p.name === "block-az-cli")!; + + it("blocks az group delete", async () => { + const ctx = makeCtx({ toolName: "Bash", toolInput: { command: "az group delete --name my-rg --yes" } }); + expect((await policy.fn(ctx)).decision).toBe("deny"); + }); + + it("allows az account show with allowPatterns", async () => { + const ctx = makeCtx({ + toolName: "Bash", + toolInput: { command: "az account show" }, + params: { allowPatterns: ["az account show"] }, + }); + expect((await policy.fn(ctx)).decision).toBe("allow"); + }); + + it("does not match commands beginning with another 'az'-prefixed binary", async () => { + const ctx = makeCtx({ toolName: "Bash", toolInput: { command: "azure-cli list" } }); + expect((await policy.fn(ctx)).decision).toBe("allow"); + }); + }); + + describe("block-helm", () => { + const policy = BUILTIN_POLICIES.find((p) => p.name === "block-helm")!; + + it("blocks helm install", async () => { + const ctx = makeCtx({ toolName: "Bash", toolInput: { command: "helm install my-release ./chart" } }); + expect((await policy.fn(ctx)).decision).toBe("deny"); + }); + + it("blocks helm upgrade", async () => { + const ctx = makeCtx({ toolName: "Bash", toolInput: { command: "helm upgrade my-release ./chart" } }); + expect((await policy.fn(ctx)).decision).toBe("deny"); + }); + + it("allows helm list with allowPatterns", async () => { + const ctx = makeCtx({ + toolName: "Bash", + toolInput: { command: "helm list" }, + params: { allowPatterns: ["helm list", "helm status *"] }, + }); + expect((await policy.fn(ctx)).decision).toBe("allow"); + }); + }); + + describe("block-gh-pipeline", () => { + const policy = BUILTIN_POLICIES.find((p) => p.name === "block-gh-pipeline")!; + + it("blocks gh workflow run", async () => { + const ctx = makeCtx({ toolName: "Bash", toolInput: { command: "gh workflow run deploy.yml" } }); + expect((await policy.fn(ctx)).decision).toBe("deny"); + }); + + it("blocks gh workflow enable", async () => { + const ctx = makeCtx({ toolName: "Bash", toolInput: { command: "gh workflow enable deploy.yml" } }); + expect((await policy.fn(ctx)).decision).toBe("deny"); + }); + + it("blocks gh run rerun", async () => { + const ctx = makeCtx({ toolName: "Bash", toolInput: { command: "gh run rerun 12345" } }); + expect((await policy.fn(ctx)).decision).toBe("deny"); + }); + + it("blocks gh run cancel", async () => { + const ctx = makeCtx({ toolName: "Bash", toolInput: { command: "gh run cancel 12345" } }); + expect((await policy.fn(ctx)).decision).toBe("deny"); + }); + + it("blocks gh pr merge", async () => { + const ctx = makeCtx({ toolName: "Bash", toolInput: { command: "gh pr merge 42 --squash" } }); + expect((await policy.fn(ctx)).decision).toBe("deny"); + }); + + it("blocks gh release create", async () => { + const ctx = makeCtx({ toolName: "Bash", toolInput: { command: "gh release create v1.0.0" } }); + expect((await policy.fn(ctx)).decision).toBe("deny"); + }); + + it("blocks gh release delete", async () => { + const ctx = makeCtx({ toolName: "Bash", toolInput: { command: "gh release delete v1.0.0 --yes" } }); + expect((await policy.fn(ctx)).decision).toBe("deny"); + }); + + it("blocks gh cache delete", async () => { + const ctx = makeCtx({ toolName: "Bash", toolInput: { command: "gh cache delete 12345" } }); + expect((await policy.fn(ctx)).decision).toBe("deny"); + }); + + it("blocks gh secret set", async () => { + const ctx = makeCtx({ toolName: "Bash", toolInput: { command: "gh secret set MY_SECRET --body xyz" } }); + expect((await policy.fn(ctx)).decision).toBe("deny"); + }); + + it("blocks gh secret delete", async () => { + const ctx = makeCtx({ toolName: "Bash", toolInput: { command: "gh secret delete MY_SECRET" } }); + expect((await policy.fn(ctx)).decision).toBe("deny"); + }); + + it("blocks gh workflow run after && chain", async () => { + const ctx = makeCtx({ toolName: "Bash", toolInput: { command: "echo deploying && gh workflow run deploy.yml" } }); + expect((await policy.fn(ctx)).decision).toBe("deny"); + }); + + it("allows gh pr view (read-only)", async () => { + const ctx = makeCtx({ toolName: "Bash", toolInput: { command: "gh pr view 42" } }); + expect((await policy.fn(ctx)).decision).toBe("allow"); + }); + + it("allows gh pr list (read-only)", async () => { + const ctx = makeCtx({ toolName: "Bash", toolInput: { command: "gh pr list --head my-branch" } }); + expect((await policy.fn(ctx)).decision).toBe("allow"); + }); + + it("allows gh run list (read-only)", async () => { + const ctx = makeCtx({ toolName: "Bash", toolInput: { command: "gh run list --limit 5" } }); + expect((await policy.fn(ctx)).decision).toBe("allow"); + }); + + it("allows gh api repos/.../check-runs (read-only)", async () => { + const ctx = makeCtx({ toolName: "Bash", toolInput: { command: "gh api repos/owner/repo/commits/abc/check-runs" } }); + expect((await policy.fn(ctx)).decision).toBe("allow"); + }); + + it("allows gh release view (read-only)", async () => { + const ctx = makeCtx({ toolName: "Bash", toolInput: { command: "gh release view v1.0.0" } }); + expect((await policy.fn(ctx)).decision).toBe("allow"); + }); + + it("allows unrelated commands", async () => { + const ctx = makeCtx({ toolName: "Bash", toolInput: { command: "git status" } }); + expect((await policy.fn(ctx)).decision).toBe("allow"); + }); + }); + describe("warn-repeated-tool-calls", () => { const policy = BUILTIN_POLICIES.find((p) => p.name === "warn-repeated-tool-calls")!; diff --git a/docs/built-in-policies.mdx b/docs/built-in-policies.mdx index 7f336ff8..368142e0 100644 --- a/docs/built-in-policies.mdx +++ b/docs/built-in-policies.mdx @@ -1,10 +1,10 @@ --- title: Built-in Policies -description: "All 32 built-in policies that catch common agent failure modes" +description: "All 39 built-in policies that catch common agent failure modes" icon: shield --- -failproofai ships with 32 built-in policies that catch common agent failure modes. Each policy fires on a specific hook event type and tool name. Twelve policies accept parameters that let you tune their behavior without writing code. Five workflow policies enforce a commit → push → PR → CI pipeline before Claude stops. +failproofai ships with 39 built-in policies that catch common agent failure modes. Each policy fires on a specific hook event type and tool name. Nineteen policies accept parameters that let you tune their behavior without writing code. Five workflow policies enforce a commit → push → PR → CI pipeline before Claude stops. --- @@ -15,6 +15,7 @@ Policies are grouped into categories: | Category | Policies | Hook type | |----------|----------|-----------| | [Dangerous commands](#dangerous-commands) | block-sudo, block-rm-rf, block-curl-pipe-sh, block-failproofai-commands | PreToolUse | +| [Infra commands](#infra-commands) | block-kubectl, block-terraform, block-aws-cli, block-gcloud, block-az-cli, block-helm, block-gh-pipeline | PreToolUse | | [Secrets (sanitizers)](#secrets-sanitizers) | sanitize-jwt, sanitize-api-keys, sanitize-connection-strings, sanitize-private-key-content, sanitize-bearer-tokens | PostToolUse | | [Environment](#environment) | block-env-files, protect-env-vars | PreToolUse | | [File access](#file-access) | block-read-outside-cwd, block-secrets-write | PreToolUse | @@ -140,6 +141,198 @@ No parameters. --- +## Infra commands + +Stop coding agents from running infrastructure CLIs or triggering CI/CD pipelines. All policies in this category are **opt-in** (`defaultEnabled: false`) — agents that legitimately need to call `kubectl`, `terraform`, etc. will not be disrupted unless you enable the policy. When enabled, every invocation of the matched CLI is denied unless the command matches an entry in `allowPatterns`. + +The pattern grammar is the same as [`block-sudo`](#block-sudo): tokens are matched against parsed argv, `*` is a wildcard for one token, and any command containing a standalone shell operator (`&&`, `||`, `|`, `;`) or a token with embedded shell metacharacters is rejected before allowlist matching to prevent injection bypasses. + +### `block-kubectl` + +**Event:** PreToolUse (Bash) +**Default:** Denies any `kubectl` invocation. + +**Parameters:** + +| Param | Type | Default | Description | +|-------|------|---------|-------------| +| `allowPatterns` | `string[]` | `[]` | kubectl command prefixes that are permitted. | + +**Example:** + +```json +{ + "policyParams": { + "block-kubectl": { + "allowPatterns": ["kubectl get *", "kubectl describe *", "kubectl logs *"] + } + } +} +``` + +With this config, `kubectl get pods` is allowed but `kubectl apply -f deploy.yaml` is denied. + +--- + +### `block-terraform` + +**Event:** PreToolUse (Bash) +**Default:** Denies any `terraform` or `tofu` (OpenTofu) invocation. + +**Parameters:** + +| Param | Type | Default | Description | +|-------|------|---------|-------------| +| `allowPatterns` | `string[]` | `[]` | terraform/tofu command prefixes that are permitted. | + +**Example:** + +```json +{ + "policyParams": { + "block-terraform": { + "allowPatterns": ["terraform plan", "terraform validate", "terraform show *"] + } + } +} +``` + +--- + +### `block-aws-cli` + +**Event:** PreToolUse (Bash) +**Default:** Denies any `aws` CLI invocation. + +**Parameters:** + +| Param | Type | Default | Description | +|-------|------|---------|-------------| +| `allowPatterns` | `string[]` | `[]` | aws CLI command prefixes that are permitted. | + +**Example:** + +```json +{ + "policyParams": { + "block-aws-cli": { + "allowPatterns": ["aws s3 ls *", "aws sts get-caller-identity"] + } + } +} +``` + +--- + +### `block-gcloud` + +**Event:** PreToolUse (Bash) +**Default:** Denies any `gcloud` (Google Cloud) CLI invocation. + +**Parameters:** + +| Param | Type | Default | Description | +|-------|------|---------|-------------| +| `allowPatterns` | `string[]` | `[]` | gcloud command prefixes that are permitted. | + +**Example:** + +```json +{ + "policyParams": { + "block-gcloud": { + "allowPatterns": ["gcloud auth list", "gcloud config list"] + } + } +} +``` + +--- + +### `block-az-cli` + +**Event:** PreToolUse (Bash) +**Default:** Denies any `az` (Azure) CLI invocation. + +**Parameters:** + +| Param | Type | Default | Description | +|-------|------|---------|-------------| +| `allowPatterns` | `string[]` | `[]` | az CLI command prefixes that are permitted. | + +**Example:** + +```json +{ + "policyParams": { + "block-az-cli": { + "allowPatterns": ["az account show", "az group list"] + } + } +} +``` + +--- + +### `block-helm` + +**Event:** PreToolUse (Bash) +**Default:** Denies any `helm` invocation. + +**Parameters:** + +| Param | Type | Default | Description | +|-------|------|---------|-------------| +| `allowPatterns` | `string[]` | `[]` | helm command prefixes that are permitted. | + +**Example:** + +```json +{ + "policyParams": { + "block-helm": { + "allowPatterns": ["helm list", "helm status *"] + } + } +} +``` + +--- + +### `block-gh-pipeline` + +**Event:** PreToolUse (Bash) +**Default:** Denies the following `gh` CLI subcommands that mutate state or trigger pipelines: + +- `gh workflow run`, `gh workflow enable`, `gh workflow disable` +- `gh run rerun`, `gh run cancel` +- `gh pr merge` +- `gh release create`, `gh release delete` +- `gh cache delete` +- `gh secret set`, `gh secret delete` + +Read-only `gh` subcommands such as `gh pr view`, `gh pr list`, `gh run list`, `gh release view`, and `gh api repos/.../...` are **not** matched by this policy — they are routinely needed for workflow checks (including failproofai's own `require-ci-green-before-stop`). + +**Parameters:** + +| Param | Type | Default | Description | +|-------|------|---------|-------------| +| `allowPatterns` | `string[]` | `[]` | Specific scripted invocations to allow even though they would otherwise be denied. | + +**Example:** + +```json +{ + "policyParams": { + "block-gh-pipeline": { + "allowPatterns": ["gh run rerun *"] + } + } +} +``` + +--- + ## Secrets (sanitizers) Stop agents from leaking credentials into their context or output. Sanitizer policies fire on **PostToolUse** events. When Claude runs a Bash command, reads a file, or calls any tool, these policies inspect the output before it is returned to Claude. If a secret pattern is detected, the policy returns a deny decision that prevents the output from being passed back. diff --git a/package.json b/package.json index 6feac13f..c3711f75 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "failproofai", - "version": "0.0.7-beta.0", + "version": "0.0.7", "description": "The easiest way to manage policies that keep your AI agents reliable, on-task, and running autonomously — for Claude Code & the Agents SDK", "bin": { "failproofai": "./dist/cli.mjs" diff --git a/src/hooks/builtin-policies.ts b/src/hooks/builtin-policies.ts index 967f4f30..273adaef 100644 --- a/src/hooks/builtin-policies.ts +++ b/src/hooks/builtin-policies.ts @@ -159,6 +159,21 @@ const TMUX_DETACH_RE = /\btmux\s+(?:new-session|new)\b[^|&;]*-d\b/; const DISOWN_RE = /\bdisown\b/; const BACKGROUND_AMPERSAND_RE = /(? matchesAllowedPattern(cmd, p))) return allow(); + return deny(denyMsg); +} + +function blockKubectl(ctx: PolicyContext): PolicyResult { + return blockInfraCli(ctx, KUBECTL_RE, "kubectl commands are blocked"); +} + +function blockTerraform(ctx: PolicyContext): PolicyResult { + return blockInfraCli(ctx, TERRAFORM_RE, "terraform/tofu commands are blocked"); +} + +function blockAwsCli(ctx: PolicyContext): PolicyResult { + return blockInfraCli(ctx, AWS_CLI_RE, "aws CLI commands are blocked"); +} + +function blockGcloud(ctx: PolicyContext): PolicyResult { + return blockInfraCli(ctx, GCLOUD_RE, "gcloud commands are blocked"); +} + +function blockAzCli(ctx: PolicyContext): PolicyResult { + return blockInfraCli(ctx, AZ_CLI_RE, "az (Azure) CLI commands are blocked"); +} + +function blockHelm(ctx: PolicyContext): PolicyResult { + return blockInfraCli(ctx, HELM_RE, "helm commands are blocked"); +} + +// gh-pipeline only fires on mutating subcommands; allowPatterns are still +// supported in case a user wants to permit a specific scripted invocation. +function blockGhPipeline(ctx: PolicyContext): PolicyResult { + return blockInfraCli(ctx, GH_PIPELINE_RE, "gh pipeline-trigger commands are blocked"); +} + // Maximum size of the per-session tool-call sidecar before we stop updating it. // If exceeded, repeated-call detection degrades gracefully (allows through) rather // than growing the file unboundedly. @@ -1485,6 +1542,111 @@ export const BUILTIN_POLICIES: BuiltinPolicyDefinition[] = [ defaultEnabled: true, category: "Dangerous Commands", }, + { + name: "block-kubectl", + description: "Block kubectl commands (Kubernetes cluster mutations)", + fn: blockKubectl, + match: { events: ["PreToolUse"], toolNames: ["Bash"] }, + defaultEnabled: false, + category: "Infra Commands", + params: { + allowPatterns: { + type: "string[]", + description: "kubectl command patterns to allow, matched token-by-token (e.g. 'kubectl get *', 'kubectl describe *')", + default: [], + }, + } satisfies PolicyParamsSchema, + }, + { + name: "block-terraform", + description: "Block terraform and tofu (OpenTofu) commands", + fn: blockTerraform, + match: { events: ["PreToolUse"], toolNames: ["Bash"] }, + defaultEnabled: false, + category: "Infra Commands", + params: { + allowPatterns: { + type: "string[]", + description: "terraform/tofu command patterns to allow (e.g. 'terraform plan', 'terraform validate')", + default: [], + }, + } satisfies PolicyParamsSchema, + }, + { + name: "block-aws-cli", + description: "Block aws CLI commands", + fn: blockAwsCli, + match: { events: ["PreToolUse"], toolNames: ["Bash"] }, + defaultEnabled: false, + category: "Infra Commands", + params: { + allowPatterns: { + type: "string[]", + description: "aws CLI command patterns to allow (e.g. 'aws s3 ls *', 'aws sts get-caller-identity')", + default: [], + }, + } satisfies PolicyParamsSchema, + }, + { + name: "block-gcloud", + description: "Block gcloud (Google Cloud) CLI commands", + fn: blockGcloud, + match: { events: ["PreToolUse"], toolNames: ["Bash"] }, + defaultEnabled: false, + category: "Infra Commands", + params: { + allowPatterns: { + type: "string[]", + description: "gcloud command patterns to allow (e.g. 'gcloud auth list', 'gcloud config list')", + default: [], + }, + } satisfies PolicyParamsSchema, + }, + { + name: "block-az-cli", + description: "Block az (Azure) CLI commands", + fn: blockAzCli, + match: { events: ["PreToolUse"], toolNames: ["Bash"] }, + defaultEnabled: false, + category: "Infra Commands", + params: { + allowPatterns: { + type: "string[]", + description: "az CLI command patterns to allow (e.g. 'az account show', 'az group list')", + default: [], + }, + } satisfies PolicyParamsSchema, + }, + { + name: "block-helm", + description: "Block helm commands", + fn: blockHelm, + match: { events: ["PreToolUse"], toolNames: ["Bash"] }, + defaultEnabled: false, + category: "Infra Commands", + params: { + allowPatterns: { + type: "string[]", + description: "helm command patterns to allow (e.g. 'helm list', 'helm status *')", + default: [], + }, + } satisfies PolicyParamsSchema, + }, + { + name: "block-gh-pipeline", + description: "Block gh CLI pipeline-trigger subcommands (workflow run, run rerun/cancel, pr merge, release create/delete, cache delete, secret set/delete)", + fn: blockGhPipeline, + match: { events: ["PreToolUse"], toolNames: ["Bash"] }, + defaultEnabled: false, + category: "Infra Commands", + params: { + allowPatterns: { + type: "string[]", + description: "gh pipeline command patterns to allow (e.g. specific scripted invocations); read-only gh subcommands like 'gh pr view' and 'gh run list' are not matched by this policy", + default: [], + }, + } satisfies PolicyParamsSchema, + }, { name: "block-secrets-write", description: "Block writing secret key files",