Skip to content

Commit 4cac149

Browse files
authored
feat(safety): enforce runtime read-only mode (#866)
* feat(safety): enforce runtime read-only mode * fix(safety): scope readonly query allowlist * fix(safety): cover auth setup and discovery writes * fix(safety): align discovery query classification * style(safety): satisfy readonly lint checks * fix(auth): preserve readonly setup guidance * style(auth): scope gcloud lint suppression
1 parent 0122f09 commit 4cac149

721 files changed

Lines changed: 1140 additions & 40 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
- CLI: add `GOG_HELP=agent` compact root help with common read-only recipes and targeted schema guidance so agents can execute Gmail, Calendar, and Drive tasks without traversing multiple help levels.
77
- Auth: add `auth setup` for guided Google Cloud project/API preparation, OAuth client installation, and optional browser authorization.
88
- API: add Discovery-backed `api list`, `api describe`, and scoped `api call` access for Google methods outside the first-class command surface, with dry-run plans and explicit write opt-in.
9+
- Safety: add global `--readonly` / `GOG_READONLY=1` runtime enforcement that blocks mutating Google and Zoom API requests before dispatch while preserving read-only query POSTs and least-privilege OAuth setup.
910

1011
## 0.30.0 - 2026-06-21
1112

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,7 @@ Useful global flags:
556556
- `--wrap-untrusted`: in JSON/raw output, wrap fetched free-text fields with
557557
external untrusted-content markers for LLM/agent consumption
558558
- `--dry-run`: print intended actions where a command supports planning
559+
- `--readonly`: block mutating API requests before network dispatch; still allow known POST-based query APIs
559560
- `--no-input`: fail instead of prompting
560561
- `--force`: confirm destructive operations
561562
- `--enable-commands <csv>`: allow selected command prefixes. Parent paths allow children, so `gmail` allows the Gmail command family.
@@ -569,6 +570,7 @@ For coding agents or CI, prefer:
569570
gog --account [email protected] \
570571
--enable-commands-exact gmail.search,gmail.get,drive.ls,docs.cat \
571572
--gmail-no-send \
573+
--readonly \
572574
--wrap-untrusted \
573575
--json \
574576
gmail search 'newer_than:7d'

docs/automation.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,18 @@ Use `--no-input` in CI and unattended processes. Use `--wrap-untrusted` when
5353
Google-hosted free text will be consumed by an LLM or another instruction-aware
5454
system.
5555

56+
Use `--readonly` (or `GOG_READONLY=1`) as a runtime backstop. It permits GET,
57+
HEAD, OPTIONS, and the small allowlist of Google APIs whose query operations use
58+
POST, while rejecting mutating API requests before network dispatch. This guard
59+
is independent of OAuth scopes and command names, propagates into MCP child
60+
processes, and also blocks Zoom meeting mutations. `gog auth add --readonly`
61+
continues to request read-only OAuth scopes where Google provides them.
62+
63+
```bash
64+
gog --readonly --account [email protected] gmail search 'newer_than:7d'
65+
gog --readonly --account [email protected] calendar freebusy [email protected]
66+
```
67+
5668
Interactive browser commands fail fast under `--no-input`. Preview
5769
`gog auth manage` with `--dry-run`; use `gog auth import` for unattended token
5870
installation.
@@ -73,6 +85,7 @@ Example:
7385
gog \
7486
--enable-commands-exact schema,gmail.search \
7587
--gmail-no-send \
88+
--readonly \
7689
--no-input \
7790
--wrap-untrusted \
7891
schema --json |
@@ -87,6 +100,7 @@ common_flags=(
87100
88101
--enable-commands-exact schema,gmail.search
89102
--gmail-no-send
103+
--readonly
90104
--no-input
91105
--wrap-untrusted
92106
)
@@ -97,6 +111,7 @@ gog "${common_flags[@]}" schema --json |
97111
.automation.safety.no_input and
98112
.automation.safety.wrap_untrusted and
99113
.automation.safety.gmail_no_send and
114+
.automation.safety.readonly and
100115
(.automation.safety.command_rules.enabled_exact | index("gmail.search"))
101116
'
102117

docs/commands/gog-admin-groups-list.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ gog admin groups list (ls) [flags]
3838
| `--no-input`<br>`--non-interactive`<br>`--noninteractive` | `bool` | | Never prompt; fail instead (useful for CI) |
3939
| `--page`<br>`--cursor` | `string` | | Page token |
4040
| `-p`<br>`--plain`<br>`--tsv` | `bool` | false | Output stable, parseable text to stdout (TSV; no colors) |
41+
| `--readonly` | `bool` | false | Block mutating API requests at runtime; auth add also requests read-only OAuth scopes |
4142
| `--results-only` | `bool` | | In JSON mode, emit only the primary result (drops envelope fields like nextPageToken) |
4243
| `--select`<br>`--pick`<br>`--project` | `string` | | In JSON mode, select comma-separated fields (best-effort; supports dot paths). Desire path: use --fields for most commands. |
4344
| `-v`<br>`--verbose` | `bool` | | Enable verbose logging |

docs/commands/gog-admin-groups-members-add.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ gog admin groups members add (invite) <groupEmail> <memberEmail> [flags]
3333
| `-j`<br>`--json`<br>`--machine` | `bool` | false | Output JSON to stdout (best for scripting) |
3434
| `--no-input`<br>`--non-interactive`<br>`--noninteractive` | `bool` | | Never prompt; fail instead (useful for CI) |
3535
| `-p`<br>`--plain`<br>`--tsv` | `bool` | false | Output stable, parseable text to stdout (TSV; no colors) |
36+
| `--readonly` | `bool` | false | Block mutating API requests at runtime; auth add also requests read-only OAuth scopes |
3637
| `--results-only` | `bool` | | In JSON mode, emit only the primary result (drops envelope fields like nextPageToken) |
3738
| `--role` | `string` | MEMBER | Member role (MEMBER, MANAGER, OWNER) |
3839
| `--select`<br>`--pick`<br>`--project` | `string` | | In JSON mode, select comma-separated fields (best-effort; supports dot paths). Desire path: use --fields for most commands. |

docs/commands/gog-admin-groups-members-list.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ gog admin groups members list (ls) <groupEmail> [flags]
3737
| `--no-input`<br>`--non-interactive`<br>`--noninteractive` | `bool` | | Never prompt; fail instead (useful for CI) |
3838
| `--page`<br>`--cursor` | `string` | | Page token |
3939
| `-p`<br>`--plain`<br>`--tsv` | `bool` | false | Output stable, parseable text to stdout (TSV; no colors) |
40+
| `--readonly` | `bool` | false | Block mutating API requests at runtime; auth add also requests read-only OAuth scopes |
4041
| `--results-only` | `bool` | | In JSON mode, emit only the primary result (drops envelope fields like nextPageToken) |
4142
| `--select`<br>`--pick`<br>`--project` | `string` | | In JSON mode, select comma-separated fields (best-effort; supports dot paths). Desire path: use --fields for most commands. |
4243
| `-v`<br>`--verbose` | `bool` | | Enable verbose logging |

docs/commands/gog-admin-groups-members-remove.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ gog admin groups members remove (rm,del,delete) <groupEmail> <memberEmail>
3333
| `-j`<br>`--json`<br>`--machine` | `bool` | false | Output JSON to stdout (best for scripting) |
3434
| `--no-input`<br>`--non-interactive`<br>`--noninteractive` | `bool` | | Never prompt; fail instead (useful for CI) |
3535
| `-p`<br>`--plain`<br>`--tsv` | `bool` | false | Output stable, parseable text to stdout (TSV; no colors) |
36+
| `--readonly` | `bool` | false | Block mutating API requests at runtime; auth add also requests read-only OAuth scopes |
3637
| `--results-only` | `bool` | | In JSON mode, emit only the primary result (drops envelope fields like nextPageToken) |
3738
| `--select`<br>`--pick`<br>`--project` | `string` | | In JSON mode, select comma-separated fields (best-effort; supports dot paths). Desire path: use --fields for most commands. |
3839
| `-v`<br>`--verbose` | `bool` | | Enable verbose logging |

docs/commands/gog-admin-groups-members.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ gog admin groups members <command>
3939
| `-j`<br>`--json`<br>`--machine` | `bool` | false | Output JSON to stdout (best for scripting) |
4040
| `--no-input`<br>`--non-interactive`<br>`--noninteractive` | `bool` | | Never prompt; fail instead (useful for CI) |
4141
| `-p`<br>`--plain`<br>`--tsv` | `bool` | false | Output stable, parseable text to stdout (TSV; no colors) |
42+
| `--readonly` | `bool` | false | Block mutating API requests at runtime; auth add also requests read-only OAuth scopes |
4243
| `--results-only` | `bool` | | In JSON mode, emit only the primary result (drops envelope fields like nextPageToken) |
4344
| `--select`<br>`--pick`<br>`--project` | `string` | | In JSON mode, select comma-separated fields (best-effort; supports dot paths). Desire path: use --fields for most commands. |
4445
| `-v`<br>`--verbose` | `bool` | | Enable verbose logging |

docs/commands/gog-admin-groups.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ gog admin groups <command>
3838
| `-j`<br>`--json`<br>`--machine` | `bool` | false | Output JSON to stdout (best for scripting) |
3939
| `--no-input`<br>`--non-interactive`<br>`--noninteractive` | `bool` | | Never prompt; fail instead (useful for CI) |
4040
| `-p`<br>`--plain`<br>`--tsv` | `bool` | false | Output stable, parseable text to stdout (TSV; no colors) |
41+
| `--readonly` | `bool` | false | Block mutating API requests at runtime; auth add also requests read-only OAuth scopes |
4142
| `--results-only` | `bool` | | In JSON mode, emit only the primary result (drops envelope fields like nextPageToken) |
4243
| `--select`<br>`--pick`<br>`--project` | `string` | | In JSON mode, select comma-separated fields (best-effort; supports dot paths). Desire path: use --fields for most commands. |
4344
| `-v`<br>`--verbose` | `bool` | | Enable verbose logging |

docs/commands/gog-admin-orgunits-create.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ gog admin orgunits (org-units,ou) create (add,new) <name> [flags]
3535
| `--no-input`<br>`--non-interactive`<br>`--noninteractive` | `bool` | | Never prompt; fail instead (useful for CI) |
3636
| `--parent` | `string` | / | Parent org unit path |
3737
| `-p`<br>`--plain`<br>`--tsv` | `bool` | false | Output stable, parseable text to stdout (TSV; no colors) |
38+
| `--readonly` | `bool` | false | Block mutating API requests at runtime; auth add also requests read-only OAuth scopes |
3839
| `--results-only` | `bool` | | In JSON mode, emit only the primary result (drops envelope fields like nextPageToken) |
3940
| `--select`<br>`--pick`<br>`--project` | `string` | | In JSON mode, select comma-separated fields (best-effort; supports dot paths). Desire path: use --fields for most commands. |
4041
| `-v`<br>`--verbose` | `bool` | | Enable verbose logging |

0 commit comments

Comments
 (0)