PAYCARGO · AI-SANDBOX · PROCESS Step 3 Supplement

DB Access Boundaries

Both the agent tier and the lockbox tier can reach DynamoDB. They share network reachability. They do not share a data store. This document explains the four independent layers that enforce that.
Related: Step.2.html (Identity / KMS / tier roles) · Step.3.html (Network) · Step.3.routing-tables.html (routing detail)

1. The question this answers

The agent tier's route tables and the lockbox tier's route tables both include a route to the DynamoDB gateway endpoint. So both tiers can reach DynamoDB.

Will they share a data store? How is the isolation actually enforced?

The one-line answer

They do not share tables. Different tables, different encryption keys, different IAM policies. Network reachability to DynamoDB is shared, but four independent layers above the network ensure that an agent calling the lockbox table gets AccessDenied — and for the same call to succeed, all four layers would have to be simultaneously misconfigured.

2. Different tables, different encryption

TierTable(s)PurposeEncrypted with
Lockbox ai-sandbox-lockbox-tokens Token → PII map. Holds actual sensitive customer data (Customer / Branch / Contact PII) as ciphertext. Lockbox CMK alias/ai-sandbox-lockbox
Agent ai-sandbox-agent-state (and per-agent state tables) Agent conversation state, scratch references, opaque tokens. Never PII. Agent CMK alias/ai-sandbox-agent

So even before any of the four enforcement layers fire, an agent calling DDB cannot accidentally read the lockbox table by name — it's a different resource ARN. The four layers exist to make accessing it impossible even if the ARN is known and explicitly targeted.

3. The four enforcement layers

Reading top-down, each layer is independent of the others. A misconfiguration in one is caught by the next.

┌─────────────────────────────────────────────────────┐ │ │ │ AGENT WORKLOAD tries to read lockbox table │ │ │ └───────────────────────┬─────────────────────────────┘ │ Layer 1 ──────────▼───── Network endpoint policy │ (DDB gateway endpoint scopes which │ tables which subnets may access) │ Layer 2 ──────────▼───── IAM tier role policy │ (sor-reader / internal capability flags; │ lockbox service role is the only role │ with full lockbox table access) │ Layer 3 ──────────▼───── KMS key policy (cryptographic) │ (Lockbox CMK policy explicitly excludes │ agent role principals; AccessDenied at │ the cryptographic operation level) │ Layer 4 ──────────▼───── Reveal Lambda chokepoint (application) │ (only path to detokenized PII; │ runs auth check + audit log + decrypt) ▼ PII (or AccessDenied at any layer)

4. Layer 1 — Gateway endpoint policy (network)

Layer 1 · Network

DDB gateway endpoint resource policy

The DynamoDB gateway endpoint has a resource policy. The policy is designed (in Step 4a, the Data module) to scope which subnets may access which tables. This is the lowest level of enforcement — it happens at the VPC endpoint itself, before the DDB API even receives the request.

Expected policy shape

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AgentSubnetsAccessAgentTablesOnly",
      "Effect": "Allow",
      "Principal": "*",
      "Action": [
        "dynamodb:GetItem", "dynamodb:PutItem",
        "dynamodb:UpdateItem", "dynamodb:DeleteItem",
        "dynamodb:Query", "dynamodb:Scan"
      ],
      "Resource": [
        "arn:aws:dynamodb:us-east-1:959228203854:table/ai-sandbox-agent-*"
      ],
      "Condition": {
        "StringEquals": {
          "aws:SourceVpc": "vpc-0ed3a16a8b82f7a34"
        },
        "StringLike": {
          "aws:PrincipalTag/tier": ["sor-reader", "internal"]
        }
      }
    },
    {
      "Sid": "LockboxRoleAccessesLockboxTables",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "*",
      "Resource": [
        "arn:aws:dynamodb:us-east-1:959228203854:table/ai-sandbox-lockbox-*"
      ],
      "Condition": {
        "ArnEquals": {
          "aws:PrincipalArn": "arn:aws:iam::959228203854:role/ai-sandbox-lockbox-service"
        }
      }
    }
  ]
}

Effect: an agent workload trying to query the lockbox table gets AccessDenied at the VPC endpoint, before the DDB API is ever consulted. The DDB API has no idea the request was attempted.

Note: this policy ships with the Data module in Step 4a. The DDB gateway endpoint is currently deployed without a custom policy (Step 3); the default policy allows all access. Layer 1 will be locked down when Step 4a applies.

5. Layer 2 — IAM tier role policies (identity)

Layer 2 · Identity

The tier role capability flags

The tier roles deployed in Step 2 have explicit boolean flags expressing what each tier may do:

Tier rolecan_read_lockbox_tokenscan_call_lockbox_revealEffective DDB access on lockbox tables
sor-reader true false Can GetItem on the lockbox table (gets back encrypted ciphertext); cannot detokenize.
internal false false Cannot touch the lockbox table at all (IAM policy does not include the resource).
Lockbox service role (Step 4a) n/a (it is the reveal Lambda's role) Full read/write on lockbox table. This is the only role with that capability.

Effect: even if Layer 1 (endpoint policy) was leaking, an internal-tier agent calling GetItem on the lockbox table gets AccessDenied from IAM.

6. Layer 3 — KMS key policy (cryptographic)

Layer 3 · Cryptographic

The lockbox CMK key policy

Verified live in Step 2. The lockbox CMK's key policy contains exactly two principal entries:

"Statement": [
  {
    "Sid": "EnableRootAccountAdmin",
    "Effect": "Allow",
    "Principal": "arn:aws:iam::959228203854:root"
  },
  {
    "Sid": "AllowLockboxStorageServices",
    "Effect": "Allow",
    "Principal": [
      "s3.amazonaws.com",
      "dynamodb.amazonaws.com"
    ]
  }
]

No agent tier role appears anywhere in this policy.

Effect: even if both Layer 1 and Layer 2 leaked, an agent receiving encrypted lockbox ciphertext cannot decrypt it. The agent's kms:Decrypt call returns AccessDenied at the cryptographic operation, not at the IAM API. The KMS layer is the deepest possible policy enforcement in AWS — it happens at the actual crypto operation, regardless of any IAM consideration above it.

This is the property called “structural PII-blindness of agents” in the project doctrine. It was the centerpiece verification of Step 2.

7. Layer 4 — Reveal Lambda chokepoint (application)

Layer 4 · Application

The only legitimate path to detokenized PII

Layer 4 exists for the case where an agent legitimately needs the detokenized PII. The pattern: agent never decrypts directly; it asks the reveal Lambda to do it, and the reveal Lambda enforces the access policy.

Agent (has token "tok_abc123")
   │
   │ HTTPS 443 → lockbox-sg
   │   (lockbox-sg only accepts inbound from agent-sg)
   ▼
Reveal Lambda (running in lockbox tier, assumes lockbox service role)
   │
   ├── Authorization check
   │   • caller's IAM role must have can_call_lockbox_reveal=true
   │   • reveal purpose must be in the allowed-reason list
   │   • caller has not exceeded rate limit
   │
   ├── Audit log entry (encrypted with audit CMK)
   │   • caller role, token, timestamp, reason, IP
   │   • this is the SecOps reveal trail
   │
   ├── DDB GetItem on lockbox table
   │   • lockbox service role has access (Layer 2 allows it for this role)
   │   • lockbox CMK decrypts because lockbox service role is in key policy
   │
   └── return PII to caller
                  │
                  ▼
       Caller (now has PII in memory; SHOULD NOT log it; ephemeral use only)

Why this is necessary: there are legitimate operational reasons for an authorized workflow to need the underlying PII (sending an email to a customer, displaying details in a UI, etc.). Layer 4 is the controlled, audited, rate-limited path for that case. Layers 1-3 ensure it is the only path.

8. Threat model: what gets caught where

Threat / failure mode Caught at How
Agent tries to GetItem on ai-sandbox-lockbox-tokens directly Layer 1 Gateway endpoint policy denies cross-tier table access. DDB API never receives the request.
Layer 1 misconfigured; agent reaches DDB API Layer 2 IAM tier role policy denies access to lockbox table resource.
Layer 1 and Layer 2 both misconfigured; agent receives encrypted item Layer 3 KMS lockbox key policy refuses agent role's kms:Decrypt call. Item remains ciphertext.
Agent uses the legitimate reveal path with elevated permissions Layer 4 Reveal Lambda authorization check (role, purpose, rate limit). Every reveal audit-logged.
Compromised lockbox service role itself (worst case) Audit trail Every reveal is logged with caller and purpose. Anomaly detection (Step 4b observability) catches unusual reveal patterns.

For an agent to successfully exfiltrate lockbox PII, four independent configurations would have to be simultaneously wrong. None of these configurations is changed by a single edit; each lives in a different module (network, identity, identity, data) and each requires explicit operator action to relax.

9. Why we share the DDB endpoint rather than have two

It would be possible to deploy two DDB gateway endpoints — one for agent subnets, one for lockbox subnets — and let only the appropriate route tables reach each. We chose one shared endpoint with a scoped policy because:

  1. Cost. Gateway endpoints are free, but operationally one is simpler to monitor and audit than two.
  2. Enforcement quality. The endpoint policy is the deepest possible scoping for DDB access from inside the VPC. Splitting into two endpoints would push the scope decision into the route table layer, which is coarser-grained (table prefixes vs full resource ARNs and conditions).
  3. Auditability. A single endpoint policy is a single artifact in code review. Two endpoints with different policies is two artifacts and the diff between them.
  4. Reachability is not access. The doctrine assumes — and demonstrates — that reachability does not constitute access. Building infrastructure that pretends otherwise would teach the wrong lesson.

The shared endpoint is a feature of the design, not a compromise.

10. What is enforced today (post-Step 3) vs. after Step 4a

LayerStatus after Step 3 (current)Status after Step 4a (Data module)
Layer 1 (endpoint policy) Default permit-all policy on the DDB and S3 gateway endpoints Scoped policy denying cross-tier table access
Layer 2 (IAM tier policies) Tier role policies are live (Step 2). The lockbox service role does not exist yet. Lockbox service role created in data module; only role with full lockbox table access
Layer 3 (KMS lockbox key policy) Live and verified (Step 2). Agent roles excluded from lockbox CMK. Unchanged. Still enforced.
Layer 4 (reveal Lambda) Does not exist yet. Deployed by data module with the authorization + audit pattern above.

Caveat: today (post-Step 3) the lockbox table does not exist yet

Layers 1 and 4 are described in this document for completeness, but the resources they protect (the lockbox table itself, the reveal Lambda) only land in Step 4a. Until then, there is nothing on the lockbox side of the boundary to leak. Layers 2 and 3 already exist and would enforce the boundary the moment a lockbox table appeared, even before Step 4a wires Layers 1 and 4.

11. How to view what is enforced today in AWS Console

Two of the four layers exist today (post-Step 3); two are deployed in Step 4a. The viewable artifacts mirror that split.

Today (post-Step 3): Layers 2 and 3 are visible

LayerWhere to lookWhat confirms enforcement
Layer 2 · sor-reader tier policy IAM → Roles → ai-sandbox-agent-sor-reader → Permissions tab → click the inline policy tier-sor-reader-permissions → JSON The ReadLockboxTokens Sid is present but its Action is read-only DDB operations (GetItem, BatchGetItem, Query). No PutItem, no DeleteItem, no UpdateItem. So even at the IAM layer, a sor-reader role can see the encrypted token but cannot modify the lockbox table.
Layer 2 · internal tier policy IAM → Roles → ai-sandbox-agent-internal → Permissions tab → JSON No ReadLockboxTokens Sid appears at all. The internal tier role has no IAM-level access to the lockbox table. Compare side-by-side with sor-reader for the diff.
Layer 3 · Lockbox CMK key policy (the centerpiece) KMS → Customer managed keys → ai-sandbox-lockbox → Key policy tab Two principal blocks: arn:aws:iam::959228203854:root (admin), and the storage services s3.amazonaws.com / dynamodb.amazonaws.com. No agent role appears. This is the structural PII-blindness guarantee, as data, enforced at the cryptographic operation layer.
Layer 3 · Agent CMK key policy (for contrast) KMS → ai-sandbox-agent → Key policy tab Agent tier role principals do appear here. So an agent can decrypt with the agent CMK; reading the two key policies side-by-side makes the boundary visible.
Permission boundary on both tier roles IAM → ai-sandbox-agent-sor-reader → Permissions tab → scroll to "Permissions boundary" Shows ai-sandbox-permission-boundary attached. Caps everything else above.

After Step 4a: Layers 1 and 4 become viewable

LayerWhere to look (after Step 4a)What confirms enforcement
Layer 1 · DDB gateway endpoint policy VPC → Endpoints → click the DDB gateway endpoint vpce-0675c4f8582974d54 → Policy tab Custom policy (replaces the current permit-all default) scoping which subnets can access which tables. Cross-tier table access denied at the network layer.
Layer 1 · S3 gateway endpoint policy VPC → Endpoints → click the S3 gateway endpoint vpce-04ccfcc0152402128 → Policy tab Same pattern for S3 buckets. Agent subnets can reach agent buckets; lockbox subnets can reach lockbox buckets; cross-access denied.
Layer 4 · Reveal Lambda Lambda → Functions → ai-sandbox-lockbox-reveal VPC config: attached to lockbox-private subnets. Permissions: assumes a lockbox service role with full lockbox table access. Environment: audit log group ARN, allowed-caller list, rate limit. Inbound: lockbox-sg allows agent-sg on 443 only.
Layer 4 · The reveal audit log CloudWatch → Log groups → /aws/lambda/ai-sandbox-lockbox-reveal-audit Encrypted with audit CMK. Every reveal call generates one log entry: caller role, token, timestamp, reason, IP. Reviewable by SecOps.

The viewing sequence that builds the mental model

  1. Open the lockbox CMK key policy. Spend a minute reading the JSON. Note no agent role.
  2. Open the agent CMK key policy in another tab. Note the agent roles ARE listed. Side-by-side diff = the boundary visualized.
  3. Open the sor-reader inline policy. Find the ReadLockboxTokens Sid. Note it's read-only.
  4. Open the internal inline policy in another tab. Search for ReadLockbox. Find nothing. Compare.
  5. After Step 4a lands: open the DDB gateway endpoint policy. The endpoint that today permits all access becomes the structural enforcer of "which subnets reach which tables."

What this looks like in an incident

"An agent appears to have read PII it should not have." The investigation walks the four layers backward: did the application gate fail (reveal Lambda logs)? did KMS allow the decrypt (CloudTrail KMS events)? did IAM allow the GetItem (CloudTrail DDB events)? did the endpoint policy allow the network path (VPC flow logs)? Each layer has a separate audit trail. A single misconfiguration is bounded by the next layer; the audit trail says where it stopped.

12. Doctrine: shared reachability, separated access

The pattern in this document — shared network reachability, independent access controls at multiple layers above — appears repeatedly in the AI-sandbox doctrine:

The general principle: reachability at one layer is acceptable as long as access at higher layers is properly scoped. Removing reachability (running two endpoints, two VPCs, two subnets) is more expensive, more complex, and provides only marginal additional security over a well-scoped multi-layer policy stack. The discipline is to ensure the layers above are actually scoped, not assumed to be.

The takeaway

Agents and lockbox can both reach DynamoDB. They cannot share a table, share encryption keys, share IAM permissions, or share decrypted PII. Four independent enforcement layers ensure that. Sharing the endpoint is a deliberate choice that lets the strongest layer (the endpoint policy itself) be the table-scoping mechanism. The doctrine is “reachability is not access.”