From 623eadc7afcc3ab3fc58d948f4cb53eb2eca2aaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=86gir=20M=C3=A1ni=20Hauksson?= <54936225+sourcehawk@users.noreply.github.com> Date: Sun, 31 May 2026 02:53:53 +0200 Subject: [PATCH] docs(cloud): clarify scope is a guardrail and read-only rests on the IAM floor Addresses two review findings: scope only constrains explicit --project/--region values (omission falls back to the CLI default, so hard project confinement is the per-project IAM grant), and allowlist entries must be leaf read-verbs (an intermediate override would admit mutating siblings via prefix match; the no-write guarantee is the read-only IAM grant, not the allowlist alone). Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/content/cloud-providers.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/content/cloud-providers.md b/docs/content/cloud-providers.md index 3ce5bb1..64f52ce 100644 --- a/docs/content/cloud-providers.md +++ b/docs/content/cloud-providers.md @@ -177,6 +177,8 @@ scope: An empty (or omitted) `projects` or `regions` axis is unconstrained on that axis. A non-empty one is a closed set: a `--project`, `--region`, or `--zone` value outside it fails validation before the command runs. +Scope constrains the value of an explicit flag; it does not force one to be present. If the agent omits `--project`, the CLI falls back to its own default target (the impersonated identity's default project, `CLOUDSDK_CORE_PROJECT`, or for AWS the configured `AWS_REGION`), which scope does not police. Hard project confinement therefore comes from the pinned identity's IAM, not from scope: grant the read-only roles only on the in-scope projects, as the setup above does, so an out-of-scope project is unreachable whatever the argv. Region has no equivalent IAM boundary, so treat region scope as a guardrail against explicit pivots rather than a hard limit. + `accounts` is informational and reserved: it documents which AWS accounts the source is expected to reach, but `run_cli` does not validate account ids on argv. What actually bounds account reach is the pinned assume-role profile, whose role can only see the accounts its trust policy and permissions allow. Treat `accounts` as a note to operators, not an enforced allowlist. Identity-selecting flags (`--account`, `--profile`) never reach scope validation at all, because the deny floor rejects them first. @@ -185,6 +187,8 @@ Identity-selecting flags (`--account`, `--profile`) never reach scope validation What the agent can run through `run_cli` is governed by a positive command allowlist of normalized subcommand paths, for example `compute firewall-rules list` for GCP or `ec2 describe-security-groups` for AWS. Each provider ships an embedded read-only default covering the six axes. Point `command_allowlist_path` at a file (relative to the profile.yaml) to override it; an empty value uses the embedded default. The allowlist is the single source of truth, so the discovery tool advertises exactly what is permitted. +Allowlist entries must be complete leaf verbs, for example `compute instances list` or `ec2 describe-security-groups`, never an intermediate group path like `compute instances` or `ec2`. The allowlist matches an entry as a prefix of the command, so an intermediate entry would also admit its sibling verbs, including mutating ones (`compute instances delete`, `ec2 terminate-instances`). The shipped defaults are all leaf read verbs. The guarantee that the agent cannot write, even under a careless override, is the read-only IAM grant on the pinned identity: a viewer-only principal's mutating call fails at the cloud. The allowlist and deny floor keep the agent to reads and exclude secret-read and exfil; the no-write property itself rests on the identity's permissions. + Underneath the allowlist sits a hardcoded deny floor the config can never re-enable, mirroring how the k8s MCP always filters Secret regardless of its kinds config. The floor covers dangerous subcommands (`secrets`, `ssh`, `scp`, `cp`, `sync`, `auth`, `config`), dangerous flags (`--impersonate-service-account`, `--account`, `--profile`, `--endpoint-url`, `--cli-input-json`, `--cli-input-yaml`, `--configuration`), and argument values beginning with `file://`, `fileb://`, `@`, `http://`, or `https://` (local-file read and SSRF vectors). A too-broad allowlist override cannot punch through it. The command allowlist and the IAM grant are independent layers and must stay aligned. The recommended policies above are least-privilege for the default allowlist. Tightening the allowlist needs no IAM change; if you widen it with `command_allowlist_path`, widen the identity's read-only grant to match, or the added commands fail at the cloud rather than at the harness. Never widen either beyond read-only. The authoritative list of what a configured source permits is whatever the agent's `list_allowed_commands` tool returns, which reads the same allowlist `run_cli` enforces; each provider's shipped default lives in its `default_commands.json` under `pkg/mcp/cloud/providers//`.