Security model
How the server enforces access control and handles credentials.
Defense in depth
Every request passes through multiple checks before reaching Google:
- Origin validation -- HTTP origins validated by exact hostname comparison
- Rate limiting -- Per-client-IP sliding window, plus per-service rate limits
- Body size check -- Configurable max request size
- Service allow-list -- Only listed services exposed
- Method denylist -- Blocked methods return an error (suffix matching:
"messages.send"matches"users.messages.send") - Read-only check -- Non-GET methods blocked when enabled
- Label policy -- Gmail messages restricted to allowed labels
- Parameter constraints -- Values validated against allowed lists with per-value access levels
- Generic tool bypass prevention -- Services with helpers block the generic tool
- Request explanation -- Write operations include a plain-English description of what they do
A denied request never reaches Google.
Policy engine
The policy engine evaluates every tools/call against the JSON policy file. Key behaviors:
- Deny by default -- Services not listed are blocked
- Generic constraints -- Any parameter can be constrained with an allowlist of values and per-value access levels (read-only or read-write)
- Body constraints -- On reads, inject query filters to restrict results. On writes, require values from the allowed list.
- Parent ancestry -- Drive folder constraints check parent folders recursively. A file in a subfolder of an allowed folder inherits access.
- Denied methods suffix matching --
"messages.send"in the denylist matches the full API method"users.messages.send". This prevents bypass through fully qualified method names. - Gmail label policy -- The
allowed_labelsfield restricts which labels the agent can access. Search queries are filtered and message access is validated against the allowed list. - Helper tool enforcement -- Services with helpers (Drive, Docs, Sheets, Slides, Gmail) suppress the generic tool. Attempting to call the generic tool returns an error directing the model to the helpers.
- Actionable errors -- Every denial includes a
Fix:hint with the exact JSON to add to the policy file - Origin parsing -- URLs are parsed and hostnames compared exactly (no substring matching)
- CRLF prevention -- Media content types are validated to reject CR/LF characters
See Policy reference for all configuration options.
Request explanation
Every write operation includes an _explanation field in structuredContent that describes the operation in plain English. This lets agents show the user what happened:
Create drive/files.create (POST): name="report.pdf", in folder folder-xyz
Delete drive/files.delete (DELETE): fileId=abc123
Create calendar/events.insert (POST): summary="Team standup", on calendar "primary"
Audit log
The --audit-log flag writes a JSONL file recording every API call with timestamps, services, methods, tool names, status, and duration. Denied requests include the denial reason. See Operations guide for details.
Credential chain
The server checks 7 sources in priority order:
| # | Source |
|---|---|
| 1 | GOOGLE_WORKSPACE_CLI_TOKEN env var |
| 2 | credentials_file in policy JSON |
| 3 | GOOGLE_WORKSPACE_CLI_CREDENTIALS_FILE env var |
| 4 | ~/.config/gws/credentials.json |
| 5 | gws auth export --unmasked (OS keyring) |
| 6 | GOOGLE_APPLICATION_CREDENTIALS env var |
| 7 | ~/.config/gcloud/application_default_credentials.json |
Token caching
Once a token is obtained, it is cached in memory for 3,500 seconds (just under the 3,600-second OAuth2 lifetime). Subsequent requests reuse the cached token.
Security properties
- Hard-fail on auth errors (no silent unauthenticated requests)
gws auth exportoutput is never written to disk or logged- Container image is
FROM scratchwith no shell or package manager - Live policy reload via SIGHUP keeps the current policy on failure