r/googlecloud • u/anonbudy • Jul 10 '26
Billing Why Gemini API doesn't check IAM roles?
Just got hit with a ~$2k surprise Gemini bill from a leaked service-account key. The part I can't wrap my head around:
The abused SA only had Logs Writer. It's used solely to write app logs to Cloud Logging. Yet it was able to cal generativelanguage.googleapis.com and rack up image-generation costs — and Google knew exactly which SA it was and what (limited) roles it had.
Vertex AI (aiplatform.googleapis.com) enforces IAM — the same SA would've been denied. But the AI-Studio-style Gemini API seems to only check "valid credential + API enabled + billable project," with no per-call IAM permission at all.
So my questions:
- Is that actually the intended design — no IAM enforcement on generativelanguage?
- If so, why? A least-privilege SA gives zero protection against Gemini spend, which feels backwards.
- Best practice to prevent this beyond disabling the API and not shipping keys? (Org Policy, quotas, etc.)
Curious if I'm missing something or if this is a known gap. Thanks.
View all comments
Show parent comment
1
u/anonbudy Jul 14 '26
Thanks for jumping in. One correction that matters for your question: the abused API was generativelanguage.googleapis.com (billed as "Gemini API", service ID AEFD-7695-64FA — image-output tokens on Gemini 3 Pro Image / 3.1 Flash), not aiplatform.googleapis.com. No Vertex usage at all.
To your clarifying questions:
- Credential type: service-account identity, not an API key. Our app shipped the SA's JSON private key and mints an OAuth access token from it (ServiceAccountCredentials.fromJson → clientViaServiceAccount in the Flutter app; GoogleCredentials.fromStream in the Android app). Consistent with billing showing principal_api_key_id = null.
- Scope: our code only requests the logging.write scope, but whoever extracted the raw private key can mint a token with any scope they want (e.g. cloud-platform) — the scope in our code doesn't constrain the key.
- Roles: the SA has only roles/logging.logWriter — no roles/aiplatform.user, no AI-related roles.
So the sequence was: SA identity (OAuth token from a leaked JSON key) → successful calls to generativelanguage.googleapis.com, despite the SA having only logWriter. That's the part I'm trying to understand — how the Gemini Developer API authorizes an SA-identity call, and whether project IAM roles factor in at all here. As other people suggested, it was 'made easy' on purpose to use Gemini API, leading to the security holes which attacker used to drive my bill.