GEMINI LABJP
FLASH35 — Gemini 3.5 Flash is now GA and powers gemini-flash-latest, making everyday generation faster and more affordableAGENTS — Managed Agents launch in public preview in the Gemini API, running in secure, isolated Google-hosted Linux sandboxesMEDIA — Nano Banana 2 Lite and Gemini Omni Flash bring faster image and high-quality video generation across AI Studio and the APITTS — Streaming speech generation is now supported for gemini-3.1-flash-tts-preview via streamGenerateContentTRANSLATE — A new audio model detects 70+ languages for live speech-to-speech translation while preserving natural intonationSPENDCAP — Project-level spend caps for billing have been added in Google AI Studio to keep costs under controlFLASH35 — Gemini 3.5 Flash is now GA and powers gemini-flash-latest, making everyday generation faster and more affordableAGENTS — Managed Agents launch in public preview in the Gemini API, running in secure, isolated Google-hosted Linux sandboxesMEDIA — Nano Banana 2 Lite and Gemini Omni Flash bring faster image and high-quality video generation across AI Studio and the APITTS — Streaming speech generation is now supported for gemini-3.1-flash-tts-preview via streamGenerateContentTRANSLATE — A new audio model detects 70+ languages for live speech-to-speech translation while preserving natural intonationSPENDCAP — Project-level spend caps for billing have been added in Google AI Studio to keep costs under control
Articles/Workspace
Workspace/2026-06-29Advanced

Keeping Apps Script + Gemini Automations on Least Privilege: Explicit Scopes and Catching Scope Creep

Apps Script automations that call Gemini quietly accumulate OAuth scopes. Here is how to declare explicit scopes in appsscript.json, catch scope creep in CI, and avoid forcing every user to re-consent.

Apps Script6Gemini API183OAuthSecurity4Google Workspace15

Premium Article

One morning I made a tiny edit to a Sheets automation that had been running quietly for about six months, redeployed it, and was met with a re-authorization screen. The line item read: "Read, compose, send, and permanently delete all your email from Gmail." All I had changed was appending one row to a sheet. I had not touched Gmail at all.

It asked for that permission anyway.

The reason was mundane. Months earlier I had called GmailApp once during a test, commented the line out, and forgotten to delete it. Apps Script statically scans your code and infers scopes from APIs that look used. A single line inside a comment was enough for it to request one of the broadest scopes available.

When you run several automations across Workspace as an indie developer, these auto-inferred scopes quietly swell over time. A script in production ends up holding read and write permissions it never actually needs. It is a dull but heavy liability: it widens the blast radius of any incident without you ever deciding to.

This article is about cutting that liability. Using a typical automation that spans Gmail, Sheets, and the Gemini API, we will declare the minimum scopes in appsscript.json, catch creep in CI, and avoid the re-consent accidents that scope changes cause.

Why auto-inferred scopes are dangerous

Apps Script has two ways to decide scopes. If you declare nothing, it infers them from your code. If you list them under oauthScopes in appsscript.json, inference stops and only the scopes you declared are requested.

Auto-inference is dangerous because three problems stack on top of each other.

ProblemWhat actually happens
It grabs oversized scopesA single GmailApp.search() pulls in "full read/write/delete of mail." You wanted read-only, but you now hold delete.
Dead code grants powerCalls left in comments or unreachable branches still feed inference. You request permissions you never exercise.
Change is invisibleNothing records who widened a permission or when. It never enters review, so creep goes unnoticed.

The principle of least privilege is that code holds only the permissions it needs right now. Auto-inference is fundamentally at odds with that.

The automation we will use

Let's work from a concrete setup, close to one I actually run:

  • Read unread mail under a specific Gmail label (never send, never delete)
  • Pass the body to the Gemini API to summarize and classify
  • Append the result to a single spreadsheet

The permissions this automation truly needs come down to three:

  1. Read Gmail, and nothing more (gmail.readonly)
  2. Read/write the one spreadsheet it is bound to (spreadsheets.currentonly)
  3. Outbound HTTP requests, to call the Gemini API (script.external_request)

No send permission. No Drive-wide permission. Left to inference, send and delete rights creep right in.

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
Step-by-step way to declare oauthScopes in appsscript.json and shut off the broad scopes Apps Script auto-assigns
A complete, copy-ready CI script that diffs declared scopes against an allowlist and fails on creep
How to roll out scope changes in stages so you never force every user into a surprise re-consent
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Gemini Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

Related Articles

Workspace2026-07-13
Retiring the Poll That Waits on an Overnight Batch — An Apps Script doPost Sink for Gemini Signals
Polling a Gemini batch or long-running operation every five minutes from an Apps Script time trigger quietly stacks up UrlFetch calls and latency. Receive the webhook in doPost, treat it as an unverified signal, then confirm authoritatively and apply idempotently.
Workspace2026-07-08
Your Apps Script Gemini Automation Fails Every Month-End — Budgeting Against the UrlFetch Daily Quota
Apps Script automations that call Gemini stall on the UrlFetch daily call quota — a separate ceiling from the 6-minute limit and trigger counts. Here is a daily budget governor with backlog carry-over that keeps the job running on busy days, with working code and a verified simulation.
Workspace2026-07-01
When Two Triggers Write at Once, Your Gemini Result Quietly Vanishes — A Durable Result Store for Apps Script
Storing Gemini results from several Apps Script triggers loses writes through read-modify-write races and PropertiesService size limits. Build a result store that survives, using LockService, a durable sink, and idempotency keys.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →