This check flags GCP firewall rules that permit all outbound traffic to 0.0.0.0/0. Unrestricted egress lets compromised workloads exfiltrate data and phone home to attacker infrastructure. Replace the broad allow-all rule with scoped egress rules that only permit the destinations and ports your workloads actually need.
Most teams obsess over inbound firewall rules and barely think about egress. That makes sense on the surface, since inbound is where the attacker knocks first. But once a workload is compromised, egress is how the damage actually happens. Data leaves, malware downloads its second stage, and a foothold turns into a breach. The Firewall Allows Unrestricted Egress check exists because a wide-open egress rule quietly removes one of your best containment controls.
What this check detects
The vpc_nopublicegress check inspects your GCP VPC firewall rules and flags any rule that:
- Has a direction of
EGRESS - Has an action of
ALLOW - Allows all protocols and ports (or a very broad range)
- Targets the destination range
0.0.0.0/0(the entire internet)
In plain terms: the rule says "anything inside this network can talk to anywhere on the internet, on any port." That is the default behavior of every new VPC in GCP, which is exactly why this slips past so many teams.
Note: Every GCP VPC ships with an implied egress allow rule (priority 65535) that permits all outbound traffic. This implied rule is invisible in most listings and cannot be deleted. To restrict egress you create a higher-priority deny rule and then carve out specific allows above it. This check catches both the implied rule being left unmanaged and any explicit allow-all egress rule you may have created.
Why it matters
Egress filtering is a containment control. It does not stop the initial compromise, but it limits what an attacker can do once they are inside. Without it, a single compromised VM, container, or Cloud Function has a clear path to the open internet.
Data exfiltration
This is the headline risk. If an attacker gains code execution on a workload, unrestricted egress means they can stream your database dumps, customer records, or secrets straight out to an S3 bucket, a pastebin, or their own server. Tight egress rules force that traffic through a narrow set of known destinations, where it can be logged, inspected, or blocked entirely.
Command and control
Modern malware rarely arrives fully formed. It establishes a connection back to a command and control server, pulls down additional payloads, and waits for instructions. Allow-all egress gives that beacon a clean line out. Blocking arbitrary outbound connections breaks the C2 channel and often stops an intrusion before it escalates.
Cryptomining and resource abuse
Compromised compute is frequently turned into a cryptominer. Miners need to reach mining pools over specific ports. Restricting egress to your actual dependencies starves these from ever connecting, and the unexpected blocked traffic becomes a useful detection signal.
Warning: Unrestricted egress also undermines compliance frameworks. PCI DSS, SOC 2, and HIPAA all expect outbound traffic to be controlled and monitored for systems handling sensitive data. An allow-all egress rule on a workload in scope is a finding waiting to happen during an audit.
How to fix it
The goal is to move from "allow everything" to "deny everything, then allow what you need." You do this in three steps: add a low-priority deny-all egress rule, then add higher-priority allow rules for legitimate destinations, then verify nothing broke.
Step 1: Identify your real egress dependencies
Before you block anything, find out what your workloads actually talk to. Common destinations include Google APIs, package mirrors, your own internal services, NTP, and DNS. Enable firewall rules logging or VPC Flow Logs first so you have data to work from.
gcloud compute firewall-rules list \
--filter="direction=EGRESS" \
--format="table(name,network,direction,priority,destinationRanges.list(),allowed[].map().firewall_rule().list())"
Step 2: Add a default deny-all egress rule
Create a low-priority deny rule. Low priority (a high number) means your specific allow rules can sit above it.
Danger: Applying a deny-all egress rule without first creating allow rules for required traffic will break outbound connectivity for affected instances. Package installs, API calls, and health checks can all fail. Always stage this in a non-production environment and use a target tag or service account to scope it narrowly before rolling out broadly.
gcloud compute firewall-rules create deny-all-egress \
--network=my-vpc \
--direction=EGRESS \
--action=DENY \
--rules=all \
--destination-ranges=0.0.0.0/0 \
--priority=65000 \
--target-tags=restricted-egress \
--enable-logging
Scoping with --target-tags=restricted-egress means only instances carrying that tag are affected, so you can roll this out workload by workload instead of all at once.
Step 3: Allow the destinations you actually need
Now carve out specific allows above the deny rule. For example, to permit HTTPS to Google's API ranges and DNS:
# Allow DNS to Google's metadata resolver
gcloud compute firewall-rules create allow-egress-dns \
--network=my-vpc \
--direction=EGRESS \
--action=ALLOW \
--rules=udp:53,tcp:53 \
--destination-ranges=169.254.169.254/32 \
--priority=1000 \
--target-tags=restricted-egress
# Allow HTTPS to a known set of destinations
gcloud compute firewall-rules create allow-egress-https \
--network=my-vpc \
--direction=EGRESS \
--action=ALLOW \
--rules=tcp:443 \
--destination-ranges=203.0.113.0/24 \
--priority=1000 \
--target-tags=restricted-egress
Tip: For reaching Google APIs without opening broad internet ranges, enable Private Google Access and route API traffic to private.googleapis.com (199.36.153.8/30) or restricted.googleapis.com (199.36.153.4/30). You then allow egress only to those tiny ranges instead of the whole internet, which dramatically shrinks your attack surface.
Step 4: Remove or tighten the explicit allow-all rule
If a teammate created an explicit allow-all egress rule (separate from the implied one), delete it.
Danger: Confirm no workload depends on this rule before deleting it. Check the rule's target tags and logging data first. Deleting a rule that backs live traffic causes immediate connection failures.
gcloud compute firewall-rules delete allow-all-egress --network=my-vpc
Fixing it with Terraform
If you manage your VPC as code, define egress controls declaratively so they stay consistent and reviewable.
resource "google_compute_firewall" "deny_all_egress" {
name = "deny-all-egress"
network = google_compute_network.my_vpc.name
direction = "EGRESS"
priority = 65000
deny {
protocol = "all"
}
destination_ranges = ["0.0.0.0/0"]
target_tags = ["restricted-egress"]
log_config {
metadata = "INCLUDE_ALL_METADATA"
}
}
resource "google_compute_firewall" "allow_egress_https" {
name = "allow-egress-https"
network = google_compute_network.my_vpc.name
direction = "EGRESS"
priority = 1000
allow {
protocol = "tcp"
ports = ["443"]
}
destination_ranges = ["199.36.153.8/30"] # private.googleapis.com
target_tags = ["restricted-egress"]
}
How to prevent it from happening again
Manual cleanup fixes today's problem. Automation keeps it from coming back. The implied allow-all egress rule is on every new VPC, so without guardrails this finding reappears every time someone spins up a network.
Enforce with Organization Policy
Use GCP Organization Policy constraints and a baseline VPC module so that every new network inherits a deny-all egress rule by default. New projects then start locked down rather than wide open.
Gate it in CI/CD with policy-as-code
Catch broad egress rules before they ever reach production. A Conftest policy over your Terraform plan can fail the build when an allow-all egress rule shows up.
package main
deny[msg] {
resource := input.resource_changes[_]
resource.type == "google_compute_firewall"
resource.change.after.direction == "EGRESS"
allow := resource.change.after.allow[_]
allow.protocol == "all"
resource.change.after.destination_ranges[_] == "0.0.0.0/0"
msg := sprintf("Firewall rule '%s' allows unrestricted egress to 0.0.0.0/0", [resource.change.after.name])
}
terraform plan -out=tfplan.binary
terraform show -json tfplan.binary > tfplan.json
conftest test tfplan.json
Tip: Wire a continuous scan with Lensix on top of your CI gate. CI catches what goes through the pipeline, but console clicks and out-of-band changes bypass it. Continuous monitoring flags an allow-all egress rule the moment it appears, no matter how it got created.
Best practices
- Default deny, explicit allow. Treat egress like ingress. Block everything, then open only the specific destinations and ports your workloads require.
- Scope rules with tags or service accounts. Avoid blanket rules across an entire VPC. Tie egress allows to the workloads that need them so a change to one does not affect others.
- Use Private Google Access. Route GCP API traffic through restricted Google ranges instead of the public internet, shrinking the destinations you have to allow.
- Enable firewall rules logging. Logging on deny rules turns blocked egress into a detection signal. Unexpected outbound attempts often mean something is wrong.
- Review egress rules on a schedule. Destinations drift over time. Re-validate that every allow rule still maps to a real dependency.
- Prefer narrow CIDRs over 0.0.0.0/0. If you must reach external services, allow their published IP ranges rather than the whole internet.
Restricting egress takes more effort than leaving it open, and that is precisely why it is valuable. The workloads that hurt you most in a breach are the ones that could reach anywhere. Close that path, and a compromise becomes a contained incident instead of a headline.

