This check flags any GCP Cloud Function that grants the allUsers or allAuthenticatedUsers principal the invoker role, meaning anyone on the internet can trigger it. Unless the function is intentionally a public HTTP endpoint, remove that binding with gcloud functions remove-invoker-policy-binding and require IAM authentication instead.
Serverless makes it trivially easy to ship a function and forget about who can call it. On GCP, a single checkbox or a stray IAM binding can take a function from "internal only" to "callable by the entire internet." The Cloud Function Allows Public Invocation check catches exactly that: functions where the cloudfunctions.invoker or run.invoker role has been granted to allUsers or allAuthenticatedUsers.
Sometimes that is intentional. A public webhook receiver or a marketing form handler is supposed to accept anonymous traffic. But far more often, public invocation is an accident left over from a quick test or a tutorial that told you to click "Allow unauthenticated invocations" to get something working fast.
What this check detects
The check inspects the IAM policy attached to each Cloud Function and looks for two specific principals in the invoker role binding:
allUsers— anyone on the internet, no authentication required.allAuthenticatedUsers— anyone with a Google account, including accounts that have nothing to do with your organization.
If either appears in the function's invoker binding, the check fails. The exact role name depends on the function's generation:
- 1st gen functions use
roles/cloudfunctions.invoker. - 2nd gen functions run on Cloud Run under the hood and use
roles/run.invoker.
Note: 2nd gen Cloud Functions are built on Cloud Run, so their invoker permission lives on the underlying Cloud Run service. When you audit or fix a 2nd gen function, you may need to target the Cloud Run service rather than the function resource directly.
Why it matters
A public function is a public attack surface. The moment allUsers can invoke your function, you have handed the internet a button that runs your code on your bill, with your service account's permissions.
Concrete risks
- Data exposure. If the function reads from a database, queries an internal API, or returns records, an unauthenticated caller can pull that data out simply by sending requests. Many leaks start with a function that "just returns some JSON."
- Privilege abuse. Functions run as a service account. If that account can write to Cloud Storage, publish to Pub/Sub, or call other internal services, an attacker inherits those capabilities through the open endpoint.
- Cost and abuse. Public functions get scraped, fuzzed, and hammered. Each invocation costs money, and a sustained flood can run up a large bill or get used as a proxy to attack third parties.
- Reconnaissance. Even a function that returns an error reveals it exists. Attackers enumerate function URLs and probe them for weak input validation, injection flaws, or verbose error messages.
Warning: A common pattern is a function that accepts a JSON body and forwards it to an internal system. If that function is public and skips input validation, you have effectively published a server-side request forgery primitive to anyone who finds the URL.
The business impact is straightforward: an open function is one of the most directly exploitable misconfigurations in a cloud account because it requires no credentials and no lateral movement. The attacker just needs the URL.
How to fix it
The fix is to remove the public principal from the invoker binding and grant access only to the specific identities that actually need to call the function.
1. Confirm the current bindings
Check who can invoke the function before you change anything:
gcloud functions get-iam-policy FUNCTION_NAME \
--region=REGION \
--project=PROJECT_ID
Look for a binding that lists allUsers or allAuthenticatedUsers under roles/cloudfunctions.invoker or roles/run.invoker.
2. Remove the public binding (1st gen)
Danger: If this function is a legitimate public endpoint (a webhook, a public form handler), removing allUsers will break it for anonymous callers. Confirm the function is meant to be private before running these commands.
gcloud functions remove-invoker-policy-binding FUNCTION_NAME \
--region=REGION \
--member="allUsers"
If allAuthenticatedUsers is also present, remove it the same way:
gcloud functions remove-invoker-policy-binding FUNCTION_NAME \
--region=REGION \
--member="allAuthenticatedUsers"
3. Remove the public binding (2nd gen)
For 2nd gen functions, target the underlying Cloud Run service:
gcloud run services remove-iam-policy-binding FUNCTION_NAME \
--region=REGION \
--member="allUsers" \
--role="roles/run.invoker"
4. Grant access to the right identities
Now grant invoke permission only to the service accounts or users that legitimately call the function. For a service-to-service call:
gcloud functions add-invoker-policy-binding FUNCTION_NAME \
--region=REGION \
--member="serviceAccount:caller-sa@PROJECT_ID.iam.gserviceaccount.com"
The calling service then attaches an identity token to its requests:
# Fetch an identity token scoped to the function URL
TOKEN=$(gcloud auth print-identity-token)
curl -H "Authorization: Bearer $TOKEN" \
https://REGION-PROJECT_ID.cloudfunctions.net/FUNCTION_NAME
Tip: If the function genuinely must be reachable from the public internet, do not leave it open. Put it behind an API Gateway or a Cloud Load Balancer with Cloud Armor and IAP in front. That gives you authentication, rate limiting, and WAF rules while keeping the function itself private.
5. Verify the fix
gcloud functions get-iam-policy FUNCTION_NAME \
--region=REGION \
--project=PROJECT_ID
The output should no longer contain allUsers or allAuthenticatedUsers.
Fixing it in Terraform
If your functions are managed with Terraform, the public binding usually comes from an allUsers member in an IAM resource. Remove it and replace it with a specific member.
For a 2nd gen function (Cloud Run service):
# Bad: anyone can invoke
resource "google_cloud_run_service_iam_member" "public" {
location = google_cloudfunctions2_function.fn.location
service = google_cloudfunctions2_function.fn.name
role = "roles/run.invoker"
member = "allUsers"
}
# Good: only the caller service account can invoke
resource "google_cloud_run_service_iam_member" "caller" {
location = google_cloudfunctions2_function.fn.location
service = google_cloudfunctions2_function.fn.name
role = "roles/run.invoker"
member = "serviceAccount:caller-sa@${var.project_id}.iam.gserviceaccount.com"
}
For a 1st gen function:
resource "google_cloudfunctions_function_iam_member" "caller" {
project = var.project_id
region = var.region
cloud_function = google_cloudfunctions_function.fn.name
role = "roles/cloudfunctions.invoker"
member = "serviceAccount:caller-sa@${var.project_id}.iam.gserviceaccount.com"
}
How to prevent it from happening again
Manual cleanup is fine once. The goal is to make sure a public function never reaches production again. A few layers work well together.
Block it with Organization Policy
GCP has a built-in org policy constraint, constraints/iam.allowedPolicyMemberDomains (Domain Restricted Sharing), that prevents granting IAM roles to members outside your organization. With it enabled, attempts to add allUsers or allAuthenticatedUsers to any resource, including Cloud Functions, are rejected.
gcloud resource-manager org-policies allow \
iam.allowedPolicyMemberDomains \
--organization=ORG_ID \
C0xxxxxxx # your Cloud Identity customer ID
Note: Domain Restricted Sharing blocks all external sharing, not just function invocation. If you have legitimate cross-org sharing or public buckets, scope the policy carefully and use exceptions rather than disabling it globally.
Gate it in CI/CD with policy-as-code
Catch the misconfiguration before it merges. A Conftest or OPA policy can scan Terraform plans for the offending member:
package main
deny[msg] {
resource := input.resource_changes[_]
resource.type == "google_cloud_run_service_iam_member"
member := resource.change.after.member
member == "allUsers"
msg := sprintf("Cloud Run invoker granted to allUsers in %s", [resource.address])
}
deny[msg] {
resource := input.resource_changes[_]
resource.type == "google_cloudfunctions_function_iam_member"
resource.change.after.member == "allUsers"
msg := sprintf("Cloud Function invoker granted to allUsers in %s", [resource.address])
}
Wire it into your pipeline against a saved plan:
terraform plan -out=tfplan.binary
terraform show -json tfplan.binary > tfplan.json
conftest test tfplan.json
Tip: Pair the policy gate with continuous scanning in Lensix so you catch functions created outside your IaC pipeline, through the console, or by other teams. Org policy plus CI gates plus runtime scanning closes all three paths a public function can sneak in through.
Best practices
- Default to private. When deploying, do not pass
--allow-unauthenticatedunless you have a specific, reviewed reason. Make authenticated invocation the team default. - Use dedicated service accounts. Give each function its own least-privilege service account instead of the default Compute or App Engine account. If the function is ever abused, the blast radius stays small.
- Front public traffic with a gateway. When you truly need public access, route it through API Gateway or a load balancer with Cloud Armor and IAP rather than exposing the function directly.
- Validate and rate limit. Treat every input as hostile. Validate payloads, set sensible timeouts, and apply per-caller limits to blunt abuse.
- Audit invoker bindings regularly. IAM drifts over time. Schedule recurring scans of who can invoke each function and alert on any new public binding.
- Log and monitor invocations. Send function logs to Cloud Logging and alert on unusual spikes or unexpected source patterns so an attack shows up before the bill does.
Public invocation is one of those misconfigurations that is cheap to prevent and expensive to discover after the fact. Lock functions down by default, block external IAM members at the org level, and keep a scanner watching for the day someone clicks "Allow unauthenticated" to make a demo work.

