fix: document all environment variables and enable CONFIG_DIR override (#222)

* docs: document all environment variables and enable CONFIG_DIR override (#171)

* docs: clarify env vars are trusted inputs in AGENTS.md

* chore: add Gemini Code Assist style guide

---------

Co-authored-by: jpoehnelt-bot <jpoehnelt-bot@users.noreply.github.com>
This commit is contained in:
Justin Poehnelt
2026-03-05 17:09:01 -07:00
committed by GitHub
parent 6daf90d331
commit 322529d8a9
7 changed files with 135 additions and 19 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"@googleworkspace/cli": patch
---
Document all environment variables and enable GOOGLE_WORKSPACE_CLI_CONFIG_DIR in release builds
+29 -12
View File
@@ -1,18 +1,35 @@
# OAuth Client Credentials
# Create these at https://console.cloud.google.com/apis/credentials
GOOGLE_WORKSPACE_CLI_CLIENT_ID=
GOOGLE_WORKSPACE_CLI_CLIENT_SECRET=
# gws — Google Workspace CLI
# Copy this file to .env and uncomment the variables you need.
# All variables are optional. See README.md for details.
# Authentication
# Path to a service account JSON key file or user credentials
# ── Authentication ────────────────────────────────────────────────
# Pre-obtained OAuth2 access token (highest priority; bypasses all credential loading)
# GOOGLE_WORKSPACE_CLI_TOKEN=
# Path to OAuth credentials JSON (user or service account)
# GOOGLE_WORKSPACE_CLI_CREDENTIALS_FILE=
# Impersonation (Domain-Wide Delegation)
# Email address of the user to impersonate when using a service account
# Default account email for multi-account usage (overridden by --account flag)
# GOOGLE_WORKSPACE_CLI_ACCOUNT=
# Email of user to impersonate via Domain-Wide Delegation (service accounts only)
# GOOGLE_WORKSPACE_CLI_IMPERSONATED_USER=
# Model Armor Sanitization
# Default template resource name for --sanitize
# GOOGLE_WORKSPACE_CLI_SANITIZE_TEMPLATE=projects/my-project/locations/us-central1/templates/my-template
# Sanitization mode: 'warn' (default) or 'block'
# ── OAuth Client ──────────────────────────────────────────────────
# OAuth client ID and secret (alternative to saving client_secret.json)
# GOOGLE_WORKSPACE_CLI_CLIENT_ID=
# GOOGLE_WORKSPACE_CLI_CLIENT_SECRET=
# ── Configuration ─────────────────────────────────────────────────
# Override the config directory (default: ~/.config/gws)
# GOOGLE_WORKSPACE_CLI_CONFIG_DIR=
# ── Model Armor (response sanitization) ──────────────────────────
# Default Model Armor template (overridden by --sanitize flag)
# GOOGLE_WORKSPACE_CLI_SANITIZE_TEMPLATE=
# Sanitization mode: warn (default) or block
# GOOGLE_WORKSPACE_CLI_SANITIZE_MODE=warn
# ── Helpers ───────────────────────────────────────────────────────
# GCP project ID fallback for gmail watch and events subscribe (overridden by --project)
# GOOGLE_WORKSPACE_PROJECT_ID=
+26
View File
@@ -0,0 +1,26 @@
# Code Review Style Guide
## Project Architecture
`gws` is a Rust CLI that dynamically generates commands from Google Discovery Documents at runtime. It does NOT use generated Rust crates (`google-drive3`, etc.) for API interaction. Do not suggest adding API-specific crates to `Cargo.toml`.
## Security: Trusted vs Untrusted Inputs
This CLI is frequently invoked by AI/LLM agents. CLI arguments may be adversarial.
- **CLI arguments (untrusted)** — Must validate paths against traversal (`../../`), reject control characters, percent-encode URL path segments, and use `reqwest .query()` for query parameters. Validators: `validate_safe_output_dir()`, `validate_safe_dir_path()`, `encode_path_segment()`, `validate_resource_name()`.
- **Environment variables (trusted)** — Set by the user in their shell profile, `.env` file, or deployment config. Do NOT flag missing path validation on environment variable values. This is consistent with `XDG_CONFIG_HOME`, `CARGO_HOME`, etc.
## Test Coverage
The `codecov/patch` check requires new/modified lines to be covered by tests. Prefer extracting testable helper functions over embedding logic in `main`/`run`. Tests should cover both happy paths and rejection paths (e.g., pass `../../.ssh` and assert `Err`).
## Changesets
Every PR must include a `.changeset/<name>.md` file. Use `patch` for fixes/chores, `minor` for features, `major` for breaking changes.
## Code Style
- Rust: `cargo clippy -- -D warnings` must pass. `cargo fmt` enforced via pre-commit hook.
- Node.js: Use `pnpm` not `npm`.
- OAuth scope strings in test code will trigger "restricted/sensitive scope" warnings — these are expected and should be ignored.
+40 -4
View File
@@ -84,6 +84,9 @@ ASCII art title cards live in `art/`. The `scripts/show-art.sh` helper clears th
> [!IMPORTANT]
> This CLI is frequently invoked by AI/LLM agents. Always assume inputs can be adversarial — validate paths against traversal (`../../.ssh`), restrict format strings to allowlists, reject control characters, and encode user values before embedding them in URLs.
> [!NOTE]
> **Environment variables are trusted inputs.** The validation rules above apply to **CLI arguments** that may be passed by untrusted AI agents. Environment variables (e.g. `GOOGLE_WORKSPACE_CLI_CONFIG_DIR`) are set by the user themselves — in their shell profile, `.env` file, or deployment config — and are not subject to path traversal validation. This is consistent with standard conventions like `XDG_CONFIG_HOME`, `CARGO_HOME`, etc.
### Path Safety (`src/validate.rs`)
When adding new helpers or CLI flags that accept file paths, **always validate** using the shared helpers:
@@ -165,7 +168,40 @@ Use these labels to categorize pull requests and issues:
## Environment Variables
- `GOOGLE_WORKSPACE_CLI_TOKEN` — Pre-obtained OAuth2 access token (highest priority; bypasses all credential file loading)
- `GOOGLE_WORKSPACE_CLI_CREDENTIALS_FILE` — Path to OAuth credentials JSON (no default; if unset, falls back to credentials secured by the OS Keyring and encrypted in `~/.config/gws/`)
- `GOOGLE_WORKSPACE_CLI_ACCOUNT` — Default account email for multi-account usage (overridden by `--account` flag)
- Supports `.env` files via `dotenvy`
### Authentication
| Variable | Description |
|---|---|
| `GOOGLE_WORKSPACE_CLI_TOKEN` | Pre-obtained OAuth2 access token (highest priority; bypasses all credential file loading) |
| `GOOGLE_WORKSPACE_CLI_CREDENTIALS_FILE` | Path to OAuth credentials JSON (no default; if unset, falls back to credentials secured by the OS Keyring and encrypted in `~/.config/gws/`) |
| `GOOGLE_WORKSPACE_CLI_ACCOUNT` | Default account email for multi-account usage (overridden by `--account` flag) |
| `GOOGLE_WORKSPACE_CLI_IMPERSONATED_USER` | Email of user to impersonate with Domain-Wide Delegation (service accounts only) |
| `GOOGLE_APPLICATION_CREDENTIALS` | Standard Google ADC path; used as fallback when no gws-specific credentials are configured |
### Configuration
| Variable | Description |
|---|---|
| `GOOGLE_WORKSPACE_CLI_CONFIG_DIR` | Override the config directory (default: `~/.config/gws`) |
### OAuth Client
| Variable | Description |
|---|---|
| `GOOGLE_WORKSPACE_CLI_CLIENT_ID` | OAuth client ID (for `gws auth login` when no `client_secret.json` is saved) |
| `GOOGLE_WORKSPACE_CLI_CLIENT_SECRET` | OAuth client secret (paired with `CLIENT_ID` above) |
### Sanitization (Model Armor)
| Variable | Description |
|---|---|
| `GOOGLE_WORKSPACE_CLI_SANITIZE_TEMPLATE` | Default Model Armor template (overridden by `--sanitize` flag) |
| `GOOGLE_WORKSPACE_CLI_SANITIZE_MODE` | `warn` (default) or `block` |
### Helpers
| Variable | Description |
|---|---|
| `GOOGLE_WORKSPACE_PROJECT_ID` | GCP project ID fallback for `gmail watch` and `events subscribe` helpers (overridden by `--project` flag) |
All variables can also live in a `.env` file (loaded via `dotenvy`).
+20
View File
@@ -33,6 +33,7 @@ npm install -g @googleworkspace/cli
- [AI Agent Skills](#ai-agent-skills)
- [MCP Server](#mcp-server)
- [Advanced Usage](#advanced-usage)
- [Environment Variables](#environment-variables)
- [Architecture](#architecture)
- [Troubleshooting](#troubleshooting)
- [Development](#development)
@@ -356,6 +357,25 @@ gws gmail users messages get --params '...' \
| `GOOGLE_WORKSPACE_CLI_SANITIZE_TEMPLATE` | Default Model Armor template |
| `GOOGLE_WORKSPACE_CLI_SANITIZE_MODE` | `warn` (default) or `block` |
## Environment Variables
All variables are optional. See [`.env.example`](.env.example) for a copy-paste template.
| Variable | Description |
|---|---|
| `GOOGLE_WORKSPACE_CLI_TOKEN` | Pre-obtained OAuth2 access token (highest priority) |
| `GOOGLE_WORKSPACE_CLI_CREDENTIALS_FILE` | Path to OAuth credentials JSON (user or service account) |
| `GOOGLE_WORKSPACE_CLI_ACCOUNT` | Default account email (overridden by `--account` flag) |
| `GOOGLE_WORKSPACE_CLI_IMPERSONATED_USER` | Email for Domain-Wide Delegation (service accounts) |
| `GOOGLE_WORKSPACE_CLI_CLIENT_ID` | OAuth client ID (alternative to `client_secret.json`) |
| `GOOGLE_WORKSPACE_CLI_CLIENT_SECRET` | OAuth client secret (paired with `CLIENT_ID`) |
| `GOOGLE_WORKSPACE_CLI_CONFIG_DIR` | Override config directory (default: `~/.config/gws`) |
| `GOOGLE_WORKSPACE_CLI_SANITIZE_TEMPLATE` | Default Model Armor template |
| `GOOGLE_WORKSPACE_CLI_SANITIZE_MODE` | `warn` (default) or `block` |
| `GOOGLE_WORKSPACE_PROJECT_ID` | GCP project ID fallback for helper commands |
Environment variables can also be set in a `.env` file (loaded via [dotenvy](https://crates.io/crates/dotenvy)).
## Architecture
`gws` uses a **two-phase parsing** strategy:
+2 -3
View File
@@ -92,7 +92,6 @@ const READONLY_SCOPES: &[&str] = &[
];
pub fn config_dir() -> PathBuf {
#[cfg(test)]
if let Ok(dir) = std::env::var("GOOGLE_WORKSPACE_CLI_CONFIG_DIR") {
return PathBuf::from(dir);
}
@@ -266,7 +265,7 @@ async fn handle_login(args: &[String]) -> Result<(), GwsError> {
}
// Determine scopes: explicit flags > interactive TUI > defaults
let mut scopes = resolve_scopes(
let scopes = resolve_scopes(
&filtered_args,
project_id.as_deref(),
services_filter.as_ref(),
@@ -277,7 +276,7 @@ async fn handle_login(args: &[String]) -> Result<(), GwsError> {
// gmail.metadata blocks query parameters like `q`, and is redundant
// when broader scopes (gmail.modify, gmail.readonly, mail.google.com)
// are already included.
let scopes = filter_redundant_restrictive_scopes(scopes);
let mut scopes = filter_redundant_restrictive_scopes(scopes);
let secret = yup_oauth2::ApplicationSecret {
client_id: client_id.clone(),
+13
View File
@@ -454,6 +454,19 @@ fn print_usage() {
println!(
" GOOGLE_WORKSPACE_CLI_ACCOUNT Default account email for multi-account"
);
println!(
" GOOGLE_WORKSPACE_CLI_IMPERSONATED_USER Email for Domain-Wide Delegation (service accounts)"
);
println!(
" GOOGLE_WORKSPACE_CLI_CONFIG_DIR Override config directory (default: ~/.config/gws)"
);
println!(" GOOGLE_WORKSPACE_CLI_SANITIZE_TEMPLATE Default Model Armor template");
println!(
" GOOGLE_WORKSPACE_CLI_SANITIZE_MODE Sanitization mode: warn (default) or block"
);
println!(
" GOOGLE_WORKSPACE_PROJECT_ID GCP project ID fallback for helper commands"
);
println!();
println!("COMMUNITY:");
println!(" Star the repo: https://github.com/googleworkspace/cli");