Skip to content

Google Workspace

nodyn connects to Google Workspace (Gmail, Sheets, Drive, Calendar, Docs) via OAuth 2.0. No third-party packages required — uses native fetch() against Google REST APIs.

  1. Go to Google Cloud Console
  2. Create a new project (or use an existing one)
  3. Enable the following APIs:
    • Gmail API
    • Google Sheets API
    • Google Drive API
    • Google Calendar API
    • Google Docs API
  1. Go to APIs & Services > Credentials
  2. Click Create Credentials > OAuth client ID
  3. Application type: Desktop app (for CLI) or Web application (if using a proxy)
  4. Note the Client ID and Client Secret
  1. Go to APIs & Services > OAuth consent screen
  2. User type: External (or Internal for Google Workspace domains)
  3. Add scopes:
    • https://www.googleapis.com/auth/gmail.readonly
    • https://www.googleapis.com/auth/gmail.send
    • https://www.googleapis.com/auth/gmail.modify
    • https://www.googleapis.com/auth/spreadsheets
    • https://www.googleapis.com/auth/drive
    • https://www.googleapis.com/auth/calendar.events
    • https://www.googleapis.com/auth/documents
  4. Add test users (required while app is in “Testing” status)

Option A: Environment variables

Terminal window
export GOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.com
export GOOGLE_CLIENT_SECRET=your-client-secret

Option B: Config file (~/.nodyn/config.json)

{
"google_client_id": "your-client-id.apps.googleusercontent.com",
"google_client_secret": "your-client-secret"
}

Option C: Setup wizard

Terminal window
nodyn init
# Follow prompts — Google section is step 5
/google auth

This starts a localhost redirect OAuth flow:

  1. nodyn opens a temporary local server and launches the Google consent screen in your browser
  2. Grant the requested permissions
  3. Google redirects back to localhost with the auth code
  4. nodyn exchanges the code for tokens and stores them encrypted in the vault (NODYN_VAULT_KEY required)

Tokens auto-refresh. Run /google status to check connection.

For headless deployments (Docker, background tasks), use a service account instead of OAuth:

  1. Go to IAM & Admin > Service Accounts in GCP Console
  2. Create a service account
  3. Download the JSON key file
  4. Set the environment variable:
Terminal window
export GOOGLE_SERVICE_ACCOUNT_KEY=/path/to/service-account.json

Key file requirements:

  • Must be an absolute path (no relative paths)
  • File permissions must be 0600 or 0400 (warns if too loose)
  • Must contain valid JSON with type: "service_account", project_id, private_key, client_email

Service accounts don’t require interactive authentication but need domain-wide delegation for accessing user data in Google Workspace domains.

CommandDescription
/google authStart OAuth device flow
/google statusShow connection status, scopes, token expiry
/google disconnectRevoke tokens and remove stored credentials

5 tools are registered when Google credentials are configured:

  • google_gmail — Email: search, read, send, reply, draft, archive, labels
  • google_sheets — Spreadsheets: read, write, append, create, list, format
  • google_drive — Files: search, read, upload, create docs, list, move, share
  • google_calendar — Events: list, create, update, delete, free/busy
  • google_docs — Documents: read, create, append, find & replace

See tools.md for full action reference.

Default OAuth scopes are read-only for security. Write scopes are opt-in:

  • READ_ONLY_SCOPES (default): Gmail read, Sheets read, Drive read, Calendar read, Docs read
  • WRITE_SCOPES (opt-in): Gmail send/modify, Sheets write, Drive write, Calendar events, Docs write

To enable write scopes, set google_oauth_scopes in config:

{
"google_oauth_scopes": [
"https://www.googleapis.com/auth/gmail.readonly",
"https://www.googleapis.com/auth/gmail.send",
"https://www.googleapis.com/auth/spreadsheets"
]
}

Alternatively, the agent can request additional scopes at runtime via requestScope(), which triggers re-authentication with the new scope set.

  • Read-only actions (search, read, list) work immediately after auth
  • Write actions (send, write, upload, create, delete) require user confirmation in interactive mode and the corresponding write scope
  • Scope escalation: If write scope is missing, the tool returns an error with instructions to re-auth. Use requestScope() to upgrade incrementally
  • OAuth tokens encrypted in SecretVault (GOOGLE_OAUTH_TOKENS key, requires NODYN_VAULT_KEY)
  • Tokens auto-refresh via refresh token — no re-authentication needed
  • /google disconnect revokes tokens at Google and deletes vault entry
  • Service account key files validated: absolute path, 0o600/0o400 permissions, valid JSON structure
  • OAuth tokens (ya29.*) and JWTs masked in debug output via maskTokenPatterns()
  • No credentials are ever logged, stored in memory, or sent to external services
  • Gmail blocks sending emails that contain detected secrets (API keys, bearer tokens)

Google Workspace data is external and attacker-controlled (anyone can send you an email, share a doc, or create a calendar invite). All read results are defended against indirect prompt injection:

  1. Tool-level: All read handlers wrap external content via wrapUntrustedData() — marks data as untrusted before the LLM sees it. Boundary-escape tags (</untrusted_data>) in content are neutralized
  2. Agent-level: All 5 Google tools are in EXTERNAL_TOOLS — every response is scanned via scanToolResult() for 17 injection patterns (12 categories) (tool invocation, instruction overrides, ChatML/Llama tokens, role impersonation, exfiltration, boundary escape)
  3. Behavioral: ToolCallTracker detects suspicious tool call sequences: Google read → email send (data exfiltration), Google read → HTTP POST (data exfiltration), Google read → sensitive file read (credential harvesting)

stripHtml() removes content that could hide injection payloads:

  • HTML comments (<!-- hidden instructions -->)
  • CDATA sections (<![CDATA[...]]>)
  • Hidden elements (display:none, visibility:hidden, opacity:0)
  • Script and style blocks (including their content)
  • All write actions require interactive user confirmation (fail-safe: blocked if no prompt available)
  • All write actions hard-blocked in autonomous mode via permission-guard.ts
  • Default OAuth scopes are read-only — write scopes require explicit opt-in