From 2c60aa238c8bd8c559fcc0801ebfaf403953cd49 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 12 Nov 2026 22:02:28 -0800 Subject: [PATCH 1/2] fix(credentials): offer keyless Google delegation only for services that can be delegated --- .../google-workload-identity.server.test.ts | 44 ++++++++++++------- .../google-workload-identity.server.ts | 36 ++++++++++----- 2 files changed, 54 insertions(+), 26 deletions(-) diff --git a/apps/sim/lib/credentials/google-workload-identity.server.test.ts b/apps/sim/lib/credentials/google-workload-identity.server.test.ts index 0de02046d10..ddb77f1a459 100644 --- a/apps/sim/lib/credentials/google-workload-identity.server.test.ts +++ b/apps/sim/lib/credentials/google-workload-identity.server.test.ts @@ -415,27 +415,41 @@ describe('getting a keyless Google Cloud token for a run', () => { expect(google.iam[0]?.body).toEqual({ scope: [BIGQUERY], lifetime: '3600s' }) }) - it('refuses domain-wide delegation before contacting Google', async () => { - fakeGoogle(fetch) + it.each([ + ['google-drive', 'google_keyless_delegation'], + ['google-bigquery', 'google_keyless_delegation_unavailable'], + ['google-ads', 'google_keyless_delegation_unavailable'], + ])( + 'refuses impersonation for %s before contacting Google, offering delegation only where it exists', + async (service, step) => { + fakeGoogle(fetch) - await expect( - resolve({ credentialId: 'cred-dwd', impersonateEmail: 'alice@example.com' }) - ).rejects.toMatchObject({ - code: 'unsupported_use', - logDetail: { step: 'google_keyless_delegation' }, - userMessage: expect.any(String), - }) - expect(fetch).not.toHaveBeenCalled() - }) + await expect( + resolve({ credentialId: 'cred-dwd', service, impersonateEmail: 'alice@example.com' }) + ).rejects.toMatchObject({ + code: 'unsupported_use', + logDetail: { step }, + userMessage: expect.any(String), + }) + expect(fetch).not.toHaveBeenCalled() + } + ) - it.each(['google-drive', 'gmail', 'google-sheets', 'google-ads', undefined])( - 'refuses %s, which keyless credentials do not serve, before contacting Google', - async (service) => { + it.each([ + ['google-drive', 'google_keyless_service'], + ['gmail', 'google_keyless_service'], + ['google-sheets', 'google_keyless_service'], + ['google-email', 'google_keyless_service'], + ['google-ads', 'google_keyless_service_unsupported'], + [undefined, 'google_keyless_service_unsupported'], + ])( + 'refuses %s, which keyless credentials do not serve, offering delegation only where it exists', + async (service, step) => { fakeGoogle(fetch) await expect(resolve({ credentialId: 'cred-workspace', service })).rejects.toMatchObject({ code: 'unsupported_use', - logDetail: { step: 'google_keyless_service' }, + logDetail: { step }, userMessage: expect.any(String), }) expect(fetch).not.toHaveBeenCalled() diff --git a/apps/sim/lib/credentials/google-workload-identity.server.ts b/apps/sim/lib/credentials/google-workload-identity.server.ts index 6d4a75d2957..dc3a35e1abd 100644 --- a/apps/sim/lib/credentials/google-workload-identity.server.ts +++ b/apps/sim/lib/credentials/google-workload-identity.server.ts @@ -930,7 +930,9 @@ export async function resolveGoogleWorkloadIdentityToken( input: ResolveGoogleWorkloadIdentityTokenInput ): Promise { const config = readStoredConfig(input.decrypted) - const delegation = googleDelegationServiceFor(input.service) ? config.delegation : undefined + // Reconnecting can add delegation only for the Workspace services keyless delegation covers. + const delegable = googleDelegationServiceFor(input.service) !== undefined + const delegation = delegable ? config.delegation : undefined if (delegation) { if (input.impersonateEmail && input.impersonateEmail.trim().toLowerCase() !== delegation.user) { @@ -942,18 +944,30 @@ export async function resolveGoogleWorkloadIdentityToken( } } else { if (input.impersonateEmail) { - throw unsupportedUse( - 'google_keyless_delegation', - 'this keyless Google credential has no domain-wide delegation for this service', - 'This keyless Google credential has no domain-wide delegation for this service, so it cannot act as a Workspace user. Clear the Impersonated Account, reconnect the credential to delegate this service, or use a JSON key credential instead' - ) + throw delegable + ? unsupportedUse( + 'google_keyless_delegation', + 'this keyless Google credential has no domain-wide delegation for this service', + 'This keyless Google credential has no domain-wide delegation for this service, so it cannot act as a Workspace user. Clear the Impersonated Account, reconnect the credential to delegate this service, or use a JSON key credential instead' + ) + : unsupportedUse( + 'google_keyless_delegation_unavailable', + 'keyless Google credentials cannot act as a Workspace user for this service', + 'A keyless Google credential cannot act as a Workspace user for this service. Clear the Impersonated Account, or use a JSON key credential instead' + ) } if (!input.service || !GOOGLE_KEYLESS_SERVICES.has(input.service)) { - throw unsupportedUse( - 'google_keyless_service', - 'this keyless Google credential works only with Google Cloud APIs and the Workspace services it was delegated', - 'This keyless Google credential works only with Google Cloud tools such as BigQuery and, through domain-wide delegation, with Gmail, Google Drive, Google Docs, Google Sheets, and Google Calendar. Reconnect it to delegate this service, or use a JSON key credential instead' - ) + throw delegable + ? unsupportedUse( + 'google_keyless_service', + 'this keyless Google credential works only with Google Cloud APIs and the Workspace services it was delegated', + 'This keyless Google credential works only with Google Cloud tools such as BigQuery and, through domain-wide delegation, with Gmail, Google Drive, Google Docs, Google Sheets, and Google Calendar. Reconnect it to delegate this service, or use a JSON key credential instead' + ) + : unsupportedUse( + 'google_keyless_service_unsupported', + 'keyless Google credentials cannot serve this service', + 'Keyless Google credentials work only with Google Cloud tools such as BigQuery and, through domain-wide delegation, with Gmail, Google Drive, Google Docs, Google Sheets, and Google Calendar. Use a JSON key credential for this service' + ) } } if (input.scopes.length === 0) { From 96868f83a7c857cf60f920ee61ba382b1bf0d0b8 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 12 Nov 2026 22:13:31 -0800 Subject: [PATCH 2/2] fix(credentials): send unsupported Google services straight to the JSON key advice --- .../google-workload-identity.server.test.ts | 2 +- .../google-workload-identity.server.ts | 29 ++++++++++--------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/apps/sim/lib/credentials/google-workload-identity.server.test.ts b/apps/sim/lib/credentials/google-workload-identity.server.test.ts index ddb77f1a459..07db28f217f 100644 --- a/apps/sim/lib/credentials/google-workload-identity.server.test.ts +++ b/apps/sim/lib/credentials/google-workload-identity.server.test.ts @@ -418,7 +418,7 @@ describe('getting a keyless Google Cloud token for a run', () => { it.each([ ['google-drive', 'google_keyless_delegation'], ['google-bigquery', 'google_keyless_delegation_unavailable'], - ['google-ads', 'google_keyless_delegation_unavailable'], + ['google-ads', 'google_keyless_service_unsupported'], ])( 'refuses impersonation for %s before contacting Google, offering delegation only where it exists', async (service, step) => { diff --git a/apps/sim/lib/credentials/google-workload-identity.server.ts b/apps/sim/lib/credentials/google-workload-identity.server.ts index dc3a35e1abd..52627cc8fc9 100644 --- a/apps/sim/lib/credentials/google-workload-identity.server.ts +++ b/apps/sim/lib/credentials/google-workload-identity.server.ts @@ -943,20 +943,23 @@ export async function resolveGoogleWorkloadIdentityToken( ) } } else { - if (input.impersonateEmail) { - throw delegable - ? unsupportedUse( - 'google_keyless_delegation', - 'this keyless Google credential has no domain-wide delegation for this service', - 'This keyless Google credential has no domain-wide delegation for this service, so it cannot act as a Workspace user. Clear the Impersonated Account, reconnect the credential to delegate this service, or use a JSON key credential instead' - ) - : unsupportedUse( - 'google_keyless_delegation_unavailable', - 'keyless Google credentials cannot act as a Workspace user for this service', - 'A keyless Google credential cannot act as a Workspace user for this service. Clear the Impersonated Account, or use a JSON key credential instead' - ) + const cloudService = Boolean(input.service && GOOGLE_KEYLESS_SERVICES.has(input.service)) + // Clearing the Impersonated Account helps only where keyless works without it. + if (input.impersonateEmail && delegable) { + throw unsupportedUse( + 'google_keyless_delegation', + 'this keyless Google credential has no domain-wide delegation for this service', + 'This keyless Google credential has no domain-wide delegation for this service, so it cannot act as a Workspace user. Clear the Impersonated Account, reconnect the credential to delegate this service, or use a JSON key credential instead' + ) + } + if (input.impersonateEmail && cloudService) { + throw unsupportedUse( + 'google_keyless_delegation_unavailable', + 'keyless Google credentials cannot act as a Workspace user for this service', + 'A keyless Google credential cannot act as a Workspace user for this service. Clear the Impersonated Account, or use a JSON key credential instead' + ) } - if (!input.service || !GOOGLE_KEYLESS_SERVICES.has(input.service)) { + if (!cloudService) { throw delegable ? unsupportedUse( 'google_keyless_service',