This check flags any GCP audit logging config that uses exemptedMembers to skip logging for specific identities. Exemptions create blind spots that attackers and rogue insiders exploit. Fix it by removing the exemptedMembers arrays from your IAM audit config so every principal gets logged.
Cloud audit logs are your record of who did what, when, and from where. In GCP, that record lives in Cloud Audit Logs, configured through your project, folder, or organization IAM policy. The trouble starts when someone adds an exemption. An exemption tells GCP to stop recording activity for a named member, and once that line is in your policy, the actions of that identity vanish from your audit trail.
This Lensix check, logging_auditexemptions in the logging_checks module, scans your audit logging configuration and reports any member that has been excluded from logging.
What this check detects
GCP audit logging is defined inside the auditConfigs block of an IAM policy. Each entry specifies a service, the log types enabled for that service (ADMIN_READ, DATA_READ, DATA_WRITE), and optionally a list of exemptedMembers. Those exempted members do not generate audit log entries for the matching log type.
Here is what a problematic configuration looks like:
{
"auditConfigs": [
{
"service": "allServices",
"auditLogConfigs": [
{
"logType": "DATA_READ",
"exemptedMembers": [
"user:[email protected]",
"serviceAccount:[email protected]"
]
},
{
"logType": "DATA_WRITE"
}
]
}
]
}
The check fires whenever it finds a non-empty exemptedMembers array anywhere in your audit config. In the example above, the contractor and the batch service account will not produce DATA_READ logs, so any data they read goes unrecorded.
Note: Admin Activity logs (which capture configuration changes) are always on and cannot be exempted or disabled. Exemptions only apply to Data Access logs, which cover reads and writes to your actual data. That is exactly the activity an attacker wants hidden.
Why it matters
An audit log with exemptions is a log you cannot trust during an incident. The whole point of Data Access logging is to answer questions like "did this service account read the customer PII bucket?" or "who pulled rows from the finance dataset last Tuesday?" An exemption removes the answer before you ever ask the question.
The insider scenario
Consider an engineer with broad project access who quietly adds their own account to exemptedMembers for DATA_READ on allServices. From that point forward they can browse storage buckets, query BigQuery datasets, and pull secrets from Secret Manager without leaving a single read entry. The change itself shows up in Admin Activity logs, but if nobody is watching for IAM policy edits, the exemption sits undetected and the data exfiltration that follows is invisible.
The compromised service account scenario
Service accounts are a common exemption target because teams want to reduce log volume from noisy automation. The problem is that service accounts are also the most frequently abused identities in cloud breaches. If a key for an exempted service account leaks, the attacker inherits a logging blind spot for free. They can read everything that account can reach, and your detection tooling has nothing to alert on.
Danger: Exempting a service account from Data Access logging means that if that account is ever compromised, you lose the ability to scope the breach. You will not be able to tell investigators, auditors, or regulators what data was accessed. Treat every exemption as a deliberate, documented exception, never a default.
Compliance impact
Frameworks like PCI DSS, HIPAA, SOC 2, and ISO 27001 all expect complete and tamper-resistant access logging. CIS Google Cloud Foundations Benchmark control 2.1 specifically requires that Cloud Audit Logging is configured properly across all services and users, with no exempted members. An auditor who finds exemptedMembers in your policy will treat it as a gap, and you will need to justify or remediate it.
How to fix it
The fix is to remove the exemptions so that every member is logged. You do this by editing the IAM policy that holds the audit config and stripping out the exemptedMembers arrays.
Step 1: Find the current policy
Export the existing IAM policy for the affected resource. For a project:
gcloud projects get-iam-policy MY_PROJECT_ID \
--format=json > policy.json
Open policy.json and locate the auditConfigs block. Look for any auditLogConfigs entry that contains an exemptedMembers field.
Step 2: Remove the exemptions
Delete the exemptedMembers arrays. The corrected block should leave the log types enabled with no exemptions:
{
"auditConfigs": [
{
"service": "allServices",
"auditLogConfigs": [
{ "logType": "ADMIN_READ" },
{ "logType": "DATA_READ" },
{ "logType": "DATA_WRITE" }
]
}
]
}
Step 3: Apply the corrected policy
Warning: set-iam-policy overwrites the entire policy. Make sure your policy.json includes the current etag and all existing bindings, not just the audit config. The etag guards against clobbering a concurrent change. Always work from a fresh export.
gcloud projects set-iam-policy MY_PROJECT_ID policy.json
For organization-level or folder-level audit configs, swap in the matching command:
# Organization
gcloud organizations get-iam-policy ORG_ID --format=json > policy.json
gcloud organizations set-iam-policy ORG_ID policy.json
# Folder
gcloud resource-manager folders get-iam-policy FOLDER_ID --format=json > policy.json
gcloud resource-manager folders set-iam-policy FOLDER_ID policy.json
Warning: Enabling full Data Access logging across allServices increases log volume, and Data Access logs beyond the free Admin Activity tier are billed. High-traffic projects can generate significant ingestion costs in Cloud Logging. Plan for it by routing Data Access logs to a dedicated bucket with a sensible retention period, or to BigQuery for cheaper long-term storage.
Step 4: Verify
Re-fetch the policy and confirm no exemptedMembers remain:
gcloud projects get-iam-policy MY_PROJECT_ID \
--format=json | grep -i exemptedMembers
An empty result means the exemptions are gone. Re-run the Lensix check to confirm the finding clears.
How to prevent it from happening again
Removing the exemption once is not enough. Without guardrails, someone will re-add it the next time log volume spikes or a service account gets noisy. Lock the configuration into code and enforce it.
Manage audit config in Terraform
Define your audit config with google_project_iam_audit_config and leave out exempted_members entirely. Because Terraform manages the resource, any out-of-band exemption added through the console or CLI will show up as drift on the next plan.
resource "google_project_iam_audit_config" "all_services" {
project = "my-project-id"
service = "allServices"
audit_log_config {
log_type = "ADMIN_READ"
}
audit_log_config {
log_type = "DATA_READ"
}
audit_log_config {
log_type = "DATA_WRITE"
}
}
Tip: Run terraform plan -detailed-exitcode on a schedule (a nightly CI job works well) against your live state. Exit code 2 means drift was detected. Pipe that into an alert so a manually added exemption pages someone instead of sitting silently for months.
Block exemptions with an org policy and OPA
For policy-as-code in CI, add a Conftest or OPA rule that rejects any Terraform plan containing exempted members:
package main
deny[msg] {
resource := input.resource_changes[_]
resource.type == "google_project_iam_audit_config"
config := resource.change.after.audit_log_config[_]
count(config.exempted_members) > 0
msg := sprintf(
"Audit log exemptions are not allowed (service: %s)",
[resource.change.after.service]
)
}
Wire that into your pipeline so the merge is blocked before the exemption ever reaches GCP:
terraform plan -out=tfplan
terraform show -json tfplan > plan.json
conftest test plan.json --policy ./policies
Alert on policy changes at runtime
Defense in depth means catching changes that slip past CI too. Create a log-based metric and alert that fires whenever an IAM policy update touches the audit config:
gcloud logging metrics create audit-config-changed \
--description="Fires when IAM audit config is modified" \
--log-filter='protoPayload.methodName="SetIamPolicy"
AND protoPayload.serviceData.policyDelta.auditConfigDeltas:*'
Attach an alerting policy in Cloud Monitoring to notify your security channel whenever that metric increments.
Best practices
- Configure audit logging at the organization level. Setting the audit config on the org and letting projects inherit it gives you one place to control and one place to audit, instead of drift across hundreds of projects.
- Enable all three log types on
allServices.ADMIN_READ,DATA_READ, andDATA_WRITEtogether give you full coverage. Partial coverage leaves the same blind spots as an exemption. - Treat exemptions as a break-glass exception, not a tuning knob. If log volume or cost is the real problem, fix it with log routing, exclusion filters on the sink (not the audit config), or sampling, none of which destroy the source record the way an exemption does.
- Route logs to an immutable, separate sink. Export audit logs to a dedicated logging project or a locked Cloud Storage bucket with retention policies, so an attacker with project access cannot delete the evidence.
- Review the audit config on every access review cycle. Fold a check for
exemptedMembersinto the same quarterly review you use for IAM bindings.
Tip: Use a log exclusion on the sink rather than an audit-config exemption when you genuinely need to cut volume. The exclusion stops the entry from being stored and billed, but the policy still logs every member, so you keep the option to flip it back on without a privileged IAM change.
Audit logging only protects you if it covers everyone. A single line in exemptedMembers can turn your most-watched project into a gap that nobody notices until an investigation goes cold. Strip the exemptions, codify the config in Terraform, gate it in CI, and alert on changes. Then your audit trail will actually hold up when you need it.

