davidho27941/cockpit/tree/master/plugins/gcloud-guard
gcloud-guard
在 gcloud 和 gsutil 命令创建、修改或删除云资源前将其挂起,显示账号、项目、位置和目标状态,并让你选择继续或取消。
关于这个 mod
gcloud-guard 通过 Bash 工具 hook 拦截会改变云资源的 gcloud / gsutil 命令。它按 &&、||、;、| 和换行拆分命令,去除 sudo、env 等包装器,分类为破坏性、修改性或创建操作。面板显示账号、项目、位置、alpha/beta 标记及最多五个目标的只读 describe 信息。按 1 Proceed,按 2 Cancel;取消、Esc 或 10 分钟超时都会拒绝命令并告知模型没有执行。提示符上方的上下文行从配置文件和环境变量读取 gcloud 项目、账号、配置及 GKE 上下文,不会启动 gcloud 进程。所有查询都是只读 argv 调用,不写文件、不发网络请求。安装后可用 /gcloud-guard 显示快照或切换、刷新该行。
安装
请先查看作者 README 确认 marketplace 和插件名称;命令可能随仓库结构改变。
claude plugin marketplace add davidho27941/cockpit claude plugin install gcloud-guard
原文 / README
gcloud-guard
Holds a gcloud or gsutil command that would create, change or delete cloud resources before Claude runs it, shows which account and project it would hit and what the targets look like right now, and opens a pane with Proceed and Cancel. Cancel refuses the command and tells Claude why, so it knows nothing ran. Read-only commands (describe, list, ls, get-iam-policy, …) and local ones (auth login, components update) pass straight through.
The hold-and-ask pattern follows Anthropic's blast-radius mod in this repo; the code is written fresh (MIT) and aimed at Google Cloud.
╭─ ⚠ gcloud-guard · destructive ──────────────────────────────────────────────╮
│ delete compute instances web-1, web-2 │
│ Command gcloud compute instances delete web-1 web-2 --zone us-central1-a │
│ Account [email protected] │
│ Project demo-proj ← from gcloud config · Config default │
│ Location us-central1-a │
│ │
│ web-1 · RUNNING · us-central1-a · e2-small · created 2026-09-01 · 2 disks │
│ web-2 · TERMINATED · us-central1-a · e2-medium · created 2026-09-03 │
│ web-2: deletion protection is on; a delete fails unless it is turned off │
│ │
│ 1: Proceed 2: Cancel Claude is waiting on your answer │
╰─────────────────────────────────────────────────────────────────────────────╯
What it holds
The command line is split on &&, ||, ;, | and newlines; VAR=value, sudo, env, nohup, time, nice and ( … ) wrappers are stripped; the first gcloud (or gsutil) segment that would change something decides. The first word after gcloud is the command group, the first table word after it is the verb.
| Severity | gcloud verbs | gsutil |
|---|---|---|
| destructive | delete, remove, destroy, purge, rm, rb, reset, abandon, cancel, revoke, wipe, detach, rollback, unregister, unbind, uninstall, terminate, and the prefixes remove-*, delete-*, detach-*, revoke-*, unregister-*, unbind-*, drop-*, purge-*, destroy-*; remove-iam-policy-binding | rm, rb; notification delete, … clear |
| mutating | update, patch, set, enable, disable, start, stop, suspend, resume, resize, move, rename, promote, failover, restart, reboot, attach, migrate, apply, replace, restore, import, export, upload, rotate, activate, deactivate, deploy, submit, execute, run, trigger, undelete, rsync, cp, mv, and the prefixes update-*, set-*, add-*, attach-*, start-*, stop-*, enable-*, disable-*, restore-*, resize-*, rotate-*, …; add-iam-policy-binding, set-iam-policy; config set / unset / configurations activate (see below) | cp, mv, rsync, setmeta, compose, rewrite; iam ch, acl set, defacl set, lifecycle set, versioning set, cors set, label set, retention set, … |
| create | create, add, insert, clone, snapshot, publish, register, reserve, provision, and create-* | mb; notification create |
Special cases:
- Whole projects, organizations, folders:
gcloud projects delete,organizations delete,folders deleteare destructive with a scope badge; the pane shows the project's state, when it was created and how many services are enabled, and reminds you of the 30-day recovery window. deploy(run deploy,app deploy,functions deploy): mutating, labelled as a deploy, since it creates or replaces a revision.builds submit: mutating, labelled as a build.- IAM bindings: the member and role are shown; for
set-iam-policythe number of bindings in the policy file is compared with the current policy. - Storage (
gcloud storage rm/mv/rsync,gsutil rm/rb/mv/rsync): the objects under each source URL are counted with a read-onlyls -r(capped at 2000);rsync -d/--delete-unmatched-destination-objectsgets a warning. gcloud config set(andunset,configurations activate): held as a local-config change, because switching the project or account silently changes what every later command hits. The pane shows the current value. Turn this off withhold_config_set.- An unknown action verb under a known group (
gcloud compute instances perform-rollout …): held as mutating with a note, to be safe. An unknown verb under an unknown group is not held.
Never held: describe, list, get, get-iam-policy, get-value, get-credentials, ls, cat, stat, du, logs, read, tail, print-access-token, ssh, scp, wait, test-iam-permissions, the list-* / describe-* / get-* / print-* prefixes; the whole auth, components, help, info, version, emulators and init groups; config list / get-value; container clusters get-credentials (it only writes your kubeconfig); compute ssh / scp (a remote shell is not a resource change; see Limits).
What the pane shows
- The headline in red (destructive), yellow (mutating) or green (create).
- The command on one line; the account and project it would hit, with where the project came from (
--projector the gcloud config) and the active configuration; the location (--zone/--region/--location, or(default)); an alpha/beta badge; a yellow warning when--quietis given, since gcloud then skips its own confirmation. - For delete- and update-class commands, one line per target (up to 5) from a read-only
describe --format=json: name, status, zone/region, machine type, database version and tier, size, node count, creation date, labels, attached disks and how many are auto-delete, IAM bindings. Deletion protection raises a note. A target that is not found raises a note too: the delete would fail, or hit something else than you think. - For create-class commands, the sizing flags (
--machine-type,--size,--tier,--num-nodes,--image,--memory,--cpu, …). - When
gcloudis not onPATHor does not answer, the pane says so and the command is still held.
Context line
At all times, not only while holding, a dim line above the prompt says which project the session is pointed at, so a gcloud … delete is never a surprise about where:
☁ gcloud · project side-project-staging · account [email protected] · config default · GKE my-cluster (us-central1)
Where the values come from (files and environment variables only; no gcloud process is started for this line):
| Part | Source |
|---|---|
| config directory | $CLOUDSDK_CONFIG, else ~/.config/gcloud |
| configuration | $CLOUDSDK_ACTIVE_CONFIG_NAME, else the active_config file, else default |
| project, account, zone, region | $CLOUDSDK_CORE_PROJECT, $CLOUDSDK_CORE_ACCOUNT, $CLOUDSDK_COMPUTE_ZONE, $CLOUDSDK_COMPUTE_REGION, else configurations/config_<name> ([core] project = …, [compute] zone = …) |
| GKE cluster | the current-context: line of $KUBECONFIG (colon-separated; the first file with a current context wins), else ~/.kube/config; a gke_<project>_<location>_<cluster> context is shown as GKE <cluster> (<location>) |
A project that comes from CLOUDSDK_CORE_PROJECT is marked ← CLOUDSDK_CORE_PROJECT. Parts that are unknown are left out; when nothing is known (no gcloud configuration at all) the line is not drawn. A kubectl context that is not a GKE cluster (docker-desktop, an EKS ARN) is shown as k8s <name> only with show_other_contexts on.
The line is re-read at session start, every 5 seconds when one of those files' modification times changed (a cheap stat, no read otherwise), after a held gcloud / gsutil command proceeds, and after any Bash command that mentions gcloud config, gcloud auth, gcloud container clusters get-credentials, kubectl config use-context or kubectx. While a command is held, the hold report replaces the line; the hold pane also shows the GKE cluster, in yellow when the cluster's project differs from the project the command would hit.
/gcloud-guard prints the whole snapshot (config directory, configuration, project and its source, account, zone/region, kubeconfig path, current context, the parsed GKE fields) and the hold setting; /gcloud-guard off / on hide and show the line for the session; /gcloud-guard refresh re-reads the files now.
Usage
It works as soon as it is installed. The only command is /gcloud-guard (above).
- When the pane appears, press
1for Proceed or2for Cancel. Prefer the digits. - Arrow keys, Tab and Enter only work while the pane actually holds the keyboard. The pane requests focus when it opens, and Claude Code grants it only over an empty composer. If the composer has text, or the terminal is too narrow and the report is drawn in the band above the prompt instead, the keys stay with the composer. An Enter there is read by Claude Code as "background this turn once the current tool finishes": the session is continued under a new session id and the transcript shows
Backgrounding after the current tool finishes…. To use the arrows, click the pane first, or pressctrl+xthentabto hand it the keyboard. - Cancel has the focus when the pane does hold the keyboard, so Enter there refuses the command.
- No answer within 10 minutes refuses the command. Interrupting the turn (Esc) refuses it too.
- One command is held at a time. A second risky call (from a subagent, say) waits until the first is answered.
Settings
| Option | Default | Meaning |
|---|---|---|
| language | auto | Language of the pane, the toast and the refusal: auto (from LC_ALL, then LC_MESSAGES, then LANG: zh* gives Traditional Chinese, ja* Japanese, anything else English), en, zh-TW or ja. |
| hold | all | destructive holds delete-class commands only; mutating holds delete- and update-class; all also holds create-class. |
| include_gsutil | true | Also watch gsutil. |
| hold_config_set | true | Hold gcloud config set / unset / configurations activate. |
| describe_timeout_seconds | 15 | How long each read-only lookup may take before it is reported as failed (3 to 60). |
| show_context | true | Draw the context line above the prompt. |
| show_other_contexts | false | Also show the current kubectl context when it is not a GKE cluster. |
Set them with /plugin configure gcloud-guard@cockpit, or --config hold=destructive at install. Command words in the pane (delete, compute instances, …) are never translated. Every refusal ends with the same English line, (gcloud-guard: the user did not approve this command; do not retry unless asked.), so Claude reads a Japanese or Chinese refusal as a refusal, not as a transient error.
Beside the other mods in this repo
- Its context line stacks above the lines of opsx-board, auto-handover and cache-keeper with a thin rule between, like theirs. While it holds a command and the pane cannot be placed, the hold report takes over the band; the other lines return once you answer.
- blast-radius and gcloud-guard can both be installed: each watches its own commands. A line such as
gcloud compute instances delete x && rm -rf buildis held by both, one after the other.
Safety boundary
- Every lookup is a read-only gcloud / gsutil command run by argv (
$.process.run), never through a shell: a target name, a URL or a flag value is one argv element, so nothing in it runs as shell. The lookups areconfig get-value,config configurations list,describe,get-iam-policy,projects describe,services list,storage ls -r/gsutil ls -r. - For the context line it reads, read-only, the gcloud configuration files (
active_config,configurations/config_<name>) and thecurrent-context:line of your kubeconfig, and stats them every 5 seconds. It reads the policy file named onset-iam-policyto count its bindings. It writes no files, sends nothing anywhere and calls no model. - The lookups run with your credentials and need the matching
get/listpermissions; where they fail the pane says so and the command is still held. - It is a safety net, not IAM. After Proceed the command runs as written, with no sandbox. For a hard block use permission rules or the project's IAM.
What it does, before you install it
claude plugin validate ./plugins/gcloud-guard
Result (v0.1.0):
hooks: session.start, command.run{command=gcloud-guard}, tool.call{tool=Bash},
ui.render{component=Pane, requestId=gcloud-guard}, ui.render{component=AbovePrompt}
calls: $.clock.every, $.clock.now, $.command.register, $.env.get, $.fs.read, $.fs.stat,
$.process.run, $.state.get, $.state.set, $.ui.close, $.ui.invalidate, $.ui.open,
$.ui.resolve, $.ui.toast
env reads: CLOUDSDK_ACTIVE_CONFIG_NAME, CLOUDSDK_COMPUTE_REGION, CLOUDSDK_COMPUTE_ZONE,
CLOUDSDK_CONFIG, CLOUDSDK_CORE_ACCOUNT, CLOUDSDK_CORE_PROJECT, HOME, KUBECONFIG,
LANG, LC_ALL, LC_MESSAGES · env writes: nothing
$.process.run runs the read-only lookups above and the 0.25-second sleep that paces the hold; $.fs.read and $.fs.stat read the gcloud and kube config files for the context line and a set-iam-policy file; $.clock.every is the 5-second file check; no network of its own, no model calls.
Limits
- It reads the command text; it is not a shell parser.
$(…), aliases,eval,bash -c "…",xargs gcloud, scripts and Makefiles that call gcloud,terraform,kubectl,bq, the Python client libraries and the Console are not covered. compute sshis not held, although a command run on the VM can change anything there.- Flag parsing is heuristic:
--flag valueis read as a value unless the flag is a known boolean (--quiet,--async,--force,--to-latest,--no-*,--enable-*, …). A boolean flag it does not know, followed by a resource name, swallows that name from the target list; the command is still held. - The verb tables cover the common groups. A verb that is not in them is held only when it looks like an action (
perform-*,trigger-*, …) under a known group; anything else is let through.gcloudgrows faster than this table. describeneeds permissions and time: up to 5 targets, each with the configured timeout, plus the config lookups. On a slow network the pane can take a few seconds to appear; the command is held from the start regardless.- Object counting for storage lists up to 2000 objects per URL and then says
2000+;gsutil ls -ron a huge bucket may hit the timeout, which is reported as a note. - One command is held at a time; a second waits.
- The context line reads the files gcloud and kubectl read, not what they would resolve: a
--projecton a later command, agcloud config setin another shell before the next 5-second check, or a kubeconfig merged from several files with nocurrent-context:in the first one can make the line lag or differ./gcloud-guard refreshre-reads at once. - Verified in the test kit against a stand-in for gcloud and a fake file system; in a live session a
gcloud config setwas held, the pane showed the account and project, and Proceed ran it.
Development
claude --plugin-dir ./plugins/gcloud-guard # load once
claude plugin validate ./plugins/gcloud-guard
claude plugin test ./plugins/gcloud-guard # classifier table, lookups, the hold with a fake gcloud
The classifier and the report text live in hooks/logic.ts (pure), the strings in hooks/i18n.ts, the hooks and the drawing in hooks/register.tsx. If blast-radius is installed while you work on this mod, write test files with the editor rather than a heredoc: a literal rm -rf in a Bash command line is held by it.
