GCP Vertex AI Hardening

Overview

This page covers the Vertex AI managed model API — Gemini models served via aiplatform.googleapis.com, the RAG Engine (grounding/retrieval), and Model Garden curated model catalogue. Not in scope: Vertex AI training pipelines, AutoML, or custom container training jobs; those surfaces carry their own IAM and build-pipeline controls addressed in compute and CI/CD hardening guides.

For the underlying threat model and cross-cutting principles that apply to all managed LLM API services, see General GenAI Hardening. Key infrastructure prerequisites are covered on GCP sibling pages: gcp-iam-08 — VPC Service Controls perimeter setup (applied to aiplatform.googleapis.com in gcp-genai-02 below) and gcp-iam-02 — service account key avoidance (the key management foundation for gcp-genai-01).

Controls are ordered severity-descending: one CRITICAL control (service account scoping) appears first, followed by four HIGH controls (VPC Service Controls, safety filters, audit logs, RAG source auth), then three MEDIUM controls (CMEK, data residency, Model Garden access). Equivalence links to AWS Bedrock, Azure OpenAI, and OCI Generative AI controls are HTML comments during authoring and will be made live in the Wave 4 seal (Phase 14 Plan 14-05).

gcp-genai-01-service-account-scoping ! CRITICAL PREVENTIVE

Create a dedicated, minimally-scoped service account for Vertex AI workloads. Do not use the default Compute Engine service account (PROJECT_NUMBER-compute@developer.gserviceaccount.com), which typically holds roles/editor at the project level — a blast-radius equivalent to running as root. Grant only roles/aiplatform.user (or a custom role with exactly the Vertex AI permissions the workload requires) to the Vertex AI service account. For GKE-based workloads, use Workload Identity Federation to avoid long-lived downloadable SA key files entirely. See gcp-iam-02 — no SA keys for the key avoidance pattern. Unit 42 April 2026 research on Vertex AI Agent Engine identity risks found that over-privileged service accounts are the primary lateral-movement path from a compromised Vertex AI workload to full GCP project takeover.

Remediation — gcloud CLI

# gcloud — audit Vertex AI service accounts and their granted roles

# Find service accounts named or tagged for Vertex AI usage
gcloud iam service-accounts list \
  --filter="displayName:vertex" \
  --format="value(email)"

# Audit all roles bound to a specific Vertex AI SA at the project level
SA_EMAIL="vertex-ai-workload@${PROJECT_ID}.iam.gserviceaccount.com"
gcloud projects get-iam-policy "${PROJECT_ID}" \
  --flatten="bindings" \
  --filter="bindings.members:${SA_EMAIL}" \
  --format="table(bindings.role)"

# Flag any binding to roles/editor or roles/owner — these must be removed
# Acceptable: roles/aiplatform.user or a custom role scoped to Vertex AI actions

Remediation — Terraform

# Terraform Google provider ~> 5.0
resource "google_service_account" "vertex_ai_workload" {
  project      = var.project_id
  account_id   = "vertex-ai-workload"
  display_name = "Vertex AI Workload SA — minimally scoped"
  description  = "Dedicated SA for Vertex AI inference workloads. Granted roles/aiplatform.user only."
}

# Bind only roles/aiplatform.user — NOT roles/aiplatform.admin or roles/editor
resource "google_project_iam_member" "vertex_ai_user" {
  project = var.project_id
  role    = "roles/aiplatform.user"
  member  = "serviceAccount:${google_service_account.vertex_ai_workload.email}"
}

# Do NOT grant roles/editor, roles/owner, or roles/aiplatform.admin.
# For GKE workloads, use Workload Identity Federation instead of a key file.

Remediation — Config Connector

apiVersion: iam.cnrm.cloud.google.com/v1beta1
kind: IAMServiceAccount
metadata:
  name: vertex-inference-sa
  namespace: config-control
spec:
  displayName: "Vertex AI inference (scoped)"
  description: "Single-purpose SA for Vertex AI Online Prediction"

Remediation — Pulumi (TypeScript)

import * as gcp from "@pulumi/gcp";

// Single-purpose service account for Vertex AI inference.
const vertexSa = new gcp.serviceaccount.Account("vertex-inference-sa", {
    accountId: "vertex-inference-sa",
    displayName: "Vertex AI inference (scoped)",
});

// Bind only the minimum Vertex AI runtime role.
const vertexUserBinding = new gcp.projects.IAMMember("vertex-sa-user", {
    project: projectId,
    role: "roles/aiplatform.user",
    member: vertexSa.email.apply(e => `serviceAccount:${e}`),
});

Compliance mapping

CIS AWS Foundations v3.0.0 CIS Microsoft Azure Foundations v3.0.0 CIS GCP Foundation v4.0.0 CIS OCI Foundation v2.0.0 NIST SP 800-53 rev5 ISO/IEC 27001:2022 ISO/IEC 27017:2015 OWASP LLM Top 10:2025 NIST AI 600-1 (Jul 2024) EU AI Act (2024/1689)
n/a (no dedicated CIS GenAI benchmark) n/a (no dedicated CIS GenAI benchmark) n/a (no dedicated CIS GenAI benchmark) n/a (no dedicated CIS GenAI benchmark) AC-2; AC-6; IA-4 A.5.15; A.5.18 n/a LLM06:2025; LLM08:2025 Information Security Art. 55 (in force 2025-08-02)

Log signals

  • Cloud Audit Logs on aiplatform.googleapis.com for SetIamPolicy on Vertex AI endpoints or models granting roles/aiplatform.user to broadly scoped principals (e.g. group:all-engineers@).
  • Endpoint-binding events where a Vertex AI service account's IAM-policy is widened to include downstream services (Cloud Storage, BigQuery) the model should not reach.
  • Cross-project impersonation: iamcredentials events generating tokens for a Vertex SA from a principal outside the documented MLOps allow-list.

Query

logName=~"projects/.*/logs/cloudaudit.googleapis.com%2Factivity"
          AND protoPayload.serviceName="aiplatform.googleapis.com"
          AND protoPayload.methodName="SetIamPolicy"
          AND protoPayload.serviceData.policyDelta.bindingDeltas.action="ADD"
          AND (protoPayload.serviceData.policyDelta.bindingDeltas.role="roles/aiplatform.user"
               OR protoPayload.serviceData.policyDelta.bindingDeltas.role="roles/aiplatform.admin")

Pin this Cloud Logging filter to a Cloud Monitoring log-based metric grouped by endpoint name; pair with a Cloud Asset Inventory query enumerating Vertex AI endpoint resources to maintain a live access-graph map.

Alert threshold

  • Page on any new aiplatform.user/admin binding outside the documented MLOps consumer allow-list.
  • Page on cross-project impersonation against a Vertex AI service account from a principal not on the documented impersonator list.

Initial response

  1. Revoke the unauthorised binding via gcloud ai endpoints remove-iam-policy-binding; rotate any tokens issued under the cross-project impersonation path.
  2. Audit Vertex AI prediction request logs for the bound principal during the window; treat any prediction whose input contained sensitive data as candidate-leaked under the broadened role.
  3. Pin endpoint IAM in Terraform; gate edits through a CI workflow that checks against the documented consumer allow-list TSV.

References

gcp-genai-02-vpc-service-controls ! HIGH PREVENTIVE

Add aiplatform.googleapis.com to a VPC Service Controls (VPC-SC) perimeter to prevent data exfiltration from Vertex AI across project boundaries. VPC-SC wraps the Vertex AI API so that requests originating from outside the defined perimeter — another GCP project, an external IP, or an identity not included in the access policy — are denied at the API gateway layer, before any inference occurs. This is the GCP equivalent of combining AWS VPC endpoints with Service Control Policies for Bedrock. See gcp-iam-08 — VPC Service Controls perimeter setup for the base perimeter configuration pattern; this control adds aiplatform.googleapis.com to the restricted services list.

Remediation — gcloud CLI

# gcloud — create VPC Service Controls perimeter for Vertex AI
# Prerequisite: an Access Context Manager access policy must already exist for the org
gcloud access-context-manager perimeters create "vertex-ai-perimeter" \
  --title="Vertex AI Perimeter" \
  --resources="projects/${PROJECT_NUMBER}" \
  --restricted-services=aiplatform.googleapis.com \
  --policy="${ACCESS_POLICY_NAME}"

# Verify the perimeter was created and aiplatform.googleapis.com is listed
gcloud access-context-manager perimeters describe "vertex-ai-perimeter" \
  --policy="${ACCESS_POLICY_NAME}" \
  --format="json(status.restrictedServices)"

Remediation — Terraform

# Terraform Google provider ~> 5.0
resource "google_access_context_manager_access_policy" "org_policy" {
  parent = "organizations/${var.org_id}"
  title  = "Vertex AI org access policy"
}

resource "google_access_context_manager_service_perimeter" "vertex_ai" {
  parent = "accessPolicies/${google_access_context_manager_access_policy.org_policy.name}"
  name   = "accessPolicies/${google_access_context_manager_access_policy.org_policy.name}/servicePerimeters/vertexAiPerimeter"
  title  = "Vertex AI VPC-SC Perimeter"

  status {
    restricted_services = ["aiplatform.googleapis.com"]
    resources           = ["projects/${var.project_number}"]
  }
}

Remediation — Config Connector

apiVersion: accesscontextmanager.cnrm.cloud.google.com/v1beta1
kind: AccessContextManagerServicePerimeter
metadata:
  name: vertex-ai-perimeter
  namespace: config-control
spec:
  parent: "accessPolicies/POLICY_ID"
  title: "Vertex AI perimeter"
  status:
    resources:
    - "projects/PROJECT_NUMBER"
    restrictedServices:
    - "aiplatform.googleapis.com"
    - "storage.googleapis.com"

Compliance mapping

CIS AWS Foundations v3.0.0 CIS Microsoft Azure Foundations v3.0.0 CIS GCP Foundation v4.0.0 CIS OCI Foundation v2.0.0 NIST SP 800-53 rev5 ISO/IEC 27001:2022 ISO/IEC 27017:2015 OWASP LLM Top 10:2025 NIST AI 600-1 (Jul 2024) EU AI Act (2024/1689)
n/a (no dedicated CIS GenAI benchmark) n/a (no dedicated CIS GenAI benchmark) n/a (no dedicated CIS GenAI benchmark) n/a (no dedicated CIS GenAI benchmark) SC-7; AC-4 A.8.20; A.8.22 CLD.13.1.4 LLM10:2025 Information Security Art. 55 (in force 2025-08-02)

Log signals

  • Cloud Audit Logs on accesscontextmanager.googleapis.com ServicePerimeter.patch where aiplatform.googleapis.com is removed from restrictedServices.
  • Egress-policy rule additions admitting Vertex AI traffic to external projects: new egressPolicies with egressTo.identityType=ANY_IDENTITY and the Vertex service in operations.serviceName.
  • VPC-SC violation drops: a sudden disappearance of Vertex AI denied-request entries after a perimeter edit is a higher-fidelity signal than the patch event alone.

Query

logName=~"organizations/.*/logs/cloudaudit.googleapis.com%2Factivity"
          AND protoPayload.serviceName="accesscontextmanager.googleapis.com"
          AND protoPayload.methodName=~".*ServicePerimeter.*"
          AND (protoPayload.request.servicePerimeter.spec.restrictedServices="aiplatform.googleapis.com"
               OR protoPayload.request.servicePerimeter.spec.egressPolicies.egressTo.operations.serviceName="aiplatform.googleapis.com")

This Cloud Logging filter watches perimeter mutations; pair with a saved query for VPC_SC_DENIED entries with servicename="aiplatform.googleapis.com" so the denial-rate timeseries surfaces silent perimeter erosion alongside the explicit patch events.

Alert threshold

  • Page on any perimeter patch that removes Vertex AI from restrictedServices or adds an egress-policy admitting external identities to Vertex AI operations.
  • Page on VPC-SC Vertex-AI denial rate dropping below 10% of rolling baseline for more than 60 minutes.

Initial response

  1. Restore the perimeter via gcloud access-context-manager perimeters update from the captured baseline; suspend any egress rule introduced in the gap window.
  2. Audit Vertex AI prediction request logs for cross-project or cross-org callers during the relaxation window; treat any successful prediction from outside the perimeter's allowed access levels as candidate data-exfil through model inference.
  3. Pin perimeter spec in Terraform; require dry-run mode for any production perimeter edit, and enforce a 48h dry-run hold before useExplicitDryRunSpec flips to false.

References

gcp-genai-03-safety-filters ! HIGH PREVENTIVE

Configure Gemini safety settings with BLOCK_MEDIUM_AND_ABOVE (or stricter) for all four harm categories: HARM_CATEGORY_HATE_SPEECH, HARM_CATEGORY_DANGEROUS_CONTENT, HARM_CATEGORY_HARASSMENT, and HARM_CATEGORY_SEXUALLY_EXPLICIT. Setting any category to BLOCK_NONE disables the safety filter entirely for that category — this is the BLOCK_NONE anti-pattern from general/genai.html#common-misconfigurations.

Critical infrastructure note — no Terraform resource: Gemini safety settings are configured at inference-time via the API request body (safety_settings parameter in GenerateContentRequest). No Terraform resource exists for this configuration. The Google Cloud provider (hashicorp/google ~> 5.0) does not include a google_vertex_ai_safety_filter resource — that resource name does not exist in the provider registry (confirmed 2026-05-24). Configure safety settings in your application code using the Vertex AI SDK or REST API. The related google_model_armor_* resources cover the separate Model Armor service, not Gemini in-line safety filters.

Remediation — Vertex AI Python SDK

# Vertex AI Python SDK — configure safety settings at inference time
# Install: pip install google-cloud-aiplatform>=1.38.0
import vertexai
from vertexai.generative_models import (
    GenerativeModel,
    SafetySetting,
    HarmCategory,
    HarmBlockThreshold,
)

vertexai.init(project=PROJECT_ID, location=REGION)

safety_settings = [
    SafetySetting(
        category=HarmCategory.HARM_CATEGORY_HATE_SPEECH,
        threshold=HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE,
    ),
    SafetySetting(
        category=HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
        threshold=HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE,
    ),
    SafetySetting(
        category=HarmCategory.HARM_CATEGORY_HARASSMENT,
        threshold=HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE,
    ),
    SafetySetting(
        category=HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT,
        threshold=HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE,
    ),
]

model = GenerativeModel("gemini-2.0-flash")
response = model.generate_content(
    "user prompt here",
    safety_settings=safety_settings,
)

Setting threshold=HarmBlockThreshold.BLOCK_NONE for any harm category disables safety filtering for that category entirely. This is equivalent to the BLOCK_NONE anti-pattern documented in general/genai.html#common-misconfigurations. Do not use BLOCK_NONE in production environments. Any deviation from BLOCK_MEDIUM_AND_ABOVE or stricter requires documented risk acceptance and an approved exception in your security register.

Remediation — Infrastructure Manager

Compliance mapping

CIS AWS Foundations v3.0.0 CIS Microsoft Azure Foundations v3.0.0 CIS GCP Foundation v4.0.0 CIS OCI Foundation v2.0.0 NIST SP 800-53 rev5 ISO/IEC 27001:2022 ISO/IEC 27017:2015 OWASP LLM Top 10:2025 NIST AI 600-1 (Jul 2024) EU AI Act (2024/1689)
n/a (no dedicated CIS GenAI benchmark) n/a (no dedicated CIS GenAI benchmark) n/a (no dedicated CIS GenAI benchmark) n/a (no dedicated CIS GenAI benchmark) SI-15 A.8.28 n/a LLM01:2025; LLM02:2025 Dangerous/Violent Content Art. 55 (in force 2025-08-02)

Log signals

  • Cloud Audit Logs on aiplatform.googleapis.com for prediction requests where protoPayload.request.safetySettings.threshold is set to BLOCK_NONE or BLOCK_ONLY_HIGH when baseline policy requires BLOCK_LOW_AND_ABOVE.
  • Per-endpoint generationConfig defaults moving toward laxer safety thresholds on shared endpoints.
  • Safety-flagged prediction rate drops: a sustained decrease in safetyRatings[].blocked=true entries while overall prediction volume is constant.

Query

logName=~"projects/.*/logs/cloudaudit.googleapis.com%2Fdata_access"
          AND protoPayload.serviceName="aiplatform.googleapis.com"
          AND protoPayload.methodName=~".*Endpoint.predict"
          AND protoPayload.request.safetySettings.threshold="BLOCK_NONE"

Stream this Cloud Logging filter (data-access tier — DATA_READ audit must be enabled on Vertex AI) into a log-based metric grouped by endpoint; pair with a saved query computing the rolling blocked-ratio so threshold drift surfaces alongside per-request overrides.

Alert threshold

  • Page on any prediction request whose safety threshold is BLOCK_NONE outside the documented red-team endpoint allow-list.
  • Surface (do not page) blocked-ratio drops below 50% of rolling baseline; confirm with the workload owner whether prompt distribution changed.

Initial response

  1. Identify the calling principal via protoPayload.authenticationInfo.principalEmail; if the call is unsanctioned, revoke the principal's aiplatform.user binding immediately.
  2. Sample the prediction responses generated during the threshold-relaxed window; quarantine any output that exhibits unsafe content for downstream-system audit and customer-notification review.
  3. Pin baseline safety-threshold defaults in the Vertex AI API client wrapper used by application code; reject requests that downgrade thresholds via a Cloud Functions admission hook on the API path.

References

gcp-genai-05-audit-logs-data-access ! HIGH DETECTIVE

Enable Cloud Audit Logs Data Access logging for aiplatform.googleapis.com. Data Access audit logs for aiplatform.googleapis.com are disabled by default and must be explicitly enabled via IAM Audit Configuration at the project or organisation level. Without this configuration, Vertex AI API calls — model inferences, endpoint invocations, dataset operations, RAG corpus queries — are not captured in Cloud Audit Logs. This means there is zero visibility into who called which model, when, from which identity, and with what parameters. Both DATA_READ and DATA_WRITE log types should be enabled; DATA_READ covers inference calls (reading model outputs) and DATA_WRITE covers resource mutations (creating endpoints, updating datasets).

Enabling Data Access audit logs incurs Cloud Logging ingestion costs proportional to Vertex AI API call volume. Configure log-based metrics or log exclusions to scope retention and manage cost. Route audit logs to Cloud Storage for long-term retention beyond the 30-day default Cloud Logging retention period to satisfy compliance frameworks that require 90-day or 1-year audit log retention.

Remediation — gcloud CLI

# gcloud — enable Data Access audit logs for aiplatform.googleapis.com
# DATA ACCESS LOGS ARE DISABLED BY DEFAULT — this enablement is the hardening action

# Step 1: Export current IAM policy to a local file
gcloud projects get-iam-policy "${PROJECT_ID}" \
  --format=json > /tmp/policy.json

# Step 2: Add the following auditLogConfigs block to policy.json under "auditConfigs":
# {
#   "service": "aiplatform.googleapis.com",
#   "auditLogConfigs": [
#     { "logType": "DATA_READ" },
#     { "logType": "DATA_WRITE" }
#   ]
# }

# Step 3: Apply the updated policy
gcloud projects set-iam-policy "${PROJECT_ID}" /tmp/policy.json

# Verify the audit config was applied
gcloud projects get-iam-policy "${PROJECT_ID}" \
  --format="json(auditConfigs)" | \
  python3 -c "import json,sys; configs=json.load(sys.stdin); \
    [print(c) for c in configs.get('auditConfigs',[]) if c.get('service')=='aiplatform.googleapis.com']"

Remediation — Terraform

# Terraform Google provider ~> 5.0
resource "google_project_iam_audit_config" "vertex_ai_data_access" {
  project = var.project_id
  service = "aiplatform.googleapis.com"

  audit_log_config {
    log_type = "DATA_READ"
  }

  audit_log_config {
    log_type = "DATA_WRITE"
  }
}

# Optional: route audit logs to Cloud Storage for long-term retention
resource "google_logging_project_sink" "vertex_ai_audit_sink" {
  name                   = "vertex-ai-audit-logs-sink"
  project                = var.project_id
  destination            = "storage.googleapis.com/${var.audit_log_bucket}"
  filter                 = "logName:\"projects/${var.project_id}/logs/cloudaudit.googleapis.com\" AND resource.type=\"audited_resource\" AND protoPayload.serviceName=\"aiplatform.googleapis.com\""
  unique_writer_identity = true
}

Remediation — Infrastructure Manager

Compliance mapping

CIS AWS Foundations v3.0.0 CIS Microsoft Azure Foundations v3.0.0 CIS GCP Foundation v4.0.0 CIS OCI Foundation v2.0.0 NIST SP 800-53 rev5 ISO/IEC 27001:2022 ISO/IEC 27017:2015 OWASP LLM Top 10:2025 NIST AI 600-1 (Jul 2024) EU AI Act (2024/1689)
n/a (no dedicated CIS GenAI benchmark) n/a (no dedicated CIS GenAI benchmark) n/a (no dedicated CIS GenAI benchmark) n/a (no dedicated CIS GenAI benchmark) AU-2; AU-12; SI-4 A.8.15; A.8.16 CLD.12.4.5 LLM10:2025 Information Security Art. 55 (in force 2025-08-02)

Log signals

  • Cloud Audit Logs SetIamPolicy events on the audit-log config removing aiplatform.googleapis.com from the DATA_READ logging scope.
  • Project-level exempted-members additions for the Vertex AI service identity — silently suppresses caller-attribution on all prediction calls.
  • Data-access log volume drops on aiplatform.googleapis.com against rolling baseline while prediction QPS metrics remain steady.

Query

logName=~"projects/.*/logs/cloudaudit.googleapis.com%2Factivity"
          AND protoPayload.methodName="SetIamPolicy"
          AND protoPayload.serviceData.policyDelta.auditConfigDeltas.service="aiplatform.googleapis.com"
          AND protoPayload.serviceData.policyDelta.auditConfigDeltas.action="REMOVE"

This Cloud Logging filter watches the audit-config delta plane; pair with a log-based metric counting Vertex AI DATA_READ entry rate so the volume-side regression surfaces independently of the SetIamPolicy event.

Alert threshold

  • Page on any audit-config removal of DATA_READ or DATA_WRITE for Vertex AI.
  • Page on Vertex-AI data-access log volume dropping below 30% of rolling baseline for more than 60 minutes while prediction QPS is unchanged.

Initial response

  1. Restore the audit-log config via gcloud projects set-iam-policy from the captured baseline JSON; verify the next prediction request surfaces in the DATA_READ stream.
  2. Audit prediction-attribution data during the gap window from any parallel telemetry (Cloud Run access logs, application-side logging) since the DATA_READ stream is incomplete.
  3. Pin audit-log config in Terraform with logType=DATA_READ for aiplatform.googleapis.com explicitly listed; the constraint is set-and-forget and removal warrants change-management.

References

gcp-genai-07-rag-grounding-source-auth ! HIGH PREVENTIVE

Secure Vertex AI RAG Engine grounding sources by enforcing IAM scoping and provenance validation on every corpus. Three specific controls are required: (1) IAM corpus restriction — only the designated Vertex AI service account should hold roles/aiplatform.user on the corpus resource; binding allUsers or allAuthenticatedUsers to corpus-level IAM is forbidden; (2) per-source document metadata filtering — configure retrieval filters to prevent embedding lookup returning documents outside the authorised scope for the calling identity; (3) source document provenance validation — validate that GCS buckets and BigQuery tables used as RAG sources are owned by the expected project and not writable by unauthenticated identities before ingestion. Each grounding corpus should use a dedicated, corpus-scoped Vertex AI service account with the minimum permissions needed for retrieval operations.

Remediation — gcloud CLI

# gcloud — enumerate Vertex AI endpoints and audit corpus IAM bindings

# List all Vertex AI endpoints in the region
gcloud ai index-endpoints list \
  --region="${REGION}" \
  --format="table(name, displayName, publicEndpointDomainName)"

# Audit all Vertex AI IAM bindings at the project level
gcloud projects get-iam-policy "${PROJECT_ID}" \
  --flatten="bindings" \
  --filter="bindings.role:roles/aiplatform" \
  --format="table(bindings.role, bindings.members)"

# Check GCS source bucket IAM — ensure no allUsers or allAuthenticatedUsers bindings
gsutil iam get "gs://${RAG_SOURCE_BUCKET}" | \
  python3 -c "import json,sys; policy=json.load(sys.stdin); \
    [print('WARNING: public binding found:', b) for b in policy.get('bindings',[]) \
     if 'allUsers' in b.get('members',[]) or 'allAuthenticatedUsers' in b.get('members',[])]"

Remediation — Terraform

# Terraform Google provider ~> 5.0
resource "google_service_account" "rag_corpus_sa" {
  project      = var.project_id
  account_id   = "vertex-rag-corpus"
  display_name = "Vertex AI RAG Corpus SA — corpus-scoped access only"
}

# Grant corpus SA access to Vertex AI for retrieval operations
resource "google_project_iam_member" "rag_corpus_vertex_user" {
  project = var.project_id
  role    = "roles/aiplatform.user"
  member  = "serviceAccount:${google_service_account.rag_corpus_sa.email}"
}

# Restrict GCS source bucket to corpus SA only — no allUsers, no allAuthenticatedUsers
resource "google_storage_bucket_iam_member" "rag_source_bucket_access" {
  bucket = var.rag_source_bucket
  role   = "roles/storage.objectViewer"
  member = "serviceAccount:${google_service_account.rag_corpus_sa.email}"
}

# Vertex AI index and endpoint for the RAG corpus
resource "google_vertex_ai_index" "rag_corpus_index" {
  project      = var.project_id
  region       = var.region
  display_name = "rag-corpus-index"
  description  = "RAG grounding corpus index — corpus-scoped SA access only"

  metadata {
    contents_delta_uri = "gs://${var.rag_source_bucket}/embeddings/"
    config {
      dimensions                  = 768
      approximate_neighbors_count = 150
    }
  }
}

resource "google_vertex_ai_index_endpoint" "rag_endpoint" {
  project      = var.project_id
  region       = var.region
  display_name = "rag-corpus-endpoint"
  network      = var.vpc_network
}

Remediation — Config Connector

apiVersion: iam.cnrm.cloud.google.com/v1beta1
kind: IAMPolicyMember
metadata:
  name: rag-bucket-reader
  namespace: config-control
spec:
  resourceRef:
    apiVersion: storage.cnrm.cloud.google.com/v1beta1
    kind: StorageBucket
    name: rag-grounding-corpus
  role: roles/storage.objectViewer
  member: "serviceAccount:vertex-inference-sa@PROJECT_ID.iam.gserviceaccount.com"

Compliance mapping

CIS AWS Foundations v3.0.0 CIS Microsoft Azure Foundations v3.0.0 CIS GCP Foundation v4.0.0 CIS OCI Foundation v2.0.0 NIST SP 800-53 rev5 ISO/IEC 27001:2022 ISO/IEC 27017:2015 OWASP LLM Top 10:2025 NIST AI 600-1 (Jul 2024) EU AI Act (2024/1689)
n/a (no dedicated CIS GenAI benchmark) n/a (no dedicated CIS GenAI benchmark) n/a (no dedicated CIS GenAI benchmark) n/a (no dedicated CIS GenAI benchmark) SI-10; AC-3; AC-4 A.8.28; A.5.15 CLD.6.3.1 LLM01:2025; LLM03:2025 Information Integrity Art. 55 (in force 2025-08-02)

Log signals

  • Cloud Audit Logs on discoveryengine.googleapis.com for DataStore.delete or IAM mutations on the RAG data-store grounding source.
  • Vertex AI Agent Builder Engine.update events changing the grounding-source path to an unauthenticated source (public URL list).
  • Cloud Storage source-bucket IAM mutations adding roles/storage.objectViewer for allUsers on the bucket backing the RAG corpus.

Query

logName=~"projects/.*/logs/cloudaudit.googleapis.com%2Factivity"
          AND ((protoPayload.serviceName="discoveryengine.googleapis.com"
                AND protoPayload.methodName=~".*DataStore.(delete|setIamPolicy)")
               OR (protoPayload.serviceName="storage.googleapis.com"
                   AND protoPayload.methodName="storage.setIamPermissions"
                   AND protoPayload.resourceName=~".*rag-corpus.*"
                   AND protoPayload.serviceData.policyDelta.bindingDeltas.member="allUsers"))

This Cloud Logging filter joins two surfaces — the Agent Builder data-store plane and the Cloud Storage bucket plane — so source-authenticity drift surfaces regardless of which path the attacker chose.

Alert threshold

  • Page on any DataStore delete or IAM mutation widening read access on the RAG grounding source.
  • Page on any Engine config update changing the grounding source to a non-authenticated path.

Initial response

  1. Restore the DataStore and source-bucket IAM from the captured Terraform state; re-index the corpus from the authenticated source.
  2. Audit Agent Builder grounding-request logs during the gap window; flag any agent response that cited the contaminated source for re-grounding and human review.
  3. Pin DataStore IAM + Engine grounding-source config in Terraform; reject deploys where the grounding source is a non-private URL via a CI policy check.

References

gcp-genai-04-cmek ! MEDIUM PREVENTIVE

Configure Customer-Managed Encryption Keys (CMEK) via Cloud KMS for Vertex AI model artifacts, training datasets, and tuned model storage on Vertex AI Endpoints. CMEK enables full encryption key lifecycle control: key rotation on schedule, immediate revocation (by disabling or destroying the key version), and enforcement that all data at rest is encrypted under a key managed in your own Cloud KMS key ring rather than a Google-managed key. CMEK is configured via encryption_spec.kms_key_name on Vertex AI endpoint and dataset resources. Note that not all Vertex AI resource types support CMEK in all regions — verify support for your specific resource types and regions at configuration time in the Vertex AI CMEK documentation.

Remediation — gcloud CLI

# gcloud — check CMEK configuration on existing Vertex AI endpoints
gcloud ai endpoints list \
  --region="${REGION}" \
  --format="json(name, displayName, encryptionSpec)"

# Create a new endpoint with CMEK
gcloud ai endpoints create \
  --region="${REGION}" \
  --display-name="cmek-protected-endpoint" \
  --kms-key-name="projects/${PROJECT_ID}/locations/${REGION}/keyRings/${KEY_RING}/cryptoKeys/${KEY_NAME}"

Remediation — Terraform

# Terraform Google provider ~> 5.0
resource "google_kms_key_ring" "vertex_ai_keyring" {
  project  = var.project_id
  name     = "vertex-ai-keyring"
  location = var.region
}

resource "google_kms_crypto_key" "vertex_ai_key" {
  name     = "vertex-ai-cmek"
  key_ring = google_kms_key_ring.vertex_ai_keyring.id

  rotation_period = "7776000s" # 90 days

  lifecycle {
    prevent_destroy = true
  }
}

# Grant Vertex AI service agent access to the KMS key
resource "google_kms_crypto_key_iam_member" "vertex_ai_kms_access" {
  crypto_key_id = google_kms_crypto_key.vertex_ai_key.id
  role          = "roles/cloudkms.cryptoKeyEncrypterDecrypter"
  member        = "serviceAccount:service-${var.project_number}@gcp-sa-aiplatform.iam.gserviceaccount.com"
}

resource "google_vertex_ai_endpoint" "cmek_endpoint" {
  project      = var.project_id
  region       = var.region
  display_name = "cmek-protected-endpoint"

  encryption_spec {
    kms_key_name = google_kms_crypto_key.vertex_ai_key.id
  }

  depends_on = [google_kms_crypto_key_iam_member.vertex_ai_kms_access]
}

Remediation — Config Connector

apiVersion: kms.cnrm.cloud.google.com/v1beta1
kind: KMSCryptoKey
metadata:
  name: vertex-cmek
  namespace: config-control
spec:
  keyRingRef:
    name: vertex-kr
  purpose: ENCRYPT_DECRYPT
  rotationPeriod: "7776000s"
  versionTemplate:
    algorithm: GOOGLE_SYMMETRIC_ENCRYPTION
    protectionLevel: SOFTWARE

Compliance mapping

CIS AWS Foundations v3.0.0 CIS Microsoft Azure Foundations v3.0.0 CIS GCP Foundation v4.0.0 CIS OCI Foundation v2.0.0 NIST SP 800-53 rev5 ISO/IEC 27001:2022 ISO/IEC 27017:2015 OWASP LLM Top 10:2025 NIST AI 600-1 (Jul 2024) EU AI Act (2024/1689)
n/a (no dedicated CIS GenAI benchmark) n/a (no dedicated CIS GenAI benchmark) n/a (no dedicated CIS GenAI benchmark) n/a (no dedicated CIS GenAI benchmark) SC-28; IA-5 A.8.24; A.8.10 n/a n/a Data Privacy Art. 55 (in force 2025-08-02)

Log signals

  • Cloud Audit Logs on aiplatform.googleapis.com for Dataset.update, Model.update, or Endpoint.update where encryptionSpec.kmsKeyName is cleared on resources tagged for CMEK-required workloads.
  • Vertex AI training-pipeline creates omitting encryptionSpec when the project baseline requires CMEK on training artefacts.
  • KMS key destroy events on the Vertex-AI-bound key — produces a hard read-failure on every existing CMEK-protected Vertex resource.

Query

logName=~"projects/.*/logs/cloudaudit.googleapis.com%2Factivity"
          AND protoPayload.serviceName="aiplatform.googleapis.com"
          AND protoPayload.methodName=~".*(Dataset|Model|Endpoint|TrainingPipeline).(create|update)"
          AND NOT protoPayload.request.encryptionSpec.kmsKeyName=~".*"

This Cloud Logging filter catches CMEK drift at resource-create time; pair with a Cloud Asset Inventory query computing the CMEK assignment for every Vertex AI resource type so steady-state coverage is visible.

Alert threshold

  • Page on any CMEK-required Vertex resource created or updated without encryptionSpec.kmsKeyName set.
  • Page on KMS-key destroy for any key bound to active Vertex AI resources; the destroy is reversible within 24 hours.

Initial response

  1. Migrate the affected Vertex resource to a CMEK-encrypted replacement via re-creation with --kms-key-name; export the training artefacts to a CMEK-protected Cloud Storage bucket and re-train if necessary.
  2. Re-bind roles/cloudkms.cryptoKeyEncrypterDecrypter on the KMS key for the Vertex AI service identity; verify the next Vertex-AI control-plane call against the key succeeds.
  3. Pin Vertex AI resource manifests in Terraform with encryption_spec blocks set; reject CI deploys that omit them on CMEK-tagged projects.

References

gcp-genai-06-data-residency ! MEDIUM PREVENTIVE

Enforce data residency for Vertex AI resources via the constraints/gcp.resourceLocations organization policy constraint. This constraint restricts which GCP regions Vertex AI endpoints, datasets, and models can be created in, preventing developers from provisioning Vertex AI resources in unapproved regions where data localisation requirements (GDPR, national data sovereignty laws) cannot be guaranteed. Combine with constraints/gcp.restrictServiceUsage to prevent users from enabling the Vertex AI API in projects outside the approved region boundary. For the general organisation policy pattern, see gcp/iam.html.

Remediation — gcloud CLI

# gcloud — check and enforce resource location constraints for Vertex AI

# Inspect the current resourceLocations policy at the project level
gcloud resource-manager org-policies describe constraints/gcp.resourceLocations \
  --project="${PROJECT_ID}"

# List all org policies at the project level
gcloud resource-manager org-policies list \
  --project="${PROJECT_ID}"

# Set the resourceLocations policy to restrict to approved EU regions only
# (adjust allowedValues to match your data-residency requirements)
cat > /tmp/resource-locations-policy.yaml <<EOF
constraint: constraints/gcp.resourceLocations
listPolicy:
  allowedValues:
    - "in:eu-locations"
EOF

gcloud resource-manager org-policies set-policy /tmp/resource-locations-policy.yaml \
  --project="${PROJECT_ID}"

Remediation — Terraform

# Terraform Google provider ~> 5.0
resource "google_organization_policy" "vertex_ai_resource_locations" {
  org_id     = var.org_id
  constraint = "constraints/gcp.resourceLocations"

  list_policy {
    allow {
      values = [
        "in:eu-locations",        # EU locations group (includes europe-west3, europe-west4, etc.)
        # "europe-west3",         # Frankfurt — uncomment for specific region restriction
        # "europe-west4",         # Netherlands
      ]
    }
  }
}

Remediation — Config Connector

apiVersion: orgpolicy.cnrm.cloud.google.com/v1beta1
kind: OrgPolicyPolicy
metadata:
  name: vertex-data-residency
  namespace: config-control
spec:
  resourceRef:
    apiVersion: resourcemanager.cnrm.cloud.google.com/v1beta1
    kind: Organization
    external: "organizations/ORG_ID"
  spec:
    rules:
    - values:
        allowedValues:
        - "in:us-locations"
        - "in:eu-locations"
  name: "organizations/ORG_ID/policies/gcp.resourceLocations"

Compliance mapping

CIS AWS Foundations v3.0.0 CIS Microsoft Azure Foundations v3.0.0 CIS GCP Foundation v4.0.0 CIS OCI Foundation v2.0.0 NIST SP 800-53 rev5 ISO/IEC 27001:2022 ISO/IEC 27017:2015 OWASP LLM Top 10:2025 NIST AI 600-1 (Jul 2024) EU AI Act (2024/1689)
n/a (no dedicated CIS GenAI benchmark) n/a (no dedicated CIS GenAI benchmark) n/a (no dedicated CIS GenAI benchmark) n/a (no dedicated CIS GenAI benchmark) SA-9; SC-28 A.5.33; A.8.10 n/a n/a Data Privacy Art. 55 (in force 2025-08-02)

Log signals

  • Cloud Audit Logs on aiplatform.googleapis.com for Endpoint.create or Model.upload where protoPayload.request.parent points to a region outside the documented residency allow-list (e.g. locations/us-central1 when policy requires locations/europe-west1).
  • Cross-region Model.copy operations: a model being copied from an in-residency region to an out-of-residency region.
  • Org Policy constraints/gcp.resourceLocations drift removing the EU multi-region from the deny-list.

Query

logName=~"projects/.*/logs/cloudaudit.googleapis.com%2Factivity"
          AND protoPayload.serviceName="aiplatform.googleapis.com"
          AND protoPayload.methodName=~".*(Endpoint.create|Model.(upload|copy))"
          AND NOT protoPayload.resourceName=~"projects/[^/]+/locations/(europe-west1|europe-west4)/.*"

This Cloud Logging filter is keyed against a residency-allow-list regex; pair with the Cloud Asset Inventory resourceLocations constraint state and surface both the policy posture and create-time drift in a single Cloud Monitoring alert policy.

Alert threshold

  • Page on any Vertex AI resource created in a region outside the residency allow-list on projects tagged for EU residency.
  • Page on Org Policy mutations that loosen the gcp.resourceLocations constraint.

Initial response

  1. Delete the out-of-residency resource and re-create in the allowed region; if the resource processed data during the gap, treat the data as having crossed the residency boundary and follow the GDPR Article 28 sub-processor notification workflow.
  2. Re-assert the gcp.resourceLocations constraint at the organisation node; pin the constraint and the Vertex AI region selection in Terraform.
  3. Audit Vertex AI prediction-request origins during the gap window to confirm whether prompts containing personal data were processed in the out-of-residency region.

References

gcp-genai-08-model-garden-access-control ! MEDIUM PREVENTIVE

Restrict which Model Garden models can be deployed and invoked in your organisation using the constraints/vertexai.allowedModels org policy constraint. This GA constraint — moved from the aiplatform.* preview namespace to the vertexai.* GA namespace — allows you to define an explicit allow-list of model publisher and model name combinations that can be invoked in Vertex AI. Developers and pipelines that request a model not on the allow-list receive a policy violation error at the API gateway before any inference occurs.

Constraint name (verified 2026-05-24): constraints/vertexai.allowedModels — this is the GA name. The former preview name constraints/aiplatform.allowedModels is deprecated and will not resolve correctly in current Google Cloud environments. Any reference to constraints/aiplatform.allowedModels in your org policy configuration is incorrect and must be updated to constraints/vertexai.allowedModels.

Remediation — gcloud CLI

# gcloud — apply constraints/vertexai.allowedModels org policy
# Constraint name: vertexai.allowedModels (GA name, verified 2026-05-24)
# The former preview name constraints/aiplatform.allowedModels is deprecated.

gcloud resource-manager org-policies set-policy policy.yaml \
  --organization="${ORG_ID}"

# Verify the policy was applied
gcloud resource-manager org-policies describe constraints/vertexai.allowedModels \
  --organization="${ORG_ID}"

policy.yaml

# policy.yaml — constraints/vertexai.allowedModels
# Restricts Model Garden to approved Google-published models only.
# Adjust allowedValues to match your security-reviewed model list.
name: organizations/${ORG_ID}/policies/vertexai.allowedModels
spec:
  rules:
  - values:
      allowedValues:
      - publishers/google/models/gemini-2.0-flash:predict
      - publishers/google/models/gemini-1.5-pro:predict

Remediation — Terraform

# Terraform Google provider ~> 5.0
# Constraint name: constraints/vertexai.allowedModels (GA — NOT constraints/aiplatform.allowedModels)
resource "google_organization_policy" "vertex_ai_allowed_models" {
  org_id     = var.org_id
  constraint = "constraints/vertexai.allowedModels"

  list_policy {
    allow {
      values = [
        "publishers/google/models/gemini-2.0-flash:predict",
        "publishers/google/models/gemini-1.5-pro:predict",
      ]
    }
  }
}

Remediation — Config Connector

apiVersion: iam.cnrm.cloud.google.com/v1beta1
kind: IAMPolicyMember
metadata:
  name: model-garden-curated-access
  namespace: config-control
spec:
  resourceRef:
    apiVersion: resourcemanager.cnrm.cloud.google.com/v1beta1
    kind: Project
    external: "projects/PROJECT_ID"
  role: roles/aiplatform.modelGardenUser
  member: "group:gcp-genai-curators@example.com"

Compliance mapping

CIS AWS Foundations v3.0.0 CIS Microsoft Azure Foundations v3.0.0 CIS GCP Foundation v4.0.0 CIS OCI Foundation v2.0.0 NIST SP 800-53 rev5 ISO/IEC 27001:2022 ISO/IEC 27017:2015 OWASP LLM Top 10:2025 NIST AI 600-1 (Jul 2024) EU AI Act (2024/1689)
n/a (no dedicated CIS GenAI benchmark) n/a (no dedicated CIS GenAI benchmark) n/a (no dedicated CIS GenAI benchmark) n/a (no dedicated CIS GenAI benchmark) CM-2; CM-7; SA-4 A.8.30; A.5.21 n/a LLM03:2025 Value Chain and Component Integration Art. 55 (in force 2025-08-02)

Log signals

  • Cloud Audit Logs on aiplatform.googleapis.com for PublisherModel.deploy calls importing Model Garden third-party models into the project — every deploy is a fresh supply-chain trust decision.
  • Custom-model uploads via Model.upload sourcing from Cloud Storage buckets outside the documented model-artifact allow-list — surfaces lateral imports.
  • Model-Garden license-acceptance events: aiplatform.googleapis.com publisher-acceptance entries indicate a new third-party model becoming available to the project's principals.

Query

logName=~"projects/.*/logs/cloudaudit.googleapis.com%2Factivity"
          AND protoPayload.serviceName="aiplatform.googleapis.com"
          AND (protoPayload.methodName=~".*PublisherModel.deploy"
               OR (protoPayload.methodName=~".*Model.upload"
                   AND NOT protoPayload.request.model.artifactUri=~"gs://approved-model-artifacts/.*"))

Pin this Cloud Logging filter at organisation scope; pair with a Cloud Asset Inventory feed on aiplatform.googleapis.com/Model so the catalogue of deployed third-party / custom models is visible alongside deploy events.

Alert threshold

  • Page on any PublisherModel deploy of a model not on the documented vendor allow-list.
  • Page on any custom-Model upload whose artifactUri points to a bucket outside the approved model-artefacts namespace.

Initial response

  1. Undeploy the unauthorised model via gcloud ai endpoints undeploy-model; identify any endpoint that bound the model and quarantine its prediction-request output for review.
  2. Verify the artefact provenance: cross-check the model's signing fingerprint against the vendor's published list (or against the project's CI build attestations for custom models).
  3. Pin Model Garden allow-list + Model-upload artifact-URI prefix in Terraform; gate model deploys through a CI workflow that consults the vendor allow-list TSV.

References

Sources

  • Vertex AI security overview — Google Cloud documentation (accessed 2026-05)
  • Google Cloud Audit Logs — Vertex AI audit logging — Google Cloud documentation (accessed 2026-05)
  • VPC Service Controls for Vertex AI — Google Cloud documentation (accessed 2026-05)
  • Organization policy constraints reference: constraints/vertexai.allowedModels — Google Cloud documentation (accessed 2026-05)
  • Gemini API safety settings — Vertex AI Python SDK documentation (accessed 2026-05)
  • OWASP Top 10 for LLM Applications 2025 (accessed 2026-05)
  • NIST AI 600-1 (July 2024, final): https://doi.org/10.6028/NIST.AI.600-1 (accessed 2026-05)
  • Unit 42 — Threat Research: Vertex AI Agent Engine identity risks (April 2026, accessed 2026-05)