PAYCARGO · AI-SANDBOX · PROCESS Step 3 Supplement

Routing Tables

The nine route tables in the AI-sandbox VPC, what each routes where, and why the routing layer (not the firewall) is the deepest enforcement point.
Related: Step.3.html (Network apply) · Step.3.db-access-boundaries.html (DB access doctrine) · Source of truth: live VPC vpc-0ed3a16a8b82f7a34

1. Why this doc exists

The AI-sandbox VPC has nine subnets and nine route tables (one per subnet). Three of the tables have no default route to the internet by design — that is the lockbox isolation guarantee, expressed in routing rather than in policy. This document walks each table, shows what it routes where, and explains why the route table layer is the strongest place to enforce "lockbox cannot reach the internet."

The one-line summary

Lockbox subnets have no 0.0.0.0/0 route in any route table. The internet is not unreachable because of a policy decision — it is unreachable because there is no route to take. Firewalls, security groups, and IAM are layered on top, but the routing layer is the floor.

2. The three tier patterns

Each route table belongs to exactly one of three patterns. The pattern is determined by the subnet it is associated with.

TierSubnets (3 AZs)Default routeInternet reachable from workloads here?
Agentai-sandbox-agent-private-us-east-1a
...-1b
...-1c
0.0.0.0/0 → NFW endpointYes — via NFW → NAT → IGW
Lockboxai-sandbox-lockbox-private-us-east-1a
...-1b
...-1c
(no default route)No. Structurally impossible.
Egressai-sandbox-egress-us-east-1a
...-1b
...-1c
0.0.0.0/0 → IGWYes — directly. This is the only tier with an IGW route.

3. Agent route tables (3 AZs, all identical pattern)

Agent route table

ai-sandbox-rt-agent-private-us-east-1a (and -1b, -1c)
DestinationTargetWhat it does
10.0.0.0/16local (implicit)Traffic inside the VPC stays inside; never leaves.
0.0.0.0/0vpce-0119843d5454e1ed0 (NFW endpoint)Default route. Every packet not bound for the VPC itself goes to the Network Firewall first.
S3 prefix listvpce-04ccfcc0152402128 (S3 gateway endpoint)S3 traffic skips NFW, goes private via the gateway endpoint. Free.
DDB prefix listvpce-0675c4f8582974d54 (DDB gateway endpoint)DynamoDB traffic skips NFW, goes private via the gateway endpoint. Free.
Key property: the default route points to the NFW endpoint. There is no way for an agent workload's outbound packet to skip the firewall.

Why S3 and DDB get their own routes

Without the gateway endpoint routes, S3 and DDB traffic would match 0.0.0.0/0 and route through NFW → NAT → IGW — paying NAT data charges, generating NFW alerts, and traversing the public internet to reach an AWS service. Adding the prefix-list routes keeps the traffic private and cheap.

4. Lockbox route tables (3 AZs, all identical pattern)

Lockbox route table

ai-sandbox-rt-lockbox-private-us-east-1a (and -1b, -1c)
DestinationTargetWhat it does
10.0.0.0/16local (implicit)Traffic inside the VPC stays inside.
0.0.0.0/0(no entry — intentional)No default route. Packets destined for the internet have no target.
S3 prefix listvpce-04ccfcc0152402128 (S3 gateway endpoint)S3 traffic goes private.
DDB prefix listvpce-0675c4f8582974d54 (DDB gateway endpoint)DDB traffic goes private.
Key property: no 0.0.0.0/0 entry exists. A packet from a lockbox workload destined for the public internet has nowhere to route. The internet is unreachable at the routing layer.

Why "no default route" is stronger than "deny in firewall"

Three independent failure modes are eliminated by removing the route rather than blocking at the firewall:

  1. Firewall misconfiguration is irrelevant. NFW rule group could be wide-open and lockbox still cannot egress.
  2. Security group escape is irrelevant. SG could be wide-open and lockbox still cannot egress.
  3. IAM over-permissioning is irrelevant. A workload could have *:* IAM permissions and still cannot egress.

Every one of those layers exists too — defense in depth — but each could in principle be misconfigured. The routing layer is what is left when every other layer is bypassed: there is simply no path for the packet.

This is the "structural" property in the project doctrine

When the doctrine says "lockbox cannot reach the internet," this is what it means. Not "the firewall blocks it." Not "the SG denies it." There is no route. Bypassing every other control still leaves you with a packet that has no next hop.

5. Egress route tables (3 AZs, all identical pattern)

Egress route table

ai-sandbox-rt-egress-us-east-1a (and -1b, -1c)
DestinationTargetWhat it does
10.0.0.0/16local (implicit)Traffic inside the VPC stays inside.
0.0.0.0/0igw-0c71813f4d0c94a01 (Internet Gateway)Default route. This is the only tier that points 0.0.0.0/0 at the IGW.
Key property: the only subnets with a direct IGW route. No workloads live here — just NFW endpoint ENIs, the NAT Gateway, and the EIP. The egress subnets are the only public-facing surface in the VPC.

Why egress subnets do not have gateway endpoint routes

S3 and DDB traffic originating from the egress subnet should be vanishingly rare (NFW endpoint is the only workload). If it does happen (NFW or NAT making AWS API calls), it goes through the public path. Adding gateway endpoint routes here would be unused complexity.

6. Packet traces (worked examples)

A) Agent calls api.salesforce.com (allowed in alert mode, allowed in drop mode)

Agent ENI in agent-private subnet
   |
   v
Agent route table: 0.0.0.0/0 → NFW endpoint
   |
   v
NFW endpoint ENI in egress subnet, us-east-1a
   | (TLS SNI inspected: matches stateful allowlist for *.salesforce.com)
   v
Egress route table: 0.0.0.0/0 → IGW (after NFW forwards)
   | (but actually, NFW post-inspection traffic goes through NAT first)
   v
NAT Gateway in egress subnet
   | (source-NAT to EIP)
   v
IGW
   |
   v
Public internet → api.salesforce.com

B) Agent calls httpbin.org (alert mode: succeeds; drop mode: blocked)

Agent ENI → NFW endpoint
   | (TLS SNI inspected: does NOT match allowlist)
   |
   | ALERT MODE: stateful default action = alert_strict
   |   — alert log entry created
   |   — traffic still forwarded to NAT
   |
   | DROP MODE: stateful default action = drop_strict + alert_strict
   |   — alert log entry created
   |   — packet dropped at NFW endpoint
   |   — TCP timeout from caller's perspective

C) Lockbox workload calls api.gitbook.com

Lockbox ENI in lockbox-private subnet
   |
   v
Lockbox route table: 10.0.0.0/16 → local
   | (no other route matches the destination IP)
   |
   v
Packet has no next hop. Dropped at the VPC level.

This is independent of NFW, SG, IAM. There is no path.

D) Agent calls Bedrock (Claude invocation)

Agent ENI → bedrock-runtime endpoint ENI (in same agent subnet)
   |
   v
Bedrock VPC endpoint
   |
   v
AWS Bedrock service plane (private; no internet hop)

E) Agent calls DynamoDB

Agent ENI in agent-private subnet
   |
   v
Agent route table: DDB prefix list → DDB gateway endpoint
   |
   v
DDB gateway endpoint policy: "may access table/ai-sandbox-agent-*"
   |
   v
DynamoDB service plane

Lockbox table: agent attempting this path gets AccessDenied from the
gateway endpoint policy, before the DDB API is even consulted.
See Step.3.db-access-boundaries.html for the full layer stack.

7. Topology summary

┌─────────────────────────────── ai-sandbox VPC (10.0.0.0/16) ───────────────────────────┐
│                                                                                        │
│  ┌─ AGENT TIER (3 AZs) ────────┐  ┌─ LOCKBOX TIER (3 AZs) ───┐  ┌─ EGRESS TIER ──┐    │
│  │  agent-private-1a (/22)     │  │  lockbox-private-1a (/24) │  │  egress-1a    │    │
│  │  agent-private-1b (/22)     │  │  lockbox-private-1b (/24) │  │  egress-1b    │    │
│  │  agent-private-1c (/22)     │  │  lockbox-private-1c (/24) │  │  egress-1c    │    │
│  │                             │  │                           │  │               │    │
│  │  routes:                    │  │  routes:                  │  │  routes:      │    │
│  │  • 10.0.0.0/16 → local      │  │  • 10.0.0.0/16 → local    │  │  • 10.0.0.0/16│    │
│  │  • 0.0.0.0/0 → NFW endpoint │  │  • (no 0.0.0.0/0)         │  │      → local  │    │
│  │  • S3 prefix → S3 GW EP     │  │  • S3 prefix → S3 GW EP   │  │  • 0.0.0.0/0  │    │
│  │  • DDB prefix → DDB GW EP   │  │  • DDB prefix → DDB GW EP │  │      → IGW    │    │
│  └─────────────────────────────┘  └───────────────────────────┘  └───────────────┘    │
│                                                                                        │
└────────────────────────────────────────────────────────────────────────────────────────┘
                                                                            │
                                  ┌─ Internet Gateway (igw-0c71813f4d0c94a01)
                                  │   reached only from egress route tables
                                  ▼
                              Public internet

8. How to view the routing tables in AWS Console

The route tables are the strongest single artifact of the lockbox isolation doctrine. They are also one of the easiest things to read in the Console — each table is a short list of destination/target rows.

WhatWhere to lookWhat to see
All 9 route tables in the VPC VPC → Route tables → filter by VPC vpc-0ed3a16a8b82f7a34 Nine rows. Names match the pattern ai-sandbox-rt-{tier}-{az}. Three each of agent, lockbox, egress.
The structural lockbox isolation Filter Name = ai-sandbox-rt-lockbox → click any one → Routes tab Three or four entries total: local route, S3 prefix list, DDB prefix list. No 0.0.0.0/0 entry. Compare to any agent or egress route table where 0.0.0.0/0 appears prominently. This is the visual proof that lockbox cannot reach the internet at the routing layer.
The agent default route to NFW Filter Name = ai-sandbox-rt-agent → click any one → Routes tab 0.0.0.0/0 destination, target column shows vpce-0119843d5454e1ed0 (the NFW endpoint ID). Hovering on the target shows the endpoint name.
The egress direct IGW route Filter Name = ai-sandbox-rt-egress → click any one → Routes tab 0.0.0.0/0 destination, target column shows the IGW igw-0c71813f4d0c94a01. Only the egress tables have this. Confirms the egress tier is the only subnet that can directly reach the internet.
Subnet associations Any route table → Subnet associations tab Each route table is associated to exactly one subnet. Confirms the per-subnet routing model — no subnet falls back to the main route table or shares a route table with a different tier.

The "Resource map" view of routing

VPC → click ai-sandbox-vpc → Resource map tab. AWS draws the entire VPC graph. Route tables appear between subnets and their downstream targets (IGW, NFW endpoint, NAT). The visual is reading-time-equivalent to reading the table rows above, but easier to absorb. The lockbox subnets visibly do not have an arrow leading to the IGW — the doctrine, as a picture.

What this looks like 1 year from now in an incident

Someone reports "a lockbox workload reached out to an unauthorized external endpoint." First place to check: the lockbox route table for the affected AZ. If 0.0.0.0/0 is still absent (the structural property is intact), then either the traffic was misattributed, or it went through a different mechanism (an interface endpoint, a service-link path), or someone has been editing route tables manually. The Console's route table page is the first stop in that triage.

9. How to verify these tables on the live VPC (CLI)

# All 9 route tables in this VPC
AWS_PROFILE=ai-sandbox aws ec2 describe-route-tables \
  --filters "Name=vpc-id,Values=vpc-0ed3a16a8b82f7a34" \
  --query 'RouteTables[].{Name:Tags[?Key==`Name`].Value|[0],Routes:Routes}' \
  --output json

# Verify lockbox tables have NO 0.0.0.0/0 entry
AWS_PROFILE=ai-sandbox aws ec2 describe-route-tables \
  --filters "Name=vpc-id,Values=vpc-0ed3a16a8b82f7a34" \
  --query 'RouteTables[?Tags[?Key==`Name`&&starts_with(Value, `ai-sandbox-rt-lockbox`)]].Routes[?DestinationCidrBlock==`0.0.0.0/0`]' \
  --output text
# Expected: empty (no routes returned). If any rows print, that is a doctrine violation.

# Verify agent tables route 0.0.0.0/0 to the NFW endpoint
AWS_PROFILE=ai-sandbox aws ec2 describe-route-tables \
  --filters "Name=vpc-id,Values=vpc-0ed3a16a8b82f7a34" \
  --query 'RouteTables[?Tags[?Key==`Name`&&starts_with(Value, `ai-sandbox-rt-agent`)]].Routes[?DestinationCidrBlock==`0.0.0.0/0`].GatewayId' \
  --output text
# Expected: three lines (one per AZ), all matching the NFW endpoint vpce-* ID.