> ## Documentation Index
> Fetch the complete documentation index at: https://docs.aviatrix.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Secure AWS Bedrock AgentCore Agent Workloads

> Step-by-step instructions for applying Aviatrix DCF containment to AWS Bedrock AgentCore agent workloads: spoke gateway insertion, IAM guardrails, SmartGroup and WebGroup policy, selective TLS decryption, and East-West containment.

When complete, every AgentCore Runtime session's egress will be intercepted at
the Aviatrix spoke gateway before leaving the VPC. Default-deny DCF policy
permits only declared model providers, tool endpoints, and AWS service
destinations. An IAM guardrail prevents any Runtime from being created in PUBLIC
mode or outside the landing zone, closing the bypass at the AWS API level before
any packet flows. East-West SmartGroup policy prevents a compromised agent from
pivoting to adjacent workload spokes in the transit fabric.

For the business case and architecture overview, see <a href={"/docs/enterprise/" + "10.1" +
"/solutions/security-for-ai/ai-agent-workload-security"}>Securing AI Agent
Workloads</a>.

## Prerequisites

* Aviatrix Controller 8.1 or later (for subnet-type SmartGroups and FQDN
  SmartGroups)
* Controller 9.0 or later required if you enable selective TLS decryption for
  URL-path enforcement
* <a href={"/docs/enterprise/" + "10.1" + "/concepts-architectures/components/controlplane/copilot-overview"}>CoPilot</a>
  4.31 or later
* <a href={"/docs/enterprise/" + "10.1" + "/guides/security/dcf/dcf-enable-feature"}>DCF for Spoke Gateways enabled</a>
  with **Log Enrichment** turned on
* A <a href={"/docs/enterprise/" + "10.1" +
  "/guides/platform-administration/cloud-account-create"}>cloud account
  onboarded in CoPilot</a> for the AWS account hosting the AgentCore workload
* AgentCore configured in **VPC mode** (PUBLIC mode has no enforcement point and
  must not be used in production)

## How it works

AgentCore VPC mode drops Elastic Network Interfaces (ENIs) into your designated
agent subnet. All outbound traffic (tool calls, model provider requests, MCP
server connections, code interpreter egress) leaves the VPC through that
subnet's route table. Pointing the route table's default route at the Aviatrix
spoke gateway puts all agent egress in-path for DCF enforcement before it
reaches the internet gateway.

An Aviatrix spoke gateway attached to your enterprise Transit provides both the
enforcement point and the East-West containment boundary. DCF evaluates every
egress connection against declared policy: AWS service endpoints are permitted
without decryption, sanctioned tool destinations are permitted with selective
TLS decryption scoped only to the hosts where URL-path inspection is needed, and
everything else is denied and logged.

Supply-chain enforcement uses a two-rule pattern. A DENY rule at priority 29
decrypts traffic to GitHub hosts and blocks connections whose URL paths match
known indicators of compromise. A PERMIT rule at priority 31 then allows
legitimate traffic to those same hosts to proceed. Decryption is scoped to a
GitHub-specific FQDN SmartGroup so AWS service traffic (ECR, Bedrock, STS) is
never decrypted, preserving certificate-chain behavior for those endpoints.

The IAM guardrail uses three separate policy statements because the IAM
condition operator behaves differently depending on whether the subnets key is
present in the request. A `Null` condition catches PUBLIC-mode creates, where no
subnet key is sent at all. Two `ForAnyValue:StringNotEquals` conditions then
catch VPC-mode creates with foreign subnets or security groups.

## Procedure

<Tabs>
  <Tab title="CoPilot UI">
    <Steps>
      <Step title="Deploy an Aviatrix spoke gateway in the cluster VPC">
        An Aviatrix spoke gateway in the same VPC as your Kubernetes cluster is the
        enforcement point where the Aviatrix controller applies containment policy to
        MCP server egress traffic.

        1. In CoPilot, navigate to **Cloud Fabric > Gateways > Spoke Gateways** and
           click **+ Spoke Gateway**.

        2. Provide the following details:

           | Parameter         | Value                                                                           |
           | ----------------- | ------------------------------------------------------------------------------- |
           | **Name**          | A name for the gateway, for example `mcp-containment-spoke`                     |
           | **Cloud**         | The cloud provider hosting the Kubernetes cluster                               |
           | **Account**       | The cloud account for the cluster                                               |
           | **Region**        | The region where the cluster runs                                               |
           | **VPC/VNet**      | The VPC or VNet where the cluster nodes run — this must match the cluster's VPC |
           | **Instance Size** | Select an instance size appropriate for your environment                        |

        3. In the **Instances** section, select a subnet in the cluster VPC for the
           gateway instance.

        4. Click **Save**.

        Wait for the gateway status to show **Up** before continuing. You can monitor
        progress at **CoPilot > Monitor > Notifications > Tasks**.
      </Step>

      <Step title="Configure the agent subnet route table">
        In the AWS Console, locate the route table associated with the AgentCore agent
        subnet and add a default route pointing to the Aviatrix spoke gateway.

        1. In the AWS Console, navigate to **VPC > Route Tables** and select the route
           table associated with the AgentCore agent subnet.

        2. Select **Edit routes** and add the following entry:

           | Destination | Target                                               |
           | ----------- | ---------------------------------------------------- |
           | `0.0.0.0/0` | The Aviatrix spoke gateway's network interface (ENI) |

        3. Click **Save changes**.

        To find the spoke gateway's ENI, navigate to **Cloud Fabric > Gateways > Spoke
        Gateways** in CoPilot, select the gateway, and locate the **Private IP** in the
        gateway details. Use this IP to identify the corresponding ENI in the AWS
        Console under **EC2 > Network Interfaces**.

        <Note>
          Do not apply this route to subnets hosting interface VPC endpoints for Bedrock
          AgentCore data plane and control plane. Those subnets route directly to their
          endpoints and must not traverse the spoke gateway.
        </Note>
      </Step>

      <Step title="Deploy the IAM guardrail">
        The IAM guardrail requires three separate Deny statements because IAM condition
        operators behave differently when a request key is absent versus present. A
        single `ForAllValues` statement cannot catch PUBLIC-mode creates, because
        `ForAllValues` on a missing key evaluates to false.

        In the AWS Console, create a managed policy with the following document and
        attach it to all IAM principals that can create AgentCore resources:

        ```json theme={null}
        {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Sid": "DenyPublicModeCreate",
              "Effect": "Deny",
              "Action": [
                "bedrock-agentcore:CreateAgentRuntime",
                "bedrock-agentcore:CreateAgentRuntimeEndpoint",
                "bedrock-agentcore:UpdateAgentRuntime",
                "bedrock-agentcore:CreateBrowser",
                "bedrock-agentcore:CreateCodeInterpreter"
              ],
              "Resource": "*",
              "Condition": {
                "Null": {
                  "bedrock-agentcore:subnets": "true"
                }
              }
            },
            {
              "Sid": "DenyForeignSubnets",
              "Effect": "Deny",
              "Action": [
                "bedrock-agentcore:CreateAgentRuntime",
                "bedrock-agentcore:CreateAgentRuntimeEndpoint",
                "bedrock-agentcore:UpdateAgentRuntime",
                "bedrock-agentcore:CreateBrowser",
                "bedrock-agentcore:CreateCodeInterpreter"
              ],
              "Resource": "*",
              "Condition": {
                "ForAnyValue:StringNotEquals": {
                  "bedrock-agentcore:subnets": ["<approved-subnet-id>"]
                }
              }
            },
            {
              "Sid": "DenyForeignSecurityGroups",
              "Effect": "Deny",
              "Action": [
                "bedrock-agentcore:CreateAgentRuntime",
                "bedrock-agentcore:CreateAgentRuntimeEndpoint",
                "bedrock-agentcore:UpdateAgentRuntime",
                "bedrock-agentcore:CreateBrowser",
                "bedrock-agentcore:CreateCodeInterpreter"
              ],
              "Resource": "*",
              "Condition": {
                "ForAnyValue:StringNotEquals": {
                  "bedrock-agentcore:securityGroups": ["<approved-security-group-id>"]
                }
              }
            }
          ]
        }
        ```

        Replace `<approved-subnet-id>` and `<approved-security-group-id>` with the
        subnet and security group from your AgentCore landing zone.
      </Step>

      <Step title="Create SmartGroups">
        SmartGroups identify the agent workload as a policy source and scope TLS
        decryption to specific destination hosts.

        1. Navigate to **Security > Distributed Cloud Firewall > Groups** and select the
           **SmartGroups** tab.
        2. Create the following SmartGroups:

           **`agentcore-runtime-subnet`**: the agent workload source

           * Match type: **Subnet**
           * Select the AgentCore runtime subnet from your VPC

           **`github-hosts`**: destination scope for selective TLS decryption

           * Match type: **FQDN**
           * Add three entries: `api.github.com`, `raw.githubusercontent.com`,
             `github.com`
           * This scopes decryption to only these hosts so AWS service traffic (ECR,
             Bedrock, STS) is never decrypted

           **`any`**: catch-all destination for deny rules

           * Match type: **CIDR**
           * CIDR: `0.0.0.0/0`

           **`adjacent-spokes`**: other workload VPCs in the transit fabric (for
           East-West containment)

           * Match type: **CIDR**
           * Add a CIDR entry for each adjacent spoke VPC CIDR that the agent must not
             reach
      </Step>

      <Step title="Create WebGroups">
        WebGroups define the FQDN allow-lists for permitted egress destinations and the
        URL-path deny patterns for supply-chain enforcement.

        1. Navigate to **Security > Distributed Cloud Firewall > Groups** and select the
           **WebGroups** tab.
        2. Create the following WebGroups:

           **`allowed-models`**: sanctioned model provider endpoints

           * Filter type: **SNI Filter**
           * `bedrock-runtime.*.amazonaws.com`
           * `bedrock.*.amazonaws.com`
           * Add FQDNs for any additional model providers your governance policy
             approves

           **`aws-control-domains`**: AWS service control-plane endpoints required by
           the AgentCore runtime

           * Filter type: **SNI Filter**
           * `sts.*.amazonaws.com`
           * `logs.*.amazonaws.com`
           * `monitoring.*.amazonaws.com`
           * `xray.*.amazonaws.com`
           * `secretsmanager.*.amazonaws.com`
           * `*.ecr.*.amazonaws.com`
           * `api.ecr.*.amazonaws.com`

           **`allowed-tools`**: sanctioned tool-call destinations

           * Filter type: **SNI Filter**
           * Add the FQDN of each external API or tool endpoint the agent is permitted
             to reach
           * Include GitHub hosts here if your agent uses GitHub as a tool source; rule
             29 will still block known-bad paths before rule 31 permits legitimate
             traffic

           **`allowed-mcp-servers`**: sanctioned remote MCP server endpoints

           * Filter type: **SNI Filter**
           * Add the FQDN of each approved remote MCP server

           **`supply-chain-ioc-github`**: URL-path deny patterns for known indicators of
           compromise

           * Filter type: **URL Filter**
           * `api.github.com/repos/*/*shai-hulud*`
           * `raw.githubusercontent.com/*/*shai-hulud*`
           * `github.com/shai-hulud*`
           * Append additional IoC path patterns from threat intelligence feeds as they
             are identified

        <Note>
          The `supply-chain-ioc-github` WebGroup uses URL Filter entries, not SNI Filter
          entries. URL filters match the full request path after decryption and require
          a DCF policy with **DECRYPT\_ALLOWED** to function. SNI filters match only the
          TLS server name at handshake time and do not require decryption.
        </Note>
      </Step>

      <Step title="Create DCF policies">
        DCF policies are evaluated top-down; the first matching rule applies. The
        supply-chain enforcement pattern uses two rules: a DENY at priority 29 blocks
        known-bad URL paths on GitHub hosts, and a PERMIT at priority 31 allows
        legitimate GitHub traffic after decryption. Both rules decrypt only flows
        destined for the `github-hosts` SmartGroup so AWS service traffic is unaffected.

        Navigate to **Security > Distributed Cloud Firewall > Policy** and create the
        following rules in priority order:

        | Priority | Name                    | Source                     | Destination       | WebGroup                  | Action | Decrypt               |
        | -------- | ----------------------- | -------------------------- | ----------------- | ------------------------- | ------ | --------------------- |
        | 29       | `deny-supply-chain-ioc` | `agentcore-runtime-subnet` | `github-hosts`    | `supply-chain-ioc-github` | DENY   | `DECRYPT_ALLOWED`     |
        | 30       | `allow-models`          | `agentcore-runtime-subnet` | `any`             | `allowed-models`          | PERMIT | `DECRYPT_NOT_ALLOWED` |
        | 31       | `allow-tools`           | `agentcore-runtime-subnet` | `github-hosts`    | `allowed-tools`           | PERMIT | `DECRYPT_ALLOWED`     |
        | 32       | `allow-aws-control`     | `agentcore-runtime-subnet` | `any`             | `aws-control-domains`     | PERMIT | `DECRYPT_NOT_ALLOWED` |
        | 33       | `allow-mcp-servers`     | `agentcore-runtime-subnet` | `any`             | `allowed-mcp-servers`     | PERMIT | `DECRYPT_NOT_ALLOWED` |
        | 50       | `deny-dns-exfil`        | `agentcore-runtime-subnet` | `any`             | —                         | DENY   | —                     |
        | 80       | `deny-east-west`        | `agentcore-runtime-subnet` | `adjacent-spokes` | —                         | DENY   | —                     |
        | 100      | `default-deny`          | `agentcore-runtime-subnet` | `any`             | —                         | DENY   | —                     |

        For rule 50, set the protocol to **UDP** and port **53**. The agent's legitimate
        DNS to the VPC resolver (`169.254.169.253`) is intra-VPC and does not traverse
        the spoke gateway, so this rule does not block normal SDK operation.

        <Warning>
          Rules 30 and 32 must have `DECRYPT_NOT_ALLOWED` set explicitly. AWS service
          endpoints (Bedrock, ECR, STS, CloudWatch) fail TLS verification when
          intercepted, and the AgentCore runtime's container image pull path cannot use
          a custom trust store. Never leave the decrypt setting unset on rules that
          permit AWS service traffic.
        </Warning>

        <Note>
          Rules 29 and 31 both target the `github-hosts` SmartGroup as the destination.
          This is intentional: decryption is scoped to that SmartGroup so only
          GitHub-bound flows are inspected. Rule 29 fires first and denies connections
          whose URL paths match the IoC patterns. Rule 31 then permits legitimate GitHub
          traffic that did not match any IoC pattern.
        </Note>
      </Step>

      <Step title="Verify containment">
        1. In CoPilot, navigate to **Security > Distributed Cloud Firewall > Policy**
           and confirm all rules are present and active.

        2. Invoke an AgentCore Runtime session and confirm it completes successfully.
           The model provider endpoints and declared tool destinations should appear as
           PERMIT events in CoPilot FlowIQ filtered by source SmartGroup
           `agentcore-runtime-subnet`.

        3. To verify egress blocking, attempt a connection from an agent to a
           destination not in any WebGroup and confirm it is denied. The denied attempt
           appears in CoPilot per-connection logs with the rule name `default-deny`.

        4. To verify DNS exfiltration blocking, attempt an external DNS query over
           UDP/53 and confirm it is denied by rule `deny-dns-exfil`.

        5. To verify the IAM guardrail, attempt to create an AgentCore Runtime without
           subnet parameters (PUBLIC mode) and confirm the AWS API returns an explicit
           deny on the `DenyPublicModeCreate` statement.

        6. To verify East-West blocking, attempt a connection from the agent subnet to
           an adjacent spoke VPC CIDR and confirm it is denied by rule `deny-east-west`.
      </Step>
    </Steps>
  </Tab>

  <Tab title="Terraform">
    <Steps>
      <Step title="Deploy an Aviatrix spoke gateway in the cluster VPC">
        An Aviatrix spoke gateway must be deployed in the same VPC as the target
        Kubernetes cluster. If a spoke gateway is already deployed in the cluster VPC,
        proceed to the next step.

        ```hcl theme={null}
        resource "aviatrix_spoke_gateway" "mcp_containment" {
          cloud_type   = var.cloud_type
          account_name = var.account_name
          gw_name      = "mcp-containment-spoke"
          vpc_id       = var.cluster_vpc_id
          vpc_reg      = var.region
          gw_size      = var.gateway_size
          subnet       = var.subnet_cidr
        }
        ```
      </Step>

      <Step title="Configure the agent subnet route table">
        Add a default route on the agent subnet route table pointing to the spoke
        gateway. Apply this route only to the agent subnet; do not apply it to subnets
        hosting interface VPC endpoints.

        ```hcl theme={null}
        resource "aws_route" "agent_default" {
          route_table_id         = var.agent_subnet_route_table_id
          destination_cidr_block = "0.0.0.0/0"
          network_interface_id   = module.spoke_agentcore.spoke_gateway.private_ip
        }
        ```

        <Note>
          If you are not using the `mc-spoke` module, retrieve the ENI directly from the
          gateway resource. Do not enable `single_ip_snat` on the spoke gateway if you
          also enable TLS decryption: the two features are mutually exclusive on the
          same gateway in Controller 9.0. Internet egress operates via the transit's
          egress path when `single_ip_snat = false`.
        </Note>
      </Step>

      <Step title="Deploy the IAM guardrail">
        The guardrail requires three separate statements. A single `ForAllValues`
        statement cannot catch PUBLIC-mode creates because that operator evaluates to
        false when the key is absent from the request.

        ```hcl theme={null}
        resource "aws_iam_policy" "agentcore_guardrail" {
          name        = "agentcore-vpc-mode-guardrail"
          description = "Deny AgentCore Create* unless subnets and security groups come from the approved runtime set"

          policy = jsonencode({
            Version = "2012-10-17"
            Statement = [
              {
                # PUBLIC mode: no subnet key present in the request at all.
                # Null condition is required because ForAnyValue on a missing key
                # evaluates to false, which would let PUBLIC-mode creates through.
                Sid    = "DenyPublicModeCreate"
                Effect = "Deny"
                Action = [
                  "bedrock-agentcore:CreateAgentRuntime",
                  "bedrock-agentcore:CreateAgentRuntimeEndpoint",
                  "bedrock-agentcore:UpdateAgentRuntime",
                  "bedrock-agentcore:CreateBrowser",
                  "bedrock-agentcore:CreateCodeInterpreter",
                ]
                Resource = "*"
                Condition = {
                  "Null" = {
                    "bedrock-agentcore:subnets" = "true"
                  }
                }
              },
              {
                # VPC mode with subnets outside the approved set.
                Sid    = "DenyForeignSubnets"
                Effect = "Deny"
                Action = [
                  "bedrock-agentcore:CreateAgentRuntime",
                  "bedrock-agentcore:CreateAgentRuntimeEndpoint",
                  "bedrock-agentcore:UpdateAgentRuntime",
                  "bedrock-agentcore:CreateBrowser",
                  "bedrock-agentcore:CreateCodeInterpreter",
                ]
                Resource = "*"
                Condition = {
                  "ForAnyValue:StringNotEquals" = {
                    "bedrock-agentcore:subnets" = [var.agent_subnet_id]
                  }
                }
              },
              {
                # VPC mode with security groups outside the approved set.
                Sid    = "DenyForeignSecurityGroups"
                Effect = "Deny"
                Action = [
                  "bedrock-agentcore:CreateAgentRuntime",
                  "bedrock-agentcore:CreateAgentRuntimeEndpoint",
                  "bedrock-agentcore:UpdateAgentRuntime",
                  "bedrock-agentcore:CreateBrowser",
                  "bedrock-agentcore:CreateCodeInterpreter",
                ]
                Resource = "*"
                Condition = {
                  "ForAnyValue:StringNotEquals" = {
                    "bedrock-agentcore:securityGroups" = [var.agent_security_group_id]
                  }
                }
              },
            ]
          })
        }
        ```
      </Step>

      <Step title="Create SmartGroups">
        ```hcl theme={null}
        # Agent runtime subnet. Subnet-type SmartGroups match the workload by
        # resource ID rather than CIDR, which is more precise for policy scoping.
        resource "aviatrix_smart_group" "agentcore_runtime" {
          name = "agentcore-runtime-subnet"

          selector {
            match_expressions {
              type   = "subnet"
              res_id = var.agent_subnet_id
            }
          }
        }

        # FQDN SmartGroup scoping TLS decryption to GitHub hosts only.
        # Used as the destination in rules 29 and 31 so that AWS service traffic
        # (ECR, Bedrock, STS) is never decrypted.
        resource "aviatrix_smart_group" "github_hosts" {
          name = "github-hosts"

          selector {
            match_expressions { fqdn = "api.github.com" }
            match_expressions { fqdn = "raw.githubusercontent.com" }
            match_expressions { fqdn = "github.com" }
          }
        }

        # Catch-all destination for default-deny and DNS exfil rules.
        resource "aviatrix_smart_group" "any" {
          name = "any"

          selector {
            match_expressions {
              cidr = "0.0.0.0/0"
            }
          }
        }

        # East-West boundary (enterprise transit fabric deployments).
        resource "aviatrix_smart_group" "adjacent_spokes" {
          name = "adjacent-spokes"

          selector {
            dynamic "match_expressions" {
              for_each = var.adjacent_spoke_cidrs
              content {
                cidr = match_expressions.value
              }
            }
          }
        }
        ```
      </Step>

      <Step title="Create WebGroups">
        ```hcl theme={null}
        locals {
          allowed_model_domains = [
            "bedrock-runtime.*.amazonaws.com",
            "bedrock.*.amazonaws.com",
          ]

          aws_control_domains = [
            # Identity and observability
            "sts.*.amazonaws.com",
            "logs.*.amazonaws.com",
            "monitoring.*.amazonaws.com",
            "xray.*.amazonaws.com",
            "secretsmanager.*.amazonaws.com",
            # ECR auth API and registry (required for AgentCore VPC-mode image pull)
            "api.ecr.*.amazonaws.com",
            "*.dkr.ecr.*.amazonaws.com",
          ]
        }

        resource "aviatrix_web_group" "allowed_models" {
          name = "allowed-models"

          selector {
            dynamic "match_expressions" {
              for_each = local.allowed_model_domains
              content {
                snifilter = match_expressions.value
              }
            }
          }
        }

        resource "aviatrix_web_group" "aws_control" {
          name = "aws-control-domains"

          selector {
            dynamic "match_expressions" {
              for_each = local.aws_control_domains
              content {
                snifilter = match_expressions.value
              }
            }
          }
        }

        resource "aviatrix_web_group" "allowed_tools" {
          name = "allowed-tools"

          selector {
            dynamic "match_expressions" {
              for_each = var.allowed_tool_domains
              content {
                snifilter = match_expressions.value
              }
            }
          }
        }

        resource "aviatrix_web_group" "allowed_mcp_servers" {
          name = "allowed-mcp-servers"

          selector {
            dynamic "match_expressions" {
              for_each = var.allowed_mcp_server_domains
              content {
                snifilter = match_expressions.value
              }
            }
          }
        }

        # URL-path deny patterns for supply-chain indicators of compromise.
        # urlfilter matches the full request path after decryption. Each entry
        # is an independent pattern; a connection matching any entry is denied.
        # Seed list covers the Shai-Hulud npm worm (Sep 2025). Append new
        # entries from threat intelligence feeds as indicators are published.
        resource "aviatrix_web_group" "supply_chain_ioc_github" {
          name = "supply-chain-ioc-github"

          selector {
            match_expressions {
              urlfilter = "api.github.com/repos/*/*shai-hulud*"
            }
            match_expressions {
              urlfilter = "raw.githubusercontent.com/*/*shai-hulud*"
            }
            match_expressions {
              urlfilter = "github.com/shai-hulud*"
            }
          }
        }
        ```
      </Step>

      <Step title="Create DCF policies">
        ```hcl theme={null}
        resource "aviatrix_distributed_firewalling_config" "this" {
          enable_distributed_firewalling = true
        }

        resource "aviatrix_distributed_firewalling_policy_list" "agentcore" {
          depends_on = [
            aviatrix_distributed_firewalling_config.this,
            aviatrix_smart_group.agentcore_runtime,
            aviatrix_smart_group.github_hosts,
            aviatrix_smart_group.any,
            aviatrix_smart_group.adjacent_spokes,
            aviatrix_web_group.allowed_models,
            aviatrix_web_group.aws_control,
            aviatrix_web_group.allowed_tools,
            aviatrix_web_group.allowed_mcp_servers,
            aviatrix_web_group.supply_chain_ioc_github,
          ]

          # ---- 29: URL-path deny for supply-chain IoC on GitHub hosts ---------------
          # Scoped to github_hosts SmartGroup so decryption fires only for
          # GitHub-bound flows. AWS service traffic (ECR, Bedrock, STS) passes
          # through encrypted via rules 30 and 32. Requires Controller 9.0+.
          policies {
            name     = "deny-supply-chain-ioc"
            action   = "DENY"
            priority = 29
            protocol = "TCP"
            logging  = true

            src_smart_groups = [aviatrix_smart_group.agentcore_runtime.uuid]
            dst_smart_groups = [aviatrix_smart_group.github_hosts.uuid]
            web_groups       = [aviatrix_web_group.supply_chain_ioc_github.uuid]

            decrypt_policy = "DECRYPT_ALLOWED"
            tls_profile    = var.dcf_tls_profile_uuid

            port_ranges { lo = 443 }
          }

          # ---- 30: egress to sanctioned model providers ----------------------------
          # DECRYPT_NOT_ALLOWED: Bedrock uses AWS SigV4; cert-chain changes break auth.
          policies {
            name     = "allow-models"
            action   = "PERMIT"
            priority = 30
            protocol = "TCP"
            logging  = true

            src_smart_groups = [aviatrix_smart_group.agentcore_runtime.uuid]
            dst_smart_groups = [aviatrix_smart_group.any.uuid]
            web_groups       = [aviatrix_web_group.allowed_models.uuid]

            decrypt_policy = "DECRYPT_NOT_ALLOWED"

            port_ranges { lo = 443 }
          }

          # ---- 31: egress to sanctioned tool destinations --------------------------
          # Scoped to github_hosts SmartGroup. Decryption is ALLOWED so the spoke
          # gateway can confirm the URL path was not blocked by rule 29 before
          # permitting the connection. Legitimate GitHub tool calls pass here.
          policies {
            name     = "allow-tools"
            action   = "PERMIT"
            priority = 31
            protocol = "TCP"
            logging  = true

            src_smart_groups = [aviatrix_smart_group.agentcore_runtime.uuid]
            dst_smart_groups = [aviatrix_smart_group.github_hosts.uuid]
            web_groups       = [aviatrix_web_group.allowed_tools.uuid]

            decrypt_policy = "DECRYPT_ALLOWED"
            tls_profile    = var.dcf_tls_profile_uuid

            port_ranges { lo = 443 }
          }

          # ---- 32: egress to AWS control-plane service APIs ------------------------
          # DECRYPT_NOT_ALLOWED: AWS service endpoints fail TLS verification when
          # intercepted. ECR image pull and container bootstrap cannot use a
          # custom trust store in the AgentCore microVM environment.
          policies {
            name     = "allow-aws-control"
            action   = "PERMIT"
            priority = 32
            protocol = "TCP"
            logging  = true

            src_smart_groups = [aviatrix_smart_group.agentcore_runtime.uuid]
            dst_smart_groups = [aviatrix_smart_group.any.uuid]
            web_groups       = [aviatrix_web_group.aws_control.uuid]

            decrypt_policy = "DECRYPT_NOT_ALLOWED"

            port_ranges { lo = 443 }
          }

          # ---- 33: egress to sanctioned remote MCP servers -------------------------
          # DECRYPT_NOT_ALLOWED: the MCP Python client validates the server cert
          # against certifi and fails if the chain is intercepted.
          policies {
            name     = "allow-mcp-servers"
            action   = "PERMIT"
            priority = 33
            protocol = "TCP"
            logging  = true

            src_smart_groups = [aviatrix_smart_group.agentcore_runtime.uuid]
            dst_smart_groups = [aviatrix_smart_group.any.uuid]
            web_groups       = [aviatrix_web_group.allowed_mcp_servers.uuid]

            decrypt_policy = "DECRYPT_NOT_ALLOWED"

            port_ranges { lo = 443 }
          }

          # ---- 50: DNS exfiltration block ------------------------------------------
          # The runtime's legitimate DNS to the VPC resolver (169.254.169.253) is
          # intra-VPC and does not traverse the spoke gateway.
          policies {
            name     = "deny-dns-exfil"
            action   = "DENY"
            priority = 50
            protocol = "UDP"
            logging  = true

            src_smart_groups = [aviatrix_smart_group.agentcore_runtime.uuid]
            dst_smart_groups = [aviatrix_smart_group.any.uuid]

            port_ranges { lo = 53 }
          }

          # ---- 80: East-West deny (enterprise transit fabric) ----------------------
          policies {
            name     = "deny-east-west"
            action   = "DENY"
            priority = 80
            protocol = "ANY"
            logging  = true

            src_smart_groups = [aviatrix_smart_group.agentcore_runtime.uuid]
            dst_smart_groups = [aviatrix_smart_group.adjacent_spokes.uuid]
          }

          # ---- 100: default deny catch-all -----------------------------------------
          policies {
            name     = "default-deny"
            action   = "DENY"
            priority = 100
            protocol = "ANY"
            logging  = true

            src_smart_groups = [aviatrix_smart_group.agentcore_runtime.uuid]
            dst_smart_groups = [aviatrix_smart_group.any.uuid]
          }
        }
        ```

        <Note>
          `var.dcf_tls_profile_uuid` is the UUID of the DCF TLS profile to use for decryption-enabled rules. Retrieve the default profile UUID from **Security > Distributed Cloud Firewall > TLS Profiles** in CoPilot or via the Aviatrix API. Decryption-enabled rules (priorities 29 and 31) require Controller 9.0 or later. If you are running Controller 8.1 and do not require URL-path enforcement, remove those two rules and the `supply_chain_ioc_github` WebGroup.
        </Note>
      </Step>

      <Step title="Verify containment">
        1. Apply the Terraform configuration and confirm no errors in the output.

        2. Invoke an AgentCore Runtime session and confirm it completes successfully.
           Verify the model provider and AWS service endpoint connections appear as
           PERMIT events in CoPilot FlowIQ filtered by source SmartGroup
           `agentcore-runtime-subnet`.

        3. Attempt a connection from an agent to an undeclared destination and confirm
           it is denied. The denied attempt appears in CoPilot per-connection logs with
           the matching rule name `default-deny`.

        4. To verify the IAM guardrail for PUBLIC mode, run:

           ```bash theme={null}
           aws bedrock-agentcore create-agent-runtime \
             --network-configuration '{"networkMode":"PUBLIC"}' \
             --region <your-region>
           ```

           Confirm the API returns an explicit deny on `DenyPublicModeCreate`.

        5. To verify the foreign-subnet guardrail:

           ```bash theme={null}
           aws bedrock-agentcore create-agent-runtime \
             --network-configuration "{\"networkMode\":\"VPC\",\"vpcConfig\":{\"subnetIds\":[\"<non-approved-subnet>\"],\"securityGroupIds\":[\"<approved-sg>\"]}}" \
             --region <your-region>
           ```

           Confirm the API returns an explicit deny on `DenyForeignSubnets`.

        6. Attempt a connection from the agent subnet to an adjacent spoke CIDR and
           confirm it is denied by rule `deny-east-west`.
      </Step>
    </Steps>
  </Tab>
</Tabs>
