Skip to content

Instantly share code, notes, and snippets.

@warrenseine
Last active October 2, 2026 08:41
Show Gist options
  • Select an option

  • Save warrenseine/70e9d9725091be4d532b4c45e6fb9ba8 to your computer and use it in GitHub Desktop.

Select an option

Save warrenseine/70e9d9725091be4d532b4c45e6fb9ba8 to your computer and use it in GitHub Desktop.
Approve gcloud sign-in
// ==UserScript==
// @name Approve gcloud sign-in
// @namespace http://tampermonkey.net/
// @version 2026-10-02
// @description Click through the Google passkey and consent screens for gcloud and application-default credentials
// @author Warren Seine
// @match https://accounts.google.com/signin/oauth/*
// @match https://accounts.google.com/v3/signin/challenge/pk/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=cloud.google.com
// @run-at document-idle
// @grant none
// ==/UserScript==
(function() {
'use strict';
// Only the Google-owned clients that `gcloud auth login` and
// `gcloud auth application-default login` sign in with. Any other app keeps
// its consent screen: approving one blindly is how consent phishing works.
const TRUSTED_CLIENTS = new Set([
// Google Cloud SDK
'32555940559.apps.googleusercontent.com',
// Google Auth Library (application-default credentials)
'764086051850-6qr4p6gpi6hn506pt8ejuq83di341hur.apps.googleusercontent.com',
]);
// "Allow" grants the listed scopes; "Continue" confirms a client that already
// holds some of them, or starts the passkey prompt.
const APPROVE = new Set(['Allow', 'Continue']);
// The consent page renders its buttons after load. Give up rather than click
// on a page that has been sitting open.
const TIMEOUT = 15000;
// No client in the URL means the page cannot be attributed, so nothing is
// clicked.
function clientId() {
const params = new URLSearchParams(document.location.search);
if (params.has('client_id')) return params.get('client_id');
// Google sometimes asks for a passkey before the consent screen. That page
// names no client itself, but carries the consent URL as its continue link.
try {
const next = new URL(params.get('continue'));
if (next.origin === 'https://accounts.google.com' && next.pathname.startsWith('/signin/oauth/')) {
return next.searchParams.get('client_id');
}
} catch (error) {
/* no continue link */
}
return null;
}
if (!TRUSTED_CLIENTS.has(clientId())) return;
const since = Date.now();
function approve() {
const button = [...document.querySelectorAll('button')]
.find(b => APPROVE.has(b.textContent.trim()));
if (button) {
clearInterval(poll);
button.click();
} else if (Date.now() - since > TIMEOUT) {
clearInterval(poll);
}
}
const poll = setInterval(approve, 300);
approve();
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment