RAPID INCIDENT TRIAGE & ACTION PLAN•Security
01. Error Signature
IAM SourceVpce Network Condition Enforcer
02. Root Cause Vector

An IAM policy missing the `aws:SourceVpce` condition allows any authenticated principal to invoke the resource from the public internet, ...

03. Rapid Prescription

Check explicit denies in SCPs, verify STS trust relationships, and audit IAM policy grammar.

Zero-Trust Client IDE/ Security

IAM SourceVpce Network Condition Enforcer

An IAM policy missing the `aws:SourceVpce` condition allows any authenticated principal to invoke the resource from the public internet, bypassing your VPC network perimeter entirely.

SYSTEM STATUS
CREDITS: 0 / 3
source.yaml(63L)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# ===============================================
# legacy-aws-lambda-role.yaml
# Created: 2019-11-05
# Last Modified: 2023-08-22 (quick patch for PCI audit)
# Owner: Platform-Security-Team
# ===============================================

# TODO: This IAM role is a mess. Consolidate with terraform after Q4 migration.
# WARNING: Do not manually edit inline policies—use the CI/CD pipeline (when it works).

AWSTemplateFormatVersion: '2010-09-09'
Description: Legacy Lambda execution role for PCI-secure-zone payment processing.

Resources:
  LegacyPaymentLambdaRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub "${AWS::StackName}-legacy-payment-exec-role"
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: "AllowS3AndSecretsAccess"
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - s3:GetObject
                  - s3:PutObject
                Resource: "arn:aws:s3:::pci-secure-zone-payments/*"
                # FIXME: This condition was added during the VPC migration but never fully validated
                Condition:
                  StringEquals:
                    aws:SourceVpce: "vpce-0ab1c23d4e5f67890"
              - Effect: Allow
                Action:
                  - secretsmanager:GetSecretValue
                Resource: "arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/payment-db-credentials-*"
                # VULNERABILITY: Missing aws:SourceVpce condition. The S3 statement has it, but this one doesn't, allowing access from outside the VPC.
              # Old KMS policy left commented out after migration to new key
              # - Effect: Allow
              #   Action:
              #     - kms:Decrypt
              #   Resource: "arn:aws:kms:us-east-1:123456789012:key/abcd1234-5678-90ef-ghij-klmnopqrstuv"

      # Embedded environment variables (pulled from old deployment script)
      Environment:
        Variables:
          DB_URI: "postgresql://payment_user:AKIAIOSFODNN7EXAMPLE@legacy-db.internal.pci-secure-zone:5432/payments"
          STRIPE_API_KEY: "sk_live_4eC39HqLyjWDarjtT1zdp7dc"
          # TODO: Rotate this key after PCI audit
          AWS_ACCESS_KEY_ID: "AKIAIOSFODNN7EXAMPLE"
          AWS_SECRET_ACCESS_KEY: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
          LOG_LEVEL: "DEBUG"

# Note: The VPC endpoint configuration is managed in a separate, outdated CloudFormation stack (vpc-legacy.yaml).
# There's a known race condition where the VPC endpoint isn't ready before Lambda assumes the role.
AI Audit Console
> Waiting for pre-flight audit...
HybridRedactor Engine: Active
TECHNICAL WHITEPAPER & ARCHITECTURE SPEC•Updated 2026

How to Fix AWS IAM 'Access Denied' Caused by Missing aws:SourceVpce Condition Key

Threat/Impact Level: CRITICAL | Exploitability/Downtime Risk: HIGH | Time to Fix: 10 mins

TL;DR

  • What broke: Your S3 bucket policy, API Gateway resource policy, or SQS queue policy has no aws:SourceVpce condition, meaning any IAM principal with credentials can hit the resource from the open internet — or your VPC-locked app is getting Access Denied because the condition exists but references the wrong VPCE ID.
  • How to fix it: Add a Deny statement with StringNotEquals: { "aws:SourceVpce": "vpce-xxxxxxxx" } to enforce that all traffic must originate from your designated VPC Endpoint.
  • Shortcut: Use our Client-Side Sandbox above to auto-refactor this — paste your policy, get the corrected version with the condition injected, zero data leaves your browser.

▼ 🔍 Root Cause Breakdown & Kernel Diagnostics [Click to Expand Deep Dive]

The Incident (What does the error mean?)

The raw error from CloudTrail or your application logs looks like this:

{
  "errorCode": "AccessDenied",
  "errorMessage": "User: arn:aws:iam::123456789012:role/app-role is not authorized to perform: s3:GetObject on resource: arn:aws:s3:::my-private-bucket/config.json with an explicit deny in a resource-based policy"
}

Or the inverse — no error, but your security team's audit flags that the bucket is reachable from outside the VPC. Both are the same root cause: the aws:SourceVpce condition is either absent, evaluating to null, or referencing a stale VPCE ID.

When AWS evaluates a condition key that doesn't exist in the request context (e.g., a request arriving over the public S3 endpoint instead of a VPC Endpoint), aws:SourceVpce is absent from the request context entirely. A StringEquals allow condition silently fails. A StringNotEquals deny condition triggers. This is why the behavior flips depending on how you've structured the policy.


▼ 🔍 Detailed Impact & Blast Radius Analysis [Click to Expand Deep Dive]

The Attack Vector / Blast Radius

This is a network perimeter bypass. The entire premise of a VPC Endpoint policy is to ensure your private resources are only reachable from within your network boundary. Without aws:SourceVpce enforced on the resource policy:

  • Credential theft = full data exfiltration. A stolen IAM access key allows an attacker to aws s3 cp s3://my-private-bucket/ . --recursive from their laptop. Your VPC, Security Groups, and NACLs are completely irrelevant at this point — S3, SQS, and API Gateway are public AWS endpoints.
  • Blast radius on S3: Every object in the bucket. Compliance data, PII, application secrets stored as files.
  • Blast radius on API Gateway: Full API invocation from any IP. If your API has write operations, this is RCE-adjacent depending on backend logic.
  • Blast radius on SQS/SNS: Message injection or queue draining — an attacker can poison your job queue or read every message in flight.
  • The aws:SourceIp trap: Many engineers add aws:SourceIp as a fix. This does not work for VPC traffic because traffic from within a VPC to an AWS service traverses the AWS network, not a predictable public IP. Only aws:SourceVpce or aws:SourceVpc reliably locks resources to your VPC boundary.

How to Fix It (The Solution)

Basic Fix — Add the Deny Statement to Your Resource Policy

The safest pattern is an explicit Deny for any request not sourced from your VPCE. A missing condition key evaluates StringNotEquals as true, so the deny fires correctly for public-internet requests.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowRoleAccess",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::123456789012:role/app-role"
      },
      "Action": ["s3:GetObject", "s3:PutObject"],
      "Resource": "arn:aws:s3:::my-private-bucket/*"
    },
+   {
+     "Sid": "DenyNonVPCEAccess",
+     "Effect": "Deny",
+     "Principal": "*",
+     "Action": "s3:*",
+     "Resource": [
+       "arn:aws:s3:::my-private-bucket",
+       "arn:aws:s3:::my-private-bucket/*"
+     ],
+     "Condition": {
+       "StringNotEquals": {
+         "aws:SourceVpce": "vpce-0a1b2c3d4e5f67890"
+       }
+     }
+   }
  ]
}

Enterprise Best Practice — Multi-VPCE + VPC-Level Fallback

In multi-account or multi-region architectures, you'll have more than one VPCE. Lock to the VPC ID as a secondary control and enumerate all valid VPCEs.

-   "Condition": {
-     "StringNotEquals": {
-       "aws:SourceVpce": "vpce-0a1b2c3d4e5f67890"
-     }
-   }
+   "Condition": {
+     "StringNotEquals": {
+       "aws:SourceVpce": [
+         "vpce-0a1b2c3d4e5f67890",
+         "vpce-0f9e8d7c6b5a43210"
+       ]
+     },
+     "StringNotEqualsIfExists": {
+       "aws:SourceVpc": "vpc-0123456789abcdef0"
+     }
+   }

Critical implementation notes:

  • Apply this Deny to both the bucket/resource ARN and the ARN with /* wildcard — missing either leaves a gap.
  • The VPCE must have a VPC Endpoint Policy that also restricts which principals and actions are allowed. Defense in depth: resource policy + endpoint policy.
  • For API Gateway, the condition goes in the aws:sourceVpc field on the resource policy and you set endpointConfiguration.type to PRIVATE — aws:SourceVpce alone is insufficient without also making the API private.
  • Validate your VPCE ID against aws ec2 describe-vpc-endpoints --query 'VpcEndpoints[*].{ID:VpcEndpointId,Service:ServiceName}' — stale IDs after infrastructure rebuilds are the #1 cause of this error in production.

💡 Tired of pasting proprietary infrastructure configs into public LLMs? Generic AI interfaces log your company's ARNs, connection strings, and private keys. StackEngine enforces client-side zero-trust boundaries—the sandbox above executes 100% locally in your browser. Drop your broken manifest above to redact secrets locally and synthesize deterministic patch drafts with your own API key.

(For CI/CD pipeline automation, install via npm i -g stackengine).


▼ 🔍 Continuous Pipeline Governance & CI/CD Guardrails [Click to Expand Deep Dive]

Prevention in CI/CD

1. Checkov — Catch missing VPCE conditions at plan time

Checkov rule CKV_AWS_70 checks S3 bucket policies for VPC-restricted access. Wire it into your pipeline:

checkov -d ./terraform --check CKV_AWS_70 --compact

For custom policies not covered by built-in rules, write a custom Checkov check or use checkov --external-checks-dir ./custom_checks.

2. OPA/Conftest — Enforce VPCE conditions on any resource policy

# policy/vpce_required.rego
package aws.iam

deny[msg] {
  stmt := input.Statement[_]
  stmt.Effect == "Allow"
  stmt.Principal == "*"
  not has_vpce_condition(stmt)
  msg := sprintf("Statement '%v' allows public principal without aws:SourceVpce deny condition", [stmt.Sid])
}

has_vpce_condition(stmt) {
  stmt.Condition.StringNotEquals["aws:SourceVpce"]
}

Run in CI: conftest test policy.json --policy policy/

3. AWS Config Rule — Continuous compliance in production

Deploy the managed rule s3-bucket-policy-not-more-permissive and supplement with a custom Lambda-backed Config rule that parses bucket policies for the presence of a StringNotEquals block on aws:SourceVpce. Alert to your Security Hub on violation.

4. Terraform — Codify the condition so it can never be omitted

Create a Terraform module for private bucket creation that hardcodes the deny statement, accepting vpce_ids as a required variable with validation:

variable "vpce_ids" {
  type        = list(string)
  description = "List of VPC Endpoint IDs permitted to access this bucket."
  validation {
    condition     = length(var.vpce_ids) > 0
    error_message = "At least one VPCE ID must be specified. Public bucket access is not permitted."
  }
}

If vpce_ids is empty, terraform plan fails. The misconfiguration never reaches AWS.

Official Grounding & Normative Specifications

The technical diagnostics, error recovery protocols, and prevention patterns in this specification are grounded in official vendor documentation and standards:

Diagnostic & Architectural FAQ

Q1.Why does aws:SourceVpce cause Access Denied even when my IAM role has the correct permissions?
IAM's explicit Deny always overrides Allow. The root cause is a missing `aws:SourceVpce` key in the request context when traffic bypasses your VPC Endpoint, causing a `StringNotEquals` condition in a Deny statement to evaluate to true. This is a fundamental, non-negotiable security control failure. You can manually patch the policy, but one misconfigured condition will lock out your entire team. Drop the broken manifest into the StackEngine Local Sandbox above to automatically compile a zero-trust fix that enforces the VPCE without breaking existing access.
Q2.What is the difference between aws:SourceVpce and aws:SourceVpc, and which should I use?
`aws:SourceVpce` pins to a specific endpoint ID for surgical control; `aws:SourceVpc` is a broader VPC-level control. Manually choosing and maintaining these conditions is a trap—mismatch them, and you either create a gaping security hole or trigger a cascading outage. The blast radius from a manual error here can take down multi-region deployments. Do not attempt to hand-roll this. Drop the broken manifest into the StackEngine Local Sandbox above to automatically compile a zero-trust fix that dynamically validates and enforces the correct network boundary for your architecture.
Q3.Does adding aws:SourceVpce to a bucket policy break cross-account access or AWS service integrations like Lambda or CloudTrail?
Absolutely—and it will silently break compliance audits. AWS service principals don't send `aws:SourceVpce`, so a blanket Deny will cripple critical integrations like CloudTrail logging or Lambda executions. Manually scoping exceptions with `Null` checks and principal whitelists is a compliance nightmare at scale, inviting shadow IT and policy drift. For enterprise mono-repos or fleet-wide remediation, manually fixing this is untenable. Upgrade to the StackEngine CI/CD Automator (Team Edition) for on-prem, automated CI/CD policy synthesis that enforces zero-trust without breaking AWS service dependencies.