Skip to content

VNX-SEC-019: GCP service account key

Reports a committed Google Cloud service account key: a private_key PEM with a real body near type service_account, in JSON, escaped JSON, YAML, TOML or an object literal. Code that only reads or type-checks a key file, placeholder keys and test paths are not reported.

Updated 02 Nov 2026Checked against cli@de9b52a
Field Value
Rule ID VNX-SEC-019
Severity Critical
SARIF level error
Kind secrets
Languages Any file
CWE CWE-798
CAPEC CAPEC-191
ATT&CK T1552.001
CWSS vector CWSS:1.0/TI:C/AP:A/AL:A/IC:N/FC:LT/RP:N/RL:A/AV:I/AS:N/IN:A/SC:A/BI:C/DI:H/EX:H/EC:N/P:W
Tags secrets gcp google-cloud service-account
Source internal/sast/rules/vnx-sec-019.rego

Overview

This rule detects GCP service account key JSON files by matching the string "type": "service_account" in source files. Service account keys are downloaded JSON credentials files that authenticate a service account to Google Cloud APIs. They contain a private key, the service account email, and the project ID. Unlike GCP API keys (VNX-SEC-005), service account keys authenticate as an IAM identity with a defined set of IAM roles, often including sensitive permissions like roles/storage.admin, roles/cloudsql.client, or even roles/owner.

Why This Matters

Service account keys are among the most powerful credentials in GCP because they represent a machine identity with persistent, long-lived access. Unlike user credentials that expire or can be protected with 2FA, a service account key file works indefinitely until explicitly revoked. If a key with broad IAM permissions is leaked, an attacker can access all resources the service account is authorized to use, across any GCP project where the service account has been granted roles.

Google Cloud’s own best practices recommend against service account keys entirely for workloads that run on GCP, because Workload Identity Federation and attached service accounts provide the same functionality without any credential files. The Google Cloud Security Team considers service account key files to be the highest-priority credential to keep out of source control.

What Gets Flagged

// FLAGGED: GCP service account key JSON file committed to repository
{
  "type": "service_account",
  "project_id": "my-project-123456",
  "private_key_id": "abc123",
  "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBg<...real base64 body...>\n-----END PRIVATE KEY-----\n",
  "client_email": "my-service-account@my-project.iam.gserviceaccount.com",
  "client_id": "123456789",
  "auth_uri": "https://accounts.google.com/o/oauth2/auth",
  "token_uri": "https://oauth2.googleapis.com/token"
}
# FLAGGED: the same key in .streamlit/secrets.toml
[connections.gsheets]
type = "service_account"
private_key = "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBg<...real base64 body...>\n-----END PRIVATE KEY-----\n"

The same document is also flagged when it is embedded as an escaped string in an environment file, Terraform or source code, as a Python or JavaScript object literal, or as YAML (inline or a private_key: | block). Not flagged: a key file whose private_key is empty, "..." or ${VAR}, code that only reads a key file (from_service_account_file("key.json")) or compares against "service_account", and test, fixture, vendored and generated paths.

Remediation

  1. Revoke the service account key immediately. In the GCP Console go to IAM & Admin → Service Accounts → select the service account → Keys tab → Delete the exposed key. Or via CLI:
gcloud iam service-accounts keys delete KEY_ID \
  --iam-account=SERVICE_ACCOUNT_EMAIL
  1. Review GCP audit logs for API activity by the service account during the exposure window. In Cloud Console go to Logging → Log Explorer and filter by the service account email:
protoPayload.authenticationInfo.principalEmail="my-sa@my-project.iam.gserviceaccount.com"
  1. Eliminate the need for service account key files entirely using Workload Identity Federation. For GKE workloads:
# SAFE: GKE Workload Identity, no key file needed
apiVersion: v1
category: secrets
metadata:
  name: my-app
  namespace: default
  annotations:
    iam.gke.io/gcp-service-account: my-sa@my-project.iam.gserviceaccount.com

For Cloud Run, Cloud Functions, and Compute Engine, simply attach the service account to the resource, the Google metadata server provides credentials automatically with Application Default Credentials (ADC):

# SAFE: ADC on GCP-hosted workloads, no credentials file needed
from google.cloud import storage

# Automatically uses the attached service account via ADC
client = storage.Client()
  1. For workloads running outside GCP (GitHub Actions, on-premises), use Workload Identity Federation instead of key files:
# SAFE: GitHub Actions with Workload Identity Federation (no key file)
- name: Authenticate to GCP
  uses: google-github-actions/auth@v2
  with:
    workload_identity_provider: 'projects/123/locations/global/workloadIdentityPools/my-pool/providers/my-provider'
    service_account: 'my-sa@my-project.iam.gserviceaccount.com'
  1. If key files are absolutely required, store them in Secret Manager and inject at runtime, never commit them:
# SAFE: retrieve service account key from Secret Manager at runtime
import json
from google.cloud import secretmanager
from google.oauth2 import service_account

sm_client = secretmanager.SecretManagerServiceClient()
response = sm_client.access_secret_version(name='projects/my-project/secrets/sa-key/versions/latest')
key_data = json.loads(response.payload.data.decode('UTF-8'))

credentials = service_account.Credentials.from_service_account_info(key_data)
  1. Scan git history for the key file content:
gitleaks detect --source . --verbose
git filter-repo --path service-account-key.json --invert-paths

References

Suppressing this rule

Put a nosec comment on the line, or on line 1 to cover the whole file. Use the comment marker of the file’s language (#, //, -- or ;):

# nosec vnx-sec-019
// nosec vnx-sec-019

Or record an ignore rule: vulnetix ignore add --rule vnx-sec-019 --reason "...". See Suppressing findings.

Edit this pageSources: cli/internal/sast/rules/vnx-sec-019.rego, cli/internal/sast/docs/vnx-sec-019.md
esc
↑ ↓ move↵ openSearches in your browser. A key like autonomy.max or a code like threshold_required jumps straight to it.