Back to blog
AWSBest PracticesCloud SecurityIdentity & AccessNetworking

VPC Endpoint Has Permissive Policy: Closing the Wildcard Principal Gap

Learn why a wildcard principal in a VPC endpoint policy is a data exfiltration risk, and how to scope, remediate, and prevent it with CLI, Terraform, and policy-as-code.

TL;DR

This check flags VPC endpoint policies that grant access to a wildcard principal ("Principal": "*"), which means any AWS account or identity that can reach the endpoint may be allowed through it. Lock the policy down to specific principals, accounts, and resources instead of leaving it wide open.

VPC endpoints let resources inside your VPC talk to AWS services without traversing the public internet. That is great for security and latency, but the endpoint itself carries a resource policy, and that policy is easy to leave at its permissive default. When the policy uses a wildcard principal, you have effectively removed one of the layers that endpoint policies are supposed to provide.

The vpc_endpointexposed check in the vpc_checks module looks at every VPC endpoint policy in your AWS account and raises a finding when it sees "Principal": "*" without conditions that meaningfully scope it down.


What this check detects

A VPC endpoint policy is an IAM resource policy attached to the endpoint. It controls which principals can use the endpoint to reach the target service, and which actions and resources they can touch. There are two flavors of VPC endpoint:

  • Gateway endpoints for Amazon S3 and DynamoDB, attached to route tables.
  • Interface endpoints (powered by AWS PrivateLink) for most other services, attached as elastic network interfaces with private IPs.

Both types support a policy document. The check fires when that document contains a statement like this:

{
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": "*",
      "Action": "*",
      "Resource": "*"
    }
  ]
}

Note: When you create a VPC endpoint without specifying a policy, AWS attaches a default "full access" policy that allows all principals and all actions. That default is exactly what this check is designed to catch, so a freshly created endpoint will usually trip it until you replace the policy.

The check is looking specifically for a wildcard principal. A wildcard action or resource is not great either, but the principal is the part that decides who can use the endpoint, so that is where the highest risk sits.


Why it matters

A common misconception is that a VPC endpoint is private, so its policy does not matter. The network path is private, but the authorization path is not. The endpoint policy combines with the target service's resource policies and IAM policies to decide what gets through. A wildcard principal weakens that combination.

Here is where it bites in practice:

  • S3 gateway endpoints and cross-account access. If your S3 buckets rely on bucket policies that trust "any principal in the VPC," and the endpoint policy is also wide open, you have lost the ability to restrict which accounts and roles can pull data through that endpoint. A misconfigured bucket policy elsewhere is no longer caught by the endpoint layer.
  • Data exfiltration paths. Endpoint policies are one of the few controls that can stop a compromised instance from using the endpoint to reach arbitrary S3 buckets, including buckets in attacker-controlled accounts. With a wildcard principal and Resource: "*", an attacker on a box in your VPC can copy stolen data straight to their own bucket over the private path, bypassing any internet egress filtering you set up.
  • Defense in depth collapses. Security teams treat the endpoint policy as a guardrail that survives even when IAM gets sloppy. A wildcard principal removes that guardrail, so a single overly broad IAM role is enough to expose the service.

Warning: Endpoint policies only restrict traffic that flows through the endpoint. They do not block access to the service over other paths, and they do not override an explicit deny elsewhere. Treat them as one layer, not the whole wall.


How to fix it

The fix is to replace the wildcard principal with the specific accounts, roles, or organization IDs that legitimately need the endpoint, and to scope actions and resources to what is actually used.

Step 1: Find the offending endpoints

aws ec2 describe-vpc-endpoints \
  --query 'VpcEndpoints[].{Id:VpcEndpointId,Service:ServiceName,Policy:PolicyDocument}' \
  --output json

Look for any PolicyDocument where Principal is "*" with no scoping conditions.

Step 2: Write a scoped policy

For an S3 gateway endpoint, a tighter policy restricts both the principals (your account) and the resources (your specific buckets):

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowOwnAccountToOwnBuckets",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::111122223333:root"
      },
      "Action": [
        "s3:GetObject",
        "s3:PutObject",
        "s3:ListBucket"
      ],
      "Resource": [
        "arn:aws:s3:::my-app-data",
        "arn:aws:s3:::my-app-data/*"
      ]
    }
  ]
}

If you want to keep a broad principal but still close the exfiltration hole, use a condition that confines the endpoint to resources in your own organization:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowOrgResourcesOnly",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:*",
      "Resource": "*",
      "Condition": {
        "StringEquals": {
          "aws:ResourceOrgID": "o-abcd1234ef"
        }
      }
    }
  ]
}

The aws:ResourceOrgID condition means traffic through the endpoint can only reach buckets owned by accounts in your AWS Organization. Even though the principal is still "*", the wildcard no longer opens a path to outside buckets.

Note: Lensix flags a bare wildcard principal. A wildcard principal that is meaningfully constrained by conditions like aws:ResourceOrgID, aws:PrincipalOrgID, or a aws:SourceVpc match is a legitimate pattern and a much smaller risk. Add the condition and you address the underlying concern.

Step 3: Apply the policy

Danger: Tightening an endpoint policy can immediately break workloads that depend on the access you are removing. If you scope to specific buckets and miss one that production reads from, those calls start failing with access denied. Test against a staging endpoint or apply during a maintenance window, and confirm your resource list is complete first.

aws ec2 modify-vpc-endpoint \
  --vpc-endpoint-id vpce-0a1b2c3d4e5f67890 \
  --policy-document file://endpoint-policy.json

Step 4: Fix it in IaC instead

If the endpoint was created by Terraform, change the source so the fix is not overwritten on the next apply:

resource "aws_vpc_endpoint" "s3" {
  vpc_id            = aws_vpc.main.id
  service_name      = "com.amazonaws.${var.region}.s3"
  vpc_endpoint_type = "Gateway"
  route_table_ids   = [aws_route_table.private.id]

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid       = "AllowOrgResourcesOnly"
        Effect    = "Allow"
        Principal = "*"
        Action    = "s3:*"
        Resource  = "*"
        Condition = {
          StringEquals = {
            "aws:ResourceOrgID" = var.org_id
          }
        }
      }
    ]
  })
}

Tip: If you have many endpoints across accounts, do not hand-edit each one. Define a reusable Terraform module or CloudFormation macro that injects the org-scoping condition by default, and reference it everywhere. New endpoints inherit the safe policy automatically.


How to prevent it from happening again

The default full-access policy is the root cause, so prevention is about never letting that default reach production.

Scan IaC before it deploys

Add a policy-as-code check to your pipeline. Here is a Checkov-style custom check, but the same logic works in OPA or Conftest:

# Fail the build if any aws_vpc_endpoint policy uses a bare wildcard principal
conftest test plan.json --policy policies/vpc_endpoint.rego

A small Rego rule that denies an unconstrained wildcard principal:

package main

deny[msg] {
  resource := input.resource_changes[_]
  resource.type == "aws_vpc_endpoint"
  policy := json.unmarshal(resource.change.after.policy)
  stmt := policy.Statement[_]
  stmt.Principal == "*"
  not stmt.Condition
  msg := sprintf("VPC endpoint %s has a wildcard principal with no conditions", [resource.address])
}

Enforce with a Service Control Policy

For an organization-wide guardrail, an SCP can deny creating or modifying endpoints with an open policy, though SCPs cannot inspect the full policy document. The more reliable enforcement point is an AWS Config rule with auto-remediation.

Detect drift continuously

Lensix runs vpc_endpointexposed on a schedule, so even an endpoint created manually in the console gets caught after the fact. Pair the IaC gate (prevents new occurrences) with continuous scanning (catches the ones that slip through) for full coverage.

Tip: Wire the finding into your alerting so a new permissive endpoint pages the owning team rather than sitting in a dashboard. The exfiltration risk is real, and an endpoint that stays open for weeks is a standing invitation.


Best practices

  • Never ship the default policy. Always replace the auto-generated full-access policy with an explicit, scoped one when you create an endpoint.
  • Prefer org-scoping conditions. If you cannot enumerate every principal, at minimum add aws:ResourceOrgID and aws:PrincipalOrgID so the endpoint cannot be used to reach or by anything outside your organization.
  • Scope resources for S3 and DynamoDB. List the actual buckets and tables. A wildcard resource on an S3 endpoint is the classic exfiltration vector.
  • Combine layers. Endpoint policies, bucket policies, and IAM policies are independent. Set them so that no single misconfiguration grants access.
  • Use VPC condition keys. On the bucket side, aws:SourceVpce lets you require that requests arrive through your specific endpoint, completing the loop.
  • Review on a cadence. Endpoints accumulate over time and policies drift as teams add new buckets. Revisit them during regular access reviews rather than treating them as set-and-forget.

A VPC endpoint is supposed to give you a private, controlled path to AWS services. A wildcard principal keeps the private part but throws away the controlled part. Tighten the policy, add a CI gate, and let continuous scanning catch the rest.