Skip to content

Instantly share code, notes, and snippets.

@F30
Last active February 27, 2026 18:25
Show Gist options
  • Select an option

  • Save F30/9fd4d4cbcfe11c6aabe44e5cc9d8358d to your computer and use it in GitHub Desktop.

Select an option

Save F30/9fd4d4cbcfe11c6aabe44e5cc9d8358d to your computer and use it in GitHub Desktop.
Check for GCP API keys affected by the retroactive enablement of the Generative Language (Gemini) API. See https://trufflesecurity.com/blog/google-api-keys-werent-secrets-but-then-gemini-changed-the-rules for details. Use at your own discretion, provided 'as is' without any warranties or liability for potential issues.
#!/usr/bin/env python3
"""
Find GCP projects with the Generative Language (Gemini) API enabled and API keys that could access it
(unrestricted or explicitly allowed).
Checks for API keys affected by the issue described at:
https://trufflesecurity.com/blog/google-api-keys-werent-secrets-but-then-gemini-changed-the-rules
Requirements:
pip install google-cloud-resource-manager google-cloud-service-usage google-cloud-api-keys
Authentication:
Uses Application Default Credentials (ADC). For example:
gcloud auth application-default login
You will have to set a project to use for API quotas:
gcloud auth application-default set-quota-project <project-name>
"""
import argparse
import re
import sys
from google.api_core.exceptions import GoogleAPICallError, PermissionDenied
from google.cloud import api_keys_v2
from google.cloud import resourcemanager_v3
from google.cloud import service_usage_v1
TARGET_SERVICE = 'generativelanguage.googleapis.com'
SERVICE_AGENT_PROJECT_ID_PATTERN = re.compile(r'^sys-[a-z0-9-]+$')
def get_clients():
projects_client = resourcemanager_v3.ProjectsClient()
service_usage_client = service_usage_v1.ServiceUsageClient()
api_keys_client = api_keys_v2.ApiKeysClient()
return projects_client, service_usage_client, api_keys_client
def is_service_agent_project(project_id):
return bool(SERVICE_AGENT_PROJECT_ID_PATTERN.fullmatch(project_id))
def iter_accessible_projects(projects_client, include_service_agent_projects=False):
pager = projects_client.search_projects(
request=resourcemanager_v3.SearchProjectsRequest(query='state:ACTIVE')
)
for project in pager:
project_name = project.display_name or ''
project_id = project.project_id or ''
if not project_id or not project.name:
continue
if not include_service_agent_projects and is_service_agent_project(project_id):
continue
# project.name is in the form 'projects/{project_number}'
parts = project.name.split('/', maxsplit=1)
if len(parts) != 2:
continue
project_number = parts[1]
if not project_number:
continue
yield {
'project_id': project_id,
'project_name': project_name,
'project_number': project_number,
}
def collect_projects_matching_check(project_iterator, check_project, check_label='Checking'):
matching_projects = []
checked_count = 0
for project in project_iterator:
checked_count += 1
project_id = project['project_id']
status_line = f'[{checked_count}] {check_label} {project_id}... '
print(f'\r\033[2K{status_line}', end='', flush=True)
try:
if check_project(project):
matching_projects.append(project)
except (PermissionDenied, GoogleAPICallError) as error:
print(f'\r\033[2K{status_line}ERROR')
print(
f' API call failed for project {project_id}: {error}',
file=sys.stderr,
)
if checked_count:
print('\n')
return matching_projects
def find_projects_with_generative_language_enabled(
projects_client, service_usage_client, include_service_agent_projects=False
):
def has_generative_language_enabled(project):
project_number = project['project_number']
resource_name = f'projects/{project_number}/services/{TARGET_SERVICE}'
service = service_usage_client.get_service(
request=service_usage_v1.GetServiceRequest(name=resource_name)
)
return service.state == service_usage_v1.types.State.ENABLED
return collect_projects_matching_check(
iter_accessible_projects(projects_client, include_service_agent_projects),
has_generative_language_enabled,
check_label='Checking Generative Language API in',
)
def is_unrestricted_key(restrictions, api_targets):
return restrictions is None or not api_targets
def is_explicit_key(_restrictions, api_targets):
return bool(api_targets) and any(
getattr(target, 'service', None) == TARGET_SERVICE for target in api_targets
)
def find_projects_with_matching_api_keys(projects, api_keys_client, key_matches):
def has_matching_api_keys(project):
parent = f"projects/{project['project_number']}/locations/global"
matching_keys = []
pager = api_keys_client.list_keys(request=api_keys_v2.ListKeysRequest(parent=parent))
for key in pager:
restrictions = getattr(key, 'restrictions', None)
api_targets = list(restrictions.api_targets) if restrictions else []
if key_matches(restrictions, api_targets):
matching_keys.append(
{
'name': key.name,
'display_name': key.display_name or '',
}
)
if matching_keys:
project['matching_api_keys'] = matching_keys
return True
return False
return collect_projects_matching_check(
projects,
has_matching_api_keys,
check_label='Checking API keys in',
)
def print_projects_with_generative_language(projects):
if not projects:
print('No accessible projects found with Generative Language API enabled.')
return
print('Projects with Generative Language API enabled:')
for project in projects:
print(f"- {project['project_id']}\t{project['project_name']}")
def print_projects_with_matching_keys(projects, description):
if not projects:
print(f'No accessible projects found with Generative Language API enabled and {description}.')
return
print(f'Projects with Generative Language API enabled and {description}:')
for project in projects:
print(f"- {project['project_id']}\t{project['project_name']}")
for key in project['matching_api_keys']:
key_label = key['display_name'] or '(no display name)'
print(f" - {key_label}\t{key['name']}")
def main():
parser = argparse.ArgumentParser(
description='Find projects with Generative Language API enabled and matching API keys.'
)
parser.add_argument(
'--include-service-agent-projects',
action='store_true',
help='Include Service Agent (Shadow/P4SA) projects in the initial project scan.',
)
args = parser.parse_args()
try:
projects_client, service_usage_client, api_keys_client = get_clients()
except (PermissionDenied, GoogleAPICallError) as error:
print(f'Failed to initialize GCP clients: {error}', file=sys.stderr)
sys.exit(1)
projects_with_generative_language = find_projects_with_generative_language_enabled(
projects_client,
service_usage_client,
include_service_agent_projects=args.include_service_agent_projects,
)
print_projects_with_generative_language(projects_with_generative_language)
print()
projects_with_unrestricted_keys = find_projects_with_matching_api_keys(
projects_with_generative_language,
api_keys_client,
key_matches=is_unrestricted_key,
)
print_projects_with_matching_keys(projects_with_unrestricted_keys, 'unrestricted API keys')
print()
projects_with_explicit_keys = find_projects_with_matching_api_keys(
projects_with_generative_language,
api_keys_client,
key_matches=is_explicit_key,
)
print_projects_with_matching_keys(projects_with_explicit_keys,
'API keys explicitly allowing the Generative Language API')
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment