<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Fernando Azevedo</title>
    <description>The latest articles on DEV Community by Fernando Azevedo (@fernando_azevedo_6844e930).</description>
    <link>https://dev.to/fernando_azevedo_6844e930</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1787949%2F6b76fcdd-ad42-4051-833f-22554f10309f.jpg</url>
      <title>DEV Community: Fernando Azevedo</title>
      <link>https://dev.to/fernando_azevedo_6844e930</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/fernando_azevedo_6844e930"/>
    <language>en</language>
    <item>
      <title>Amazon Quick on a private network: PrivateLink's two planes and where they leak</title>
      <dc:creator>Fernando Azevedo</dc:creator>
      <pubDate>Fri, 23 Oct 2026 15:48:23 +0000</pubDate>
      <link>https://dev.to/fernando_azevedo_6844e930/amazon-quick-on-a-private-network-privatelinks-two-planes-and-where-they-leak-2lk</link>
      <guid>https://dev.to/fernando_azevedo_6844e930/amazon-quick-on-a-private-network-privatelinks-two-planes-and-where-they-leak-2lk</guid>
      <description>&lt;p&gt;When someone from compliance asks whether Amazon Quick "goes over the internet", the honest answer has two parts, and almost every design I have reviewed answers only one. User traffic into Quick uses PrivateLink interface endpoints. Quick's traffic out to your on-premises database, your private MCP server or your internal API uses something else: ENIs that Quick itself injects into your VPC. DNS, endpoint policy, IP/VPC restriction and IAM sit on top of those two planes, and each layer covers less than its name suggests. The useful question is not "is it private?", it's "which byte still leaves for the internet, and who decided that was fine?".&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters now
&lt;/h2&gt;

&lt;p&gt;While Quick was just BI, the VPC connection existed so Quick Sight could read PostgreSQL, SQL Server or Oracle behind a Direct Connect. The problem was well understood and the blast radius fit inside a dashboard. What changed is who uses that same VPC connection. It now carries Quick Agents calling MCP servers hosted on your network, Quick Flows and Quick Automate invoking action connectors, and Quick Research and Apps in Quick pulling from private sources. The ENI that used to run a &lt;code&gt;SELECT&lt;/code&gt; for a chart now carries an agent's tool call.&lt;/p&gt;

&lt;p&gt;In financial environments this matters in two places. &lt;strong&gt;Regulation:&lt;/strong&gt; CMN Resolution 4.893 and PCI-DSS expect you to show the data path and control who enters the perimeter. The AWS post lists HIPAA, FedRAMP and PCI-DSS as the usual triggers. &lt;strong&gt;Audit:&lt;/strong&gt; "traffic stays on the AWS backbone" only works as evidence if you can say which domains still leave through NAT, and some do.&lt;/p&gt;

&lt;p&gt;The 21 Oct 2026 post is a good walkthrough. What it doesn't do, and isn't meant to, is say where each control stops working. That's what this piece covers: the documented limits of each layer, and the cost of keeping all of it working for years, not the cost of building it once.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two planes of private Quick access, and what stays public
&lt;/h2&gt;

&lt;p&gt;Ingress (user to Quick) over interface endpoints. Egress (Quick to your data) over VPC connection ENIs. Dashed lines are traffic no endpoint covers.&lt;/p&gt;

&lt;h3&gt;
  
  
  🏢 On-premises: rede corporativa
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;DNS corporativo forward de .quicksight.aws.amazon.com (network)&lt;/li&gt;
&lt;li&gt;Oracle / SQL Server porta do banco liberada só para o SG do Quick (data)&lt;/li&gt;
&lt;li&gt;Servidor MCP privado API interna por IP (compute)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  📡 Rede: Direct Connect ou Site-to-Site VPN
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Direct Connect rota para o CIDR da VPC (network)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🟧 AWS: VPC do cliente (2 AZs no mínimo)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Route 53 Resolver inbound 2+ ENIs (network)&lt;/li&gt;
&lt;li&gt;VPCE quicksight-website policy só com aws:PrincipalAccount / aws:OrgId (security)&lt;/li&gt;
&lt;li&gt;VPCE quicksight (API) policy por ação; 13 APIs fora (security)&lt;/li&gt;
&lt;li&gt;ENIs da VPC connection SG QuickSight-VPC, VPC ID imutável (network)&lt;/li&gt;
&lt;li&gt;VPCE secretsmanager / redshift / glue S3 por gateway (security)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🤖 AWS: Amazon Quick (Enterprise)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Quick Sight, Agents, Flows, Automate Identity Center, RLS/CLS (ai)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🌐 Externo: saída pública que continua obrigatória
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;*.cloudfront.net, *.execute-api, *.kinesisvideo sign-in do Identity Center (external)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Flows
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;user -&amp;gt; dns: resolves the site name&lt;/li&gt;
&lt;li&gt;dns -&amp;gt; resolver: forwards the private zone&lt;/li&gt;
&lt;li&gt;user -&amp;gt; dx: HTTPS 443&lt;/li&gt;
&lt;li&gt;dx -&amp;gt; vpce-web: console and dashboards&lt;/li&gt;
&lt;li&gt;dx -&amp;gt; vpce-api: API calls&lt;/li&gt;
&lt;li&gt;vpce-web -&amp;gt; quick: PrivateLink (ingress)&lt;/li&gt;
&lt;li&gt;vpce-api -&amp;gt; quick: PrivateLink (ingress)&lt;/li&gt;
&lt;li&gt;quick -&amp;gt; eni: assumes execution role, uses ENIs&lt;/li&gt;
&lt;li&gt;eni -&amp;gt; dx: data and tool-call egress&lt;/li&gt;
&lt;li&gt;dx -&amp;gt; db: SQL query&lt;/li&gt;
&lt;li&gt;dx -&amp;gt; mcp: action connector / MCP&lt;/li&gt;
&lt;li&gt;eni -&amp;gt; sm: credentials without leaving the VPC&lt;/li&gt;
&lt;li&gt;user -&amp;gt; public: assets, metrics, sign-in via NAT/proxy&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Ingress plane: interface endpoints, DNS and what the policy accepts
&lt;/h2&gt;

&lt;p&gt;Ingress uses two interface endpoints with different jobs: &lt;code&gt;com.amazonaws.&amp;lt;region&amp;gt;.quicksight-website&lt;/code&gt; for the site and &lt;code&gt;com.amazonaws.&amp;lt;region&amp;gt;.quicksight&lt;/code&gt; for the API (with a &lt;code&gt;-fips&lt;/code&gt; variant). If administrators sign in as IAM users, add &lt;code&gt;console&lt;/code&gt; and &lt;code&gt;signin&lt;/code&gt; too. Place each one in at least two AZs, with a security group that accepts only 443 from the corporate CIDR.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DNS is where the first thing breaks.&lt;/strong&gt; The API endpoint, with private DNS on, takes over &lt;code&gt;quicksight.&amp;lt;region&amp;gt;.amazonaws.com&lt;/code&gt; by itself. The website endpoint doesn't: its private name is not the public URL. You create a private hosted zone for &lt;code&gt;&amp;lt;region&amp;gt;.quicksight.aws.amazon.com&lt;/code&gt; with an alias A record pointing at the endpoint. But a private hosted zone only answers associated VPCs, so corporate DNS has to forward that zone to a Route 53 Resolver inbound endpoint. Without the forward, the browser resolves the public IP, access still works, and nobody notices PrivateLink is being bypassed. Test with &lt;code&gt;nslookup&lt;/code&gt; from inside the corporate network and expect an IP from the VPC CIDR.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The website endpoint policy is almost decorative.&lt;/strong&gt; &lt;code&gt;Principal&lt;/code&gt;, &lt;code&gt;Action&lt;/code&gt; and &lt;code&gt;Resource&lt;/code&gt; must be &lt;code&gt;"*"&lt;/code&gt;, and the condition only accepts &lt;code&gt;aws:PrincipalAccount&lt;/code&gt; or &lt;code&gt;aws:OrgId&lt;/code&gt;. It stops your network from becoming a doorway into another account's Quick, which helps against exfiltration, but it doesn't control what the user does. The API endpoint accepts action-level restriction, and that's where being specific pays off.&lt;/p&gt;

&lt;h2&gt;
  
  
  Egress plane: the VPC connection is an injected ENI, not PrivateLink
&lt;/h2&gt;

&lt;p&gt;When Quick needs to fetch data, it doesn't go through the interface endpoint. To create a VPC connection (Enterprise only), you supply a VPC ID, at least two subnets in different AZs, at least one security group, and an execution role that trusts &lt;code&gt;quicksight.amazonaws.com&lt;/code&gt;. Quick assumes that role and creates ENIs in your subnets with &lt;code&gt;ec2:CreateNetworkInterface&lt;/code&gt;. From then on your network decides: route table, NACL, security group and the Direct Connect route.&lt;/p&gt;

&lt;p&gt;Three details turn into incidents if you don't know them. &lt;strong&gt;The VPC ID can't change after creation:&lt;/strong&gt; reorganizing the network means recreating the connection and repointing every data source. &lt;strong&gt;The &lt;code&gt;PARTIALLY AVAILABLE&lt;/code&gt; status&lt;/strong&gt; means one ENI is down and the connection still works. It's exactly the state nobody watches until the second AZ fails. &lt;strong&gt;Connections created before 27 Apr 2023 use a non-stateful security group&lt;/strong&gt; and need an inbound rule that lets return traffic in on every port (0–65535) from the source's SG. If yours dates from then, recreate it.&lt;/p&gt;

&lt;p&gt;For on-premises database host names, enter the Resolver inbound IPs in the DNS resolver endpoints field. Without them Quick resolves through public DNS and can't find the host. Credentials go to Secrets Manager through an interface endpoint in the same VPC as the ENIs, so that even the password lookup doesn't leave through NAT.&lt;/p&gt;

&lt;p&gt;One limitation changes the design: the Quick Index knowledge base VPC connection currently supports only Amazon Redshift. Confluence, SharePoint, Box, Google Drive, OneDrive and S3 can't use it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The ENI security group is now the agent's network allow-list:&lt;/strong&gt; The same VPC connection serves Quick Sight and Quick Agents calling private MCP. In practice, the egress of the &lt;code&gt;QuickSight-VPC&lt;/code&gt; SG and the routes on those subnets define everything an agent can reach on your network. If the SG opens the whole on-premises CIDR because "BI needed it", the agent inherits that reach. Split subnets and security groups by purpose (analytical data on one side, action tools on the other), even if it costs a second VPC connection.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Enforcing endpoint-only access: where the restriction stops
&lt;/h2&gt;

&lt;p&gt;Quick's IP/VPC restriction (&lt;code&gt;update-ip-restriction --enabled --vpc-endpoint-id-restriction-rule-map vpce-...=Name&lt;/code&gt;) blocks anything that doesn't come from the approved endpoint. It has four properties you need to know before turning it on in production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It covers web, embedded and mobile, but not the public API.&lt;/strong&gt; Anyone with credentials can call any operation from any IP. For the API the control is IAM: an &lt;code&gt;aws:SourceVpce&lt;/code&gt; or &lt;code&gt;aws:SourceIp&lt;/code&gt; condition in a deny policy, ideally in an SCP. Here's the catch: 13 APIs don't work through the interface endpoint (the 8 action connector ones, such as &lt;code&gt;CreateActionConnector&lt;/code&gt;, and 5 Flows ones, such as &lt;code&gt;ListFlows&lt;/code&gt;). A blanket &lt;code&gt;aws:SourceVpce&lt;/code&gt; deny breaks the automation that manages connectors. You write the exception with &lt;code&gt;NotAction&lt;/code&gt; for those operations and run that automation from a controlled path with public egress.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;UpdateIpRestriction&lt;/code&gt; replaces the whole map.&lt;/strong&gt; A script that sends only the new rule deletes the others. Run &lt;code&gt;describe-ip-restriction&lt;/code&gt; first, or better, keep the map versioned and always apply the full state.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A rule takes up to 10 minutes to apply and is global&lt;/strong&gt;, enforced in every Region and managed only from the account's Region. The limit is 100 rules combined, with IP and VPC rules OR'ed together.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lockout is real.&lt;/strong&gt; Without a rule covering your own access, the console won't let you save. But anyone on a source outside the list loses access the moment you turn it on. Publicly embedded dashboards then work only through the endpoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  What stays public, and what keeping the rest private costs
&lt;/h2&gt;

&lt;p&gt;The documentation lists the domains the browser reaches, and several have no endpoint: &lt;code&gt;*.cloudfront.net&lt;/code&gt; (CSS and JS), &lt;code&gt;*.execute-api.&amp;lt;region&amp;gt;.amazonaws.com&lt;/code&gt; (client metrics), &lt;code&gt;*.kinesisvideo.amazonaws.com&lt;/code&gt; (automation streaming), &lt;code&gt;&amp;lt;region&amp;gt;.signin.aws&lt;/code&gt; and, with IAM Identity Center, browser sign-in, which requires the public sign-in and Identity Center endpoints. No public IP is needed, but NAT or a proxy is. Your compliance report should say "the data and API path is private; static assets, telemetry and authentication leave through an inspected proxy". Writing "zero internet" claims something the documentation itself contradicts.&lt;/p&gt;

&lt;p&gt;Now the recurring cost, in &lt;code&gt;us-east-1&lt;/code&gt;. An interface endpoint costs US$ 0.01 per hour per ENI, plus US$ 0.01 per GB for the first 1 PB. Website and API in 2 AZs are 4 ENIs, about US$ 29/month. Add &lt;code&gt;console&lt;/code&gt; and &lt;code&gt;signin&lt;/code&gt;, another US$ 29. Secrets Manager, Redshift and Glue in 2 AZs, roughly US$ 44 more. The most expensive piece is the one nobody budgets: a Resolver inbound endpoint costs US$ 0.125 per ENI per hour, with at least two, so about US$ 182/month, plus US$ 0.40 per million queries. If you already have a shared Resolver inbound in a central network VPC, reuse it: it's the biggest line item and there's nothing Quick-specific about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anti-patterns I find in this design
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Website endpoint without on-premises DNS forwarding:&lt;/strong&gt; PrivateLink exists, the browser resolves the public IP and access works from outside. It's a silent failure that only shows up in &lt;code&gt;nslookup&lt;/code&gt; or VPC Flow Logs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treating IP/VPC restriction as an API control:&lt;/strong&gt; it doesn't cover the public API. Without &lt;code&gt;aws:SourceVpce&lt;/code&gt; in IAM or an SCP, a leaked credential calls everything from anywhere.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blanket &lt;code&gt;aws:SourceVpce&lt;/code&gt; deny with no exception:&lt;/strong&gt; the 13 action connector and Flows APIs don't go through the endpoint, and the automation that manages connectors stops working without an obvious error.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A script that calls &lt;code&gt;UpdateIpRestriction&lt;/code&gt; with only the new rule:&lt;/strong&gt; the map is replaced whole, the other rules disappear and an entire branch office loses access within 10 minutes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One VPC connection for BI and agents with the whole on-premises CIDR open:&lt;/strong&gt; the agent inherits BI's network reach, and the blast radius of a prompt injection becomes the corporate network.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keeping a VPC connection from before 27 Apr 2023:&lt;/strong&gt; the non-stateful SG requires return traffic open on 0–65535, a rule no auditor accepts without questions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Well-Architected lens
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;security&lt;/strong&gt;: Layered defense with clear roles: website endpoint policy scoped by &lt;code&gt;aws:OrgId&lt;/code&gt;, API endpoint policy by action, IP/VPC restriction for web and mobile, an SCP with &lt;code&gt;aws:SourceVpce&lt;/code&gt; and &lt;code&gt;NotAction&lt;/code&gt; for the 13 APIs outside the endpoint, RLS and CLS on the data. Restriction map changes land in CloudTrail; alarm on &lt;code&gt;UpdateIpRestriction&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;reliability&lt;/strong&gt;: Two AZs on every endpoint and on the VPC connection, an alarm whenever status leaves &lt;code&gt;AVAILABLE&lt;/code&gt; (including &lt;code&gt;PARTIALLY AVAILABLE&lt;/code&gt;), and Direct Connect with a backup VPN, because the VPC connection has no other path to on-premises.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What I would do:&lt;/strong&gt; I'd start with the report I'll hand the auditor, and only then create the first endpoint: which domains leave through the proxy, which controls cover the API and which cover the browser. Then I'd build two security groups and two VPC connections, one for analytical data and one for agent tools, because I've seen a "temporary" BI security group last four years. The restriction map would go into code from day one, applied as full state. The lesson I carry from financial platforms: the expensive control isn't the one you configure, it's the one you think is active and isn't, and in Quick that control is DNS forwarding.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Verdict
&lt;/h2&gt;

&lt;p&gt;Use the full design (website and API endpoints, VPC connection, Resolver inbound, IP/VPC restriction and an SCP with &lt;code&gt;aws:SourceVpce&lt;/code&gt;) when you have regulated on-premises data, agents calling private tools, and an auditor who will ask for the path of every byte. If Quick only reads Redshift and S3 in the same account and sign-in goes through Identity Center, a VPC connection with service endpoints covers most of the risk, and IP restriction on the corporate egress covers the browser for well under US$ 300/month. Either way, document what stays public. That sentence is what separates a defensible design from a promise the documentation contradicts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rating:&lt;/strong&gt; Recomendado com ressalvas / Recommended &lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/blogs/machine-learning/securing-quick-access-from-corporate-networks-with-aws-privatelink/" rel="noopener noreferrer"&gt;AWS Machine Learning Blog: Securing Quick access from corporate networks with AWS PrivateLink&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/quicksight/latest/developerguide/vpc-interface-endpoints.html" rel="noopener noreferrer"&gt;Quick and interface VPC endpoints (AWS PrivateLink)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/quicksight/latest/user/enabling-ip-restrictions.html" rel="noopener noreferrer"&gt;Managing IP/VPC restrictions in Amazon Quick&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/quick/latest/userguide/working-with-aws-vpc.html" rel="noopener noreferrer"&gt;Configuring VPC connections in Amazon Quick Sight&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/privatelink/pricing/" rel="noopener noreferrer"&gt;AWS PrivateLink pricing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/route53/pricing/" rel="noopener noreferrer"&gt;Amazon Route 53 pricing (Resolver endpoints)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://fernando.moretes.com/blog/amazon-quick-em-rede-privada-os-dois-planos-do-privatelink-e-onde" rel="noopener noreferrer"&gt;fernando.moretes.com&lt;/a&gt;. By Fernando F. Azevedo: Senior Solutions Architect.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>awscloud</category>
      <category>amazonquick</category>
      <category>privatelink</category>
      <category>vpcendpoints</category>
    </item>
    <item>
      <title>After login: per-operation authorization, revocation and recovery for agent tools</title>
      <dc:creator>Fernando Azevedo</dc:creator>
      <pubDate>Wed, 21 Oct 2026 16:01:25 +0000</pubDate>
      <link>https://dev.to/fernando_azevedo_6844e930/after-login-per-operation-authorization-revocation-and-recovery-for-agent-tools-45i0</link>
      <guid>https://dev.to/fernando_azevedo_6844e930/after-login-per-operation-authorization-revocation-and-recovery-for-agent-tools-45i0</guid>
      <description>&lt;h2&gt;
  
  
  A valid login is only the starting point
&lt;/h2&gt;

&lt;p&gt;An agent may continue a conversation after a user loses a permission. The operational question is whether the next tool call can still change a resource. This article proposes an architecture exercise, not a report of an AWS deployment or a completed cloud experiment. The AWS scenarios below are test proposals; the local recovery lab has a narrower purpose.&lt;/p&gt;

&lt;p&gt;The starting points are Carlos Biagolini's articles on &lt;a href="https://builder.aws.com/content/3Kuk63NZmMt59SD8ONcrmAmf3mE/primeiros-passos-com-amazon-bedrock-agentcore-identity" rel="noopener noreferrer"&gt;AgentCore Identity&lt;/a&gt; and &lt;a href="https://builder.aws.com/content/3Ga6SwtCJCXBaQgy1sxBi5Qw8um/construindo-um-servidor-mcp-serverless-na-aws-com-autenticacao-iam" rel="noopener noreferrer"&gt;a serverless MCP server with IAM authentication&lt;/a&gt;. They motivate a useful follow-up: once identity is established, which component enforces the right to perform each operation, and what happens if execution is interrupted?&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate three decisions
&lt;/h2&gt;

&lt;p&gt;Authentication establishes the caller's identity. Authorization decides whether that caller may perform a particular operation on a particular resource and tenant. Approval records consent to a concrete proposed change. A valid token, a visible tool or an old approval does not answer all three questions.&lt;/p&gt;

&lt;p&gt;Imagine a remediation tool shared by two tenants. A caller may have permission to invoke the endpoint and still lack permission to change the selected resource. Similarly, approval of an earlier proposal should not authorize a modified proposal. These checks must remain explicit even when the model produces a convincing explanation.&lt;/p&gt;

&lt;h2&gt;
  
  
  A route is a boundary, not the whole policy
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-access-control.html" rel="noopener noreferrer"&gt;API Gateway HTTP APIs&lt;/a&gt; support IAM, JWT authorizers and Lambda authorizers. This exercise compares IAM-based invocation with application-defined Lambda authorization. With AWS_IAM, a signed request and invocation permission control entry. A Lambda authorizer applies application logic: format 1.0 requires an IAM policy; format 2.0 also supports a simple Boolean response when enabled.&lt;/p&gt;

&lt;p&gt;If several MCP tools share &lt;code&gt;POST /mcp&lt;/code&gt;, the tool name and arguments live in the body. Permission to invoke that route does not establish permission for every operation carried by it. In this design, the executor enforces fine-grained authorization. Another trusted layer could do so if it understands the operation, cannot be bypassed and must run before the effect. Separate routes help entry controls but do not remove resource and tenant checks.&lt;/p&gt;

&lt;p&gt;Do not accept a model-supplied tenant as proof of authority. In a JWT flow, use validated claims and their authorized relationship to the resource. In an IAM/SigV4 flow, use the authenticated principal and a service-controlled authorization mapping. Carry that trusted context to the execution boundary. A Lambda role's ability to access DynamoDB does not automatically prove that every caller may read every item the function can access.&lt;/p&gt;

&lt;h2&gt;
  
  
  Revocation has an observable window
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_control-access_disable-perms.html" rel="noopener noreferrer"&gt;IAM documentation&lt;/a&gt; explains that temporary-credential permissions are evaluated for AWS requests, while policy updates may take minutes to propagate. Per-request evaluation therefore does not imply instantaneous revocation.&lt;/p&gt;

&lt;p&gt;A &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-lambda-authorizer.html" rel="noopener noreferrer"&gt;cached Lambda-authorizer result&lt;/a&gt; introduces another window. Include the cache key, route separation and TTL in the test design. Denying future work does not reverse a completed effect. Sensitive operations also need an explicit policy for in-flight work and a check near the effect boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Approval and completion are different facts
&lt;/h2&gt;

&lt;p&gt;Bind approval to reviewer identity, proposal digest, scope, expiry and expected resource state. After a worker restart, a &lt;code&gt;pending&lt;/code&gt; record does not prove whether an effect happened. Look for durable target evidence before replaying. If evidence is insufficient, preserve uncertainty and request review instead of guessing.&lt;/p&gt;

&lt;p&gt;The public &lt;a href="https://github.com/fernando-moretes/lab-security-remediation/releases/tag/v0.4.0" rel="noopener noreferrer"&gt;v0.4.0 lab&lt;/a&gt; explores that recovery boundary with SQLite and a synthetic target. Its &lt;a href="https://github.com/fernando-moretes/lab-security-remediation/blob/v0.4.0/DURABILITY.md" rel="noopener noreferrer"&gt;reproduction guide&lt;/a&gt; requires Node.js 22.13+ and creates local files. No AWS account is needed. CLI approval is simulated; the lab does not implement authenticated approval, IAM or revocation. Atomic local target-and-receipt writes do not establish exactly-once effects across a remote API.&lt;/p&gt;

&lt;p&gt;In the documented exercise, a deliberate crash occurs after the synthetic target and receipt are committed but before the execution ledger acknowledges completion. Recovery uses that receipt to reconcile the operation. Keeping the original proposal matters: generating a new proposal after the target changes produces a different digest. This is a concrete way to discuss recovery without implying a cloud guarantee the exercise cannot demonstrate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Proposed test matrix
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Valid principal requests another tenant&lt;/strong&gt;: Deny before protected data access. Evidence: Synthetic identity and decision.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shared MCP endpoint receives an unauthorized tool&lt;/strong&gt;: Deny at the operation boundary. Evidence: Tool name, scope and decision.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Permission changes during a session&lt;/strong&gt;: Measure when new calls stop succeeding. Evidence: Change time and decision timeline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authorizer result is cached&lt;/strong&gt;: Test before and after TTL across routes and tenants. Evidence: Cache settings and observations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Proposal changes after approval&lt;/strong&gt;: Reject the changed digest. Evidence: Original and changed synthetic digests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resource changes before execution&lt;/strong&gt;: Reject the stale precondition. Evidence: Expected and observed version.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Process stops after effect, before acknowledgement&lt;/strong&gt;: Reconcile using evidence. Evidence: Ledger, target version and receipt.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For every scenario, distinguish expected from observed results. These AWS authorization tests are proposed, not completed. Use synthetic inputs and avoid storing tokens, secrets or customer data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trade-offs and the next experiment
&lt;/h2&gt;

&lt;p&gt;Fresh authorization checks add work and latency. Caching can reduce both while extending the stale-decision window. Choose the design using operation impact, revocation policy and measurements. A low-risk read and a destructive remediation may justify different controls.&lt;/p&gt;

&lt;p&gt;Before adding another agent, ask the team to demonstrate which identity drives the decision, which component enforces it and how interrupted work is recovered. A future isolated experiment can add authenticated approval and revocation, with a cost ceiling and cleanup procedure defined before deployment.&lt;/p&gt;

&lt;p&gt;What would you test first: cross-tenant access, revocation during a session, or recovery after the effect? A useful answer includes the evidence needed to prove the outcome.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-access-control.html" rel="noopener noreferrer"&gt;HTTP API access controls&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-access-control-iam.html" rel="noopener noreferrer"&gt;IAM authorization for HTTP APIs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-lambda-authorizer.html" rel="noopener noreferrer"&gt;Lambda authorizers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_control-access_disable-perms.html" rel="noopener noreferrer"&gt;Temporary-credential permissions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://builder.aws.com/content/3Kuk63NZmMt59SD8ONcrmAmf3mE/primeiros-passos-com-amazon-bedrock-agentcore-identity" rel="noopener noreferrer"&gt;AgentCore Identity article&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://builder.aws.com/content/3Ga6SwtCJCXBaQgy1sxBi5Qw8um/construindo-um-servidor-mcp-serverless-na-aws-com-autenticacao-iam" rel="noopener noreferrer"&gt;Serverless MCP and IAM article&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/fernando-moretes/lab-security-remediation/blob/v0.4.0/DURABILITY.md" rel="noopener noreferrer"&gt;Local recovery exercise and limitations&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Authorization boundaries
&lt;/h2&gt;

&lt;p&gt;Proposed design; identity and policy must reach the effect boundary.&lt;/p&gt;

&lt;h3&gt;
  
  
  Trusted application boundary
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Authenticated caller (user)&lt;/li&gt;
&lt;li&gt;Route entry control (edge)&lt;/li&gt;
&lt;li&gt;Tool contract (compute)&lt;/li&gt;
&lt;li&gt;Operation + resource + tenant (security)&lt;/li&gt;
&lt;li&gt;Proposal approval (security)&lt;/li&gt;
&lt;li&gt;Constrained executor (compute)&lt;/li&gt;
&lt;li&gt;Durable evidence (storage)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Flows
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;caller -&amp;gt; entry&lt;/li&gt;
&lt;li&gt;entry -&amp;gt; contract&lt;/li&gt;
&lt;li&gt;contract -&amp;gt; policy&lt;/li&gt;
&lt;li&gt;policy -&amp;gt; approval&lt;/li&gt;
&lt;li&gt;approval -&amp;gt; executor&lt;/li&gt;
&lt;li&gt;executor -&amp;gt; receipt&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  C4
&lt;/h2&gt;

&lt;p&gt;A user requests remediation through an agent; the model proposes, the application enforces boundaries.&lt;/p&gt;

&lt;h3&gt;
  
  
  Containers
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;API / MCP endpoint&lt;/strong&gt; (API Gateway + application): Authenticates the caller and controls entry.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tool executor&lt;/strong&gt; (Application service): Checks operation, resource, tenant and approval before the effect.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evidence store&lt;/strong&gt; (SQLite in local lab): Preserves synthetic proposal, state and receipt for recovery.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Components
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Policy check&lt;/strong&gt;: Uses trusted context independent of model arguments.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recovery reconciler&lt;/strong&gt;: Reconciles using evidence; preserves uncertainty without proof.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  AWS Well-Architected
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;security&lt;/strong&gt;: Deny out-of-scope operations before effects; do not trust model-supplied tenant identity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;reliability&lt;/strong&gt;: Recover using receipts and preconditions without promising remote exactly-once effects.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;operational-excellence&lt;/strong&gt;: Record expected and observed results with versions and synthetic evidence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;cost&lt;/strong&gt;: Run the local lab first; define a ceiling and cleanup before an AWS experiment.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Checklist before automation
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Bind identity, digest, scope and expiry to approval.&lt;/strong&gt;: Document the evidence, owner and known limitation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Test the wrong tenant before data access.&lt;/strong&gt;: Document the evidence, owner and known limitation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Measure propagation and caching during revocation.&lt;/strong&gt;: Document the evidence, owner and known limitation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Simulate a crash after the effect and inspect the receipt.&lt;/strong&gt;: Document the evidence, owner and known limitation.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Anti-patterns
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Treating authentication or old approval as universal authorization.&lt;/li&gt;
&lt;li&gt;Repeating an effect merely because the ledger still says pending.&lt;/li&gt;
&lt;li&gt;Promising instant revocation or remote exactly-once execution without measurement.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://fernando.moretes.com/blog/depois-do-login-autorizacao-revogacao-recuperacao-agentes" rel="noopener noreferrer"&gt;fernando.moretes.com&lt;/a&gt;. By Fernando F. Azevedo: Senior Solutions Architect.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>securityresilience</category>
      <category>aws</category>
      <category>iam</category>
      <category>agenticai</category>
    </item>
    <item>
      <title>Retro: EMR Serverless on IPv6-only and the subnet left without DNS64</title>
      <dc:creator>Fernando Azevedo</dc:creator>
      <pubDate>Tue, 20 Oct 2026 15:42:40 +0000</pubDate>
      <link>https://dev.to/fernando_azevedo_6844e930/retro-emr-serverless-on-ipv6-only-and-the-subnet-left-without-dns64-355a</link>
      <guid>https://dev.to/fernando_azevedo_6844e930/retro-emr-serverless-on-ipv6-only-and-the-subnet-left-without-dns64-355a</guid>
      <description>&lt;p&gt;The dangerous question about IPv6 on EMR Serverless is not "do my jobs run on IPv6?", it is "what does each subnet in the application know how to do when a job asks for an address that only exists in IPv4?". The 10/16/2026 announcement is short: Spark and Hive can now run on dual-stack or IPv6-only subnets, at no additional cost, in every Region and release version of the service. The documentation is more honest than the announcement: the addressing mode is derived from the subnets, and getting IPv6 traffic to your data is your responsibility. This piece is a pre-emptive retro, an incident that did not happen, built from the documentation and the failure modes it lists itself. I would rather write the postmortem before than after.&lt;/p&gt;

&lt;h2&gt;
  
  
  The scenario: /22 subnets that no longer fit three applications
&lt;/h2&gt;

&lt;p&gt;The starting point is what AWS itself uses as a planning example. Each EMR Serverless worker consumes an IP address in the subnet where it launches, through an ENI. An application with 4 vCPU workers and a 4,000 vCPU ceiling reaches 1,000 workers, meaning 1,000 ENIs. A /22 subnet has 1,024 addresses and 1,019 usable ones, because AWS reserves five. With three applications sharing the same subnets and a 12,000 vCPU account quota, the documentation's recommendation becomes 3,000 free addresses per subnet, in at least two Availability Zones.&lt;/p&gt;

&lt;p&gt;In a bank's data platform this usually hits a wall: the VPC IPv4 block was negotiated with corporate networking years ago, does not overlap with on-premises and does not grow without an approval round. That is exactly the case IPv6 solves. A VPC IPv6 subnet is a /64, and the address conversation is over.&lt;/p&gt;

&lt;p&gt;In the scenario, the team goes straight to IPv6-only on three subnets, one per AZ, created by an IaC module. Two were built by hand during the pilot and got DNS64. The third came from the module, which did not know DNS64 existed. No integration test caught it, because the test job ran twice and landed on the two good subnets both times.&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers that drive the decision
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;1.000&lt;/strong&gt;: ENIs per application. 4 vCPU workers with a 4,000 vCPU ceiling: one IP per worker&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1.019&lt;/strong&gt;: Usable IPs in a /22. Barely fits one application; three need 3,000 per subnet&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;US$ 0,045/GB&lt;/strong&gt;: NAT gateway processing. Pricing page example (US East Ohio): every byte through NAT64 pays&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Timeline of the simulated incident
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;D-7: subnet swap&lt;/strong&gt;: The team stops the application, runs &lt;code&gt;update-application&lt;/code&gt; with the three IPv6-only subnets in &lt;code&gt;--network-configuration&lt;/code&gt; and starts it again. The mode is evaluated at every start, so the change only applies after the restart. Two test jobs pass.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;02:00: the nightly batch starts&lt;/strong&gt;: About a third of the jobs fail with &lt;code&gt;UnknownHostException&lt;/code&gt; when calling endpoints that only publish an A record. Each job runs in a single subnet picked from the set, and the ones that landed on the subnet without DNS64 cannot resolve anything that is not IPv6.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;02:40: retries hide the pattern&lt;/strong&gt;: The orchestrator's retry policy resubmits the jobs. Some pass because they land on another subnet. The failure looks intermittent, and intermittent becomes "flaky network" in the on-call's first hypothesis.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;04:10: correlation by subnet&lt;/strong&gt;: Someone opens each job run detail, which shows the subnet used, and crosses it with success and failure. 100% of the failures sit on the same subnet. &lt;code&gt;modify-subnet-attribute --enable-dns64&lt;/code&gt; fixes name resolution.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;05:00: the second failure&lt;/strong&gt;: An ingestion job with an old JDBC driver fails with &lt;code&gt;UnresolvedAddressException&lt;/code&gt;. It carried &lt;code&gt;-Djava.net.preferIPv4Stack=true&lt;/code&gt; in &lt;code&gt;spark.driver.extraJavaOptions&lt;/code&gt;, a workaround from years ago. The documentation forbids forcing the IPv4 stack in any mode.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;D+5: the bill arrives&lt;/strong&gt;: Cost Explorer shows a jump in NAT gateway data processing. S3 reads, which used to leave through the gateway endpoint, now resolve through DNS64 to &lt;code&gt;64:ff9b::/96&lt;/code&gt; and cross NAT64.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Root cause:&lt;/strong&gt; EMR Serverless derives the addressing mode from the subnets and does not validate whether the network is ready for it. Each subnet's readiness (IPv6 CIDR, auto-assign, DNS64, a &lt;code&gt;64:ff9b::/96&lt;/code&gt; route to the NAT gateway, a &lt;code&gt;::/0&lt;/code&gt; route to the egress-only gateway) was not encoded in the IaC module, and one-subnet-per-job selection turned a deterministic defect into an apparently random failure.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why the failure looked random: the addressing mode rules
&lt;/h2&gt;

&lt;p&gt;It is worth understanding the rules before blaming the network. An IPv4-only subnet runs as it always did. A dual-stack subnet runs IPv4 until you turn on &lt;code&gt;spark.emr-serverless.networking.ipv6.enabled=true&lt;/code&gt; in the application's &lt;code&gt;spark-defaults&lt;/code&gt; (for Hive, &lt;code&gt;hive.emr-serverless.networking.ipv6.enabled&lt;/code&gt; in &lt;code&gt;hive-site&lt;/code&gt;). An IPv6-only subnet runs IPv6 with no property at all.&lt;/p&gt;

&lt;p&gt;When the set mixes types, the whole application runs in the one mode every subnet supports. IPv4-only with IPv6-only is rejected at validation. IPv6-only with dual-stack runs everything IPv6-only, including the dual-stack subnets, which must then meet the same prerequisites. IPv4-only with dual-stack runs everything on IPv4.&lt;/p&gt;

&lt;p&gt;Three details produce incidents:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The mode applies at start:&lt;/strong&gt; a running application keeps the mode it started with. You fix the subnet and nothing changes until the next &lt;code&gt;stop&lt;/code&gt;/&lt;code&gt;start&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One job, one subnet:&lt;/strong&gt; a job runs in a single subnet picked from the list. One bad subnet out of three fails a third of the time, and retries mask it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dual-stack does not prefer IPv6 by default:&lt;/strong&gt; even with the opt-in on, the engine uses IPv4 until you set &lt;code&gt;spark.emr-serverless.driverEnv.SPARK_PREFER_IPV6&lt;/code&gt; and &lt;code&gt;spark.executorEnv.SPARK_PREFER_IPV6&lt;/code&gt;. Setting those on an IPv4-only subnet kills the job.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The lesson: the addressing mode is derived state, not configured state. The API does not let you declare the mode; it lets you declare subnets and infers the rest. Derived state needs a test that proves the derivation on every subnet, not on average.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where each byte of a job on an IPv6-only subnet leaves
&lt;/h2&gt;

&lt;p&gt;The path depends on the destination's DNS record: AAAA leaves directly, A becomes &lt;code&gt;64:ff9b::/96&lt;/code&gt; and pays NAT64.&lt;/p&gt;

&lt;h3&gt;
  
  
  🟧 AWS: subnet IPv6-only (uma por AZ)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Spark driver SPARK_PREFER_IPV6 automático (compute)&lt;/li&gt;
&lt;li&gt;Executors 1 ENI IPv6 por worker (compute)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  📡 VPC: resolução e rotas
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Route 53 Resolver DNS64 na subnet (network)&lt;/li&gt;
&lt;li&gt;NAT gateway rota 64:ff9b::/96 (NAT64) (network)&lt;/li&gt;
&lt;li&gt;Egress-only IGW rota ::/0 (network)&lt;/li&gt;
&lt;li&gt;Interface endpoints Glue, STS, KMS, Logs (security)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🌐 Destinos
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;S3 dual-stack s3.dualstack. (storage)&lt;/li&gt;
&lt;li&gt;Endpoint só IPv4 JDBC legado, API pública (external)&lt;/li&gt;
&lt;li&gt;Endpoint IPv6 AAAA publicado (external)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Flows
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;drv -&amp;gt; r53: DNS query&lt;/li&gt;
&lt;li&gt;exe -&amp;gt; r53: DNS query&lt;/li&gt;
&lt;li&gt;r53 -&amp;gt; nat: A record only: synthesizes 64:ff9b::&lt;/li&gt;
&lt;li&gt;nat -&amp;gt; v4: translates to IPv4, billed per GB&lt;/li&gt;
&lt;li&gt;exe -&amp;gt; eigw: destination with AAAA&lt;/li&gt;
&lt;li&gt;eigw -&amp;gt; s3ds: data reads and writes&lt;/li&gt;
&lt;li&gt;eigw -&amp;gt; v6: egress without ingress&lt;/li&gt;
&lt;li&gt;drv -&amp;gt; vpce: catalog, credentials, keys&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Network remediation: turning prerequisites into invariants
&lt;/h2&gt;

&lt;p&gt;The DNS64 fix took one command. The remediation that prevents recurrence took more work, and it is the one that matters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Subnet module with a contract:&lt;/strong&gt; the IaC module now requires, for every subnet tagged as an EMR Serverless target, &lt;code&gt;assign_ipv6_address_on_creation = true&lt;/code&gt;, &lt;code&gt;enable_dns64 = true&lt;/code&gt;, a &lt;code&gt;64:ff9b::/96&lt;/code&gt; route to the NAT gateway and a &lt;code&gt;::/0&lt;/code&gt; route to the egress-only internet gateway. Auto-assign only applies to resources created after the change, so turning it on late does not fix an ENI that already exists.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Validation before apply:&lt;/strong&gt; a pipeline check (policy as code, or a test that reads &lt;code&gt;describe-subnets&lt;/code&gt; and &lt;code&gt;describe-route-tables&lt;/code&gt;) fails the deploy if any subnet in the set breaks the contract. Validation that runs after the incident is a postmortem, not a control.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Per-subnet canary:&lt;/strong&gt; since the job picks the subnet and you do not, I create a canary application with a single subnet, one per AZ. A minimal Spark job resolves and connects to the endpoints the batch depends on (catalog, STS, KMS, the data bucket, the source database) and fails loudly. It runs before the batch window and after any network change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Clean JVM:&lt;/strong&gt; a sweep across every &lt;code&gt;sparkSubmitParameters&lt;/code&gt; and &lt;code&gt;runtimeConfiguration&lt;/code&gt; looking for &lt;code&gt;preferIPv4Stack&lt;/code&gt;. A 2019 workaround cannot decide the network stack in 2026.&lt;/p&gt;

&lt;p&gt;None of this is sophisticated. Its cost is not writing it, it is keeping the contract when someone creates the fourth subnet eighteen months from now without reading this document.&lt;/p&gt;

&lt;h2&gt;
  
  
  The cost the announcement does not mention: NAT64 and S3
&lt;/h2&gt;

&lt;p&gt;"No additional cost" is true for the feature. It is not true for the path the traffic now takes.&lt;/p&gt;

&lt;p&gt;On an IPv6-only subnet, every destination that only publishes an A record gets a synthesized address in &lt;code&gt;64:ff9b::/96&lt;/code&gt;, and that route points to the NAT gateway. The NAT gateway bills per hour and per GB processed; the VPC pricing page uses US$ 0.045 per GB as its example in US East (Ohio). A platform reading 8 TB a night from S3 and sending it through NAT64 pays around US$ 360 a night in processing alone, roughly US$ 10,800 over 30 nights. The S3 gateway endpoint on the workload subnet does not catch this traffic, because the destination is no longer an IPv4 prefix.&lt;/p&gt;

&lt;p&gt;S3 only serves IPv6 through its dual-stack endpoints (&lt;code&gt;s3.dualstack.&amp;lt;region&amp;gt;.amazonaws.com&lt;/code&gt;), and the client has to ask for them explicitly. Do not trust it before measuring: compare the NAT gateway's &lt;code&gt;BytesOutToDestination&lt;/code&gt; before and after the switch.&lt;/p&gt;

&lt;p&gt;Other services need the same care. In the AWS IPv6 support table, Glue has dual-stack and a private endpoint with IPv6, but not the public endpoint; Lake Formation also only has IPv6 through its private endpoint. On IPv6-only, that means interface endpoints for catalog and permissions, or paid NAT64 on every call.&lt;/p&gt;

&lt;p&gt;And security: the S3 documentation warns that policies with IPv4-only &lt;code&gt;aws:SourceIp&lt;/code&gt; make clients incorrectly lose or gain access once they start using IPv6. Review every bucket policy before the switch, not after.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three modes side by side
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Criterion&lt;/th&gt;
&lt;th&gt;IPv4-only&lt;/th&gt;
&lt;th&gt;Dual-stack with opt-in&lt;/th&gt;
&lt;th&gt;IPv6-only&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;IPv4 consumption&lt;/td&gt;
&lt;td&gt;1 per worker&lt;/td&gt;
&lt;td&gt;1 per worker, still consumed&lt;/td&gt;
&lt;td&gt;Zero&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Activation&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ipv6.enabled&lt;/code&gt; on the application + &lt;code&gt;SPARK_PREFER_IPV6&lt;/code&gt; on the job&lt;/td&gt;
&lt;td&gt;Automatic from the subnets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IPv4-only destinations&lt;/td&gt;
&lt;td&gt;Direct&lt;/td&gt;
&lt;td&gt;Direct over IPv4&lt;/td&gt;
&lt;td&gt;Only with DNS64 + NAT64, billed per GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Incident risk&lt;/td&gt;
&lt;td&gt;Exhausting the subnet at peak&lt;/td&gt;
&lt;td&gt;Low: failure falls back to IPv4&lt;/td&gt;
&lt;td&gt;High if any subnet or dependency is not ready&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;When to use&lt;/td&gt;
&lt;td&gt;Address headroom and no IPv6 mandate&lt;/td&gt;
&lt;td&gt;Transition and dependency validation&lt;/td&gt;
&lt;td&gt;Compliance mandate or real exhaustion, with dependencies mapped&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Well-Architected reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;security&lt;/strong&gt;: An IPv6 address is globally unique: egress only through an egress-only gateway, never a &lt;code&gt;::/0&lt;/code&gt; route to the internet gateway on worker subnets. EMR Serverless already refuses security groups with ports open to &lt;code&gt;0.0.0.0/0&lt;/code&gt; or &lt;code&gt;::/0&lt;/code&gt;. Review &lt;code&gt;aws:SourceIp&lt;/code&gt; in bucket policies and prefer &lt;code&gt;aws:SourceVpce&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;reliability&lt;/strong&gt;: Spread subnets across at least two AZs, as the documentation recommends, but treat each one as its own failure unit: a canary per subnet and an alarm per subnet. Automatic retries without per-subnet correlation turn a deterministic defect into noise.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Anti-patterns this retro tries to prevent
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Validating with two jobs:&lt;/strong&gt; with three subnets and one picked per job, two green jobs prove nothing about the third.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Going straight to IPv6-only without a dependency inventory:&lt;/strong&gt; every driver, SDK and endpoint the job touches must support IPv6 or go through NAT64; EMR Serverless does not change your code or third-party endpoints.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Turning on &lt;code&gt;SPARK_PREFER_IPV6&lt;/code&gt; as a global default:&lt;/strong&gt; on an IPv4-only subnet the preference kills the job.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fixing the subnet without restarting the application:&lt;/strong&gt; the mode was fixed at start, and on-call concludes the fix did not work.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What I would do:&lt;/strong&gt; I would start with dual-stack with the opt-in, on a new application, and keep the IPv6 preference on only for jobs whose dependencies I have already measured. IPv6-only waits for a mandate or real exhaustion, and only after two weeks of green per-subnet canaries and a NAT gateway bytes chart I can explain line by line. The lesson I took from other network migrations is that the defect is almost never in the new service, it is in the subnet someone created with the old module. That is why the contract lives in IaC, not in the wiki.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Lessons and recommendation
&lt;/h2&gt;

&lt;p&gt;Adopt IPv6 on EMR Serverless when the IPv4 space in shared subnets already limits your vCPU ceiling or when there is a formal IPv6 requirement. Use dual-stack with the opt-in while there is any IPv4-only dependency that matters in volume; use IPv6-only when every dependency publishes AAAA or sits behind an interface endpoint, and NAT64 is limited to control calls. Either way, encode DNS64, routes and auto-assign in the subnet module, run a per-subnet canary and measure the NAT gateway before declaring the migration done. The feature is free; an unprepared network bills per GB.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rating:&lt;/strong&gt; Adote com dual-stack primeiro / Adopt wi&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/about-aws/whats-new/2026/10/amazon-emr-serverless/" rel="noopener noreferrer"&gt;Amazon EMR Serverless now supports IPv6 and dual-stack connectivity (AWS What's New, 16/10/2026)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/emr/latest/EMR-Serverless-UserGuide/ipv6-subnets.html" rel="noopener noreferrer"&gt;Configure IPv6 subnets for EMR Serverless applications&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/emr/latest/EMR-Serverless-UserGuide/vpc-access.html" rel="noopener noreferrer"&gt;Configuring VPC access for EMR Serverless applications (subnet planning)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/vpc/latest/userguide/nat-gateway-nat64-dns64.html" rel="noopener noreferrer"&gt;DNS64 and NAT64 (Amazon VPC User Guide)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/vpc/latest/userguide/egress-only-internet-gateway.html" rel="noopener noreferrer"&gt;Egress-only internet gateway (Amazon VPC User Guide)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/vpc/latest/userguide/aws-ipv6-support.html" rel="noopener noreferrer"&gt;AWS services that support IPv6&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/developerguide/ipv6-access.html" rel="noopener noreferrer"&gt;Making requests to Amazon S3 over IPv6&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/vpc/pricing/" rel="noopener noreferrer"&gt;Amazon VPC pricing (NAT gateway)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://fernando.moretes.com/blog/retro-emr-serverless-em-ipv6-only-e-a-subnet-que-ficou-sem-dns64" rel="noopener noreferrer"&gt;fernando.moretes.com&lt;/a&gt;. By Fernando F. Azevedo: Senior Solutions Architect.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>awscloud</category>
      <category>emrserverless</category>
      <category>ipv6</category>
      <category>vpc</category>
    </item>
    <item>
      <title>CloudFront dual-stack VPC origins: IPv6 to private origins, with caveats</title>
      <dc:creator>Fernando Azevedo</dc:creator>
      <pubDate>Fri, 16 Oct 2026 17:37:34 +0000</pubDate>
      <link>https://dev.to/fernando_azevedo_6844e930/cloudfront-dual-stack-vpc-origins-ipv6-to-private-origins-with-caveats-1bj0</link>
      <guid>https://dev.to/fernando_azevedo_6844e930/cloudfront-dual-stack-vpc-origins-ipv6-to-private-origins-with-caveats-1bj0</guid>
      <description>&lt;p&gt;For almost two years, anyone who adopted CloudFront VPC origins and had an IPv6 target lived with a bad choice: keep the ALB in a private subnet and stay on IPv4 between edge and origin, or expose the origin to the internet to get end-to-end IPv6. The 10/15/2026 announcement removes that dilemma. The question that matters now is not 'can I get IPv6 to a private origin?', it's 'what in my VPC, my security group and my change process breaks when I turn it on?'. That is the question this analysis answers.&lt;/p&gt;

&lt;h2&gt;
  
  
  The launch in numbers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;US$ 0&lt;/strong&gt;: Additional cost. Dual-stack VPC origins carry no extra charge; the ENI addresses can be private.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;34&lt;/strong&gt;: Commercial Regions. Every Region with VPC origins, including sa-east-1; four have one excluded AZ.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;55/60&lt;/strong&gt;: Prefix list weight. The CloudFront managed list weighs 55 against the default 60 rules per security group.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What actually changed
&lt;/h2&gt;

&lt;p&gt;VPC origins, launched in November 2024, places an ALB, NLB or EC2 instance in a private subnet and makes CloudFront the single point of entry. The connection arrives through a service-managed ENI created inside your subnet, and access is controlled by a security group AWS creates with the naming pattern &lt;code&gt;CloudFront-VPCOrigins-Service-SG&lt;/code&gt;. Until yesterday that ENI spoke IPv4 only.&lt;/p&gt;

&lt;p&gt;In September 2025 CloudFront gained IPv6 for custom origins, with three options: IPv4 only (default), IPv6 only and dual-stack. That announcement excluded S3 and VPC origins. What shipped now closes half of that gap: VPC origins accept dual-stack. The other half stays open, because the current CloudFront IPv6 documentation still lists IPv6 only as "excluding Amazon S3 and VPC origins".&lt;/p&gt;

&lt;p&gt;In practice, the change is one field: when you create the VPC origin, the console asks for &lt;strong&gt;Origin type&lt;/strong&gt;, and the default is still IPv4. For dual-stack to work, the private subnet needs at least one free IPv6 address in addition to the IPv4 one, because that is where the service ENI is born. Without it, ENI creation fails.&lt;/p&gt;

&lt;p&gt;What does not change: gRPC is still out, Lambda@Edge origin request and origin response triggers are still out, a Gateway Load Balancer cannot be an origin, and neither can an NLB with a TLS listener. If you ruled out VPC origins for one of those reasons, you have no new reason to revisit.&lt;/p&gt;

&lt;h2&gt;
  
  
  End-to-end IPv6 path with a private origin
&lt;/h2&gt;

&lt;p&gt;Every hop where IPv6 can silently fall back to IPv4, and the control that governs that hop.&lt;/p&gt;

&lt;h3&gt;
  
  
  🌐 DNS e borda
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Route 53 alias A + alias AAAA (network)&lt;/li&gt;
&lt;li&gt;CloudFront edge IPv6 de viewer ligado (edge)&lt;/li&gt;
&lt;li&gt;Origin-facing escolhe IPv4 ou IPv6 (edge)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🟧 AWS: VPC privada (sa-east-1)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;ENI do serviço 1 IPv4 + 1 IPv6 livres (network)&lt;/li&gt;
&lt;li&gt;ALB interno IpAddressType: dualstack (compute)&lt;/li&gt;
&lt;li&gt;Target group IPv4 ou IPv6 (compute)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🔐 Controles
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;CloudFront-VPCOrigins-Service-SG fonte da regra de entrada (security)&lt;/li&gt;
&lt;li&gt;NACL de saída efêmeras 1024–65535 IPv4 e IPv6 (security)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Flows
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;viewer -&amp;gt; r53: AAAA lookup&lt;/li&gt;
&lt;li&gt;viewer -&amp;gt; edge: HTTPS over IPv6&lt;/li&gt;
&lt;li&gt;edge -&amp;gt; ofs: cache miss&lt;/li&gt;
&lt;li&gt;ofs -&amp;gt; eni: private connection, family picked by the service&lt;/li&gt;
&lt;li&gt;eni -&amp;gt; alb: allowed by service SG&lt;/li&gt;
&lt;li&gt;alb -&amp;gt; tg: new connection, target group family&lt;/li&gt;
&lt;li&gt;svcsg -&amp;gt; alb: inbound rule&lt;/li&gt;
&lt;li&gt;nacl -&amp;gt; alb: return path&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where it shines
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The trade-off is gone:&lt;/strong&gt; IPv6 to the origin no longer requires a public subnet, an internet-facing ALB and an IP list to maintain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero license cost:&lt;/strong&gt; no additional charge, and the ENI IPv6 addresses do not need to be public.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Same security model:&lt;/strong&gt; the service SG still restricts the origin to your distributions, now across both families.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Brazil included:&lt;/strong&gt; &lt;code&gt;sa-east-1&lt;/code&gt; is on the list, so a regulated workload that must stay in-country is not left out.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Dual-stack is not guaranteed IPv6
&lt;/h2&gt;

&lt;p&gt;This is the point the announcement does not make explicit, and it matters to whoever has to answer the audit. The CloudFront documentation describes dual-stack mode this way: the service automatically picks IPv4 or IPv6 for the origin connection to prioritize performance and availability. In other words, you don't decide the family of each connection. CloudFront does.&lt;/p&gt;

&lt;p&gt;For most sites that is exactly the right behavior. For a team that has to prove to a regulator or a contract that traffic runs over IPv6 all the way to the origin, it is an evidence problem. Turning on dual-stack produces no proof at all; measurement does.&lt;/p&gt;

&lt;p&gt;The same holds on the viewer side. The documentation itself warns that CloudFront may answer a viewer over IPv4 when its data says the experience will be better, and that the IPv6 share should remain a minority because not every access network supports the protocol. Two automatic decisions, one at each end, and neither is yours.&lt;/p&gt;

&lt;p&gt;What I would do: treat "end-to-end IPv6" as a measured SLO, not a setting. At the edge, the &lt;code&gt;c-ip&lt;/code&gt; field in standard logs shows the viewer family. At the origin, ALB access logs show the address that opened the connection, which for VPC origins is the service ENI. The daily ratio of IPv6 connections to total is the number that goes into the report. If the requirement is IPv6-only, VPC origins still doesn't meet it: IPv6 only remains excluded.&lt;/p&gt;

&lt;h2&gt;
  
  
  Network prerequisites that fail silently
&lt;/h2&gt;

&lt;p&gt;The CloudFront side is one field. The real work is in the VPC, and much of it fails without a clear message.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;IPv6 in the VPC and subnets:&lt;/strong&gt; the ALB only accepts &lt;code&gt;IpAddressType: dualstack&lt;/code&gt; if the VPC and its subnets have an associated IPv6 CIDR block, the route tables route IPv6, and the SG and NACL allow IPv6. In an older account built IPv4-only, that is a project, not a click.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An address for the ENI:&lt;/strong&gt; the subnet where the VPC origin lives needs a free IPv6 address. A /28 that runs out of IPv4 is already a known problem; with dual-stack the same math repeats in the other family.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Origin security group:&lt;/strong&gt; the documentation gives two options. Managed prefix lists, and for dual-stack that means both, &lt;code&gt;com.amazonaws.global.cloudfront.origin-facing&lt;/code&gt; and &lt;code&gt;com.amazonaws.global.ipv6.cloudfront.origin-facing&lt;/code&gt;. The VPC documentation assigns the CloudFront list a weight of 55, against the default of 60 rules per security group, which leaves 5 rules for everything else. The other option is to reference &lt;code&gt;CloudFront-VPCOrigins-Service-SG&lt;/code&gt;: it takes one rule and restricts the origin to your distributions, not to any CloudFront distribution. That is the one I use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Outbound NACL:&lt;/strong&gt; inbound NACL rules are not evaluated for traffic from CloudFront, but outbound rules are, on the return path, on ephemeral ports 1024–65535. A NACL keeps IPv6 rules separate from IPv4 ones. If you only allowed &lt;code&gt;0.0.0.0/0&lt;/code&gt;, you will watch the IPv6 handshake die on the way back.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Limits that still apply:&lt;/strong&gt; IPv6 only is still unavailable for VPC origins. gRPC and Lambda@Edge origin triggers are not supported. A Gateway Load Balancer and an NLB with a TLS listener cannot be origins, and an NLB must have a security group attached. And IPv6 ends at the ALB: the connection to targets uses the target group's family, so a dual-stack ALB with an IPv4 target group is IPv6 to the ALB, not to the application.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Migrating a VPC origin already in production
&lt;/h2&gt;

&lt;p&gt;Here is the cost the announcement does not show. Per the documented procedure, updating a VPC origin requires first making sure it is not the default origin of any cache behavior, then disassociating it from &lt;strong&gt;every&lt;/strong&gt; distribution that uses it, editing, waiting for the status to return to &lt;strong&gt;Deployed&lt;/strong&gt; (up to 15 minutes), and only then associating it again. In production, that is a scheduled outage window, multiplied by the number of distributions sharing the origin.&lt;/p&gt;

&lt;p&gt;I would not edit the existing VPC origin. The safer path is to create a second, dual-stack VPC origin pointing at the same ALB ARN, and swap origins through CloudFront continuous deployment: a staging distribution gets the new origin, you test with a slice of traffic, and you promote the configuration when the signals are clean. Rollback is promoting the previous configuration back, without waiting 15 minutes for a VPC origin to propagate.&lt;/p&gt;

&lt;p&gt;The signals I would watch on staging: the distribution's &lt;code&gt;5xxErrorRate&lt;/code&gt; and &lt;code&gt;OriginLatency&lt;/code&gt; in CloudWatch, the ALB's &lt;code&gt;TargetConnectionErrorCount&lt;/code&gt; and &lt;code&gt;HTTPCode_ELB_5XX_Count&lt;/code&gt;, and the share of IPv6 connections in the ALB access logs. If that share stays at zero, the cause is almost always an SG or NACL missing the IPv6 rule, and CloudFront falls back to IPv4 without complaining, which is good for availability and terrible for the target.&lt;/p&gt;

&lt;p&gt;If you share VPC origins across accounts through AWS RAM, add coordination with the owner of every consuming distribution.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to adopt without an outage window
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Prepare the VPC&lt;/strong&gt;: Associate an IPv6 CIDR with the VPC and private subnets, adjust route tables and confirm each subnet has a free IPv6 address for the ENI.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Move the ALB to dual-stack&lt;/strong&gt;: Run &lt;code&gt;aws elbv2 set-ip-address-type --ip-address-type dualstack&lt;/code&gt; on the internal ALB; decide separately whether the target group also moves to IPv6.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Lock down SG and NACL&lt;/strong&gt;: ALB inbound from &lt;code&gt;CloudFront-VPCOrigins-Service-SG&lt;/code&gt;; NACL outbound on ephemeral 1024–65535 for both IPv4 and IPv6.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Create a new VPC origin&lt;/strong&gt;: Origin type dual-stack, same ALB ARN. Don't edit the existing one; wait for Deployed status.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Test on staging and promote&lt;/strong&gt;: Staging distribution with the new origin, partial traffic, 5xx signals and IPv6 share; promote only when both are clean.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Finish the edge&lt;/strong&gt;: Enable viewer IPv6 and add a second AAAA alias in Route 53 for each alternate domain name; review signed URLs using a custom &lt;code&gt;IpAddress&lt;/code&gt; policy.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Anti-patterns
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Editing the VPC origin in production:&lt;/strong&gt; the procedure requires disassociating from every distribution, and the wait of up to 15 minutes becomes an outage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claiming compliance because the field says dual-stack:&lt;/strong&gt; CloudFront picks the family per connection; without log measurement, there is no evidence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Allowing the prefix lists when the origin only serves your own CloudFront:&lt;/strong&gt; it consumes 55 of the default 60 rules and accepts any distribution, not just yours.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Forgetting the target group:&lt;/strong&gt; a dual-stack ALB with IPv4 targets delivers IPv6 to the load balancer, not to the application.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Curator's note:&lt;/strong&gt; In a financial environment I would turn this on first for a low-risk distribution, with staging and an IPv6 share target written down before the change. I have seen an IP family migration pass functional testing and fail the first audit report, because the fallback to IPv4 is silent by design. The lesson: in networking, whatever degrades without an error needs its own metric, otherwise you find out from the wrong person. And I would not edit an existing VPC origin under any circumstances; creating another one and swapping via continuous deployment costs an afternoon and saves a 2 AM window.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Verdict
&lt;/h2&gt;

&lt;p&gt;Adopt it when you have an end-to-end IPv6 requirement, contractual, regulatory or platform-driven, and the origin already is or needs to be in a private subnet: there is no additional cost and the security model does not get worse. Wait if the requirement is IPv6-only, because IPv6 only is still out for VPC origins, or if the VPC has no IPv6 CIDR yet, because then the project is a network project, not a CDN one. Either way, create a new VPC origin and swap via staging; do not edit the one serving traffic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rating:&lt;/strong&gt; 4/5&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/about-aws/whats-new/2026/10/cloudfront-vpc-origin-dualstack/" rel="noopener noreferrer"&gt;AWS What's New: Amazon CloudFront announces dual-stack support for VPC origins (15/10/2026)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-vpc-origins.html" rel="noopener noreferrer"&gt;Amazon CloudFront Developer Guide: Restrict access with VPC origins&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cloudfront-enable-ipv6.html" rel="noopener noreferrer"&gt;Amazon CloudFront Developer Guide: Enable IPv6 for CloudFront distributions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/about-aws/whats-new/2025/09/amazon-cloudfront-ipv6-origins/" rel="noopener noreferrer"&gt;AWS What's New: Amazon CloudFront announces support for IPv6 origins (09/2025)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/vpc/latest/userguide/working-with-aws-managed-prefix-lists.html" rel="noopener noreferrer"&gt;Amazon VPC User Guide: AWS-managed prefix lists and weights&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-ip-address-type.html" rel="noopener noreferrer"&gt;Elastic Load Balancing: Update the IP address types for your Application Load Balancer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/blogs/networking-and-content-delivery/optimizing-application-performance-the-strategic-benefits-of-combining-amazon-cloudfront-with-application-load-balancers" rel="noopener noreferrer"&gt;AWS Networking &amp;amp; Content Delivery Blog: Combining Amazon CloudFront with Application Load Balancers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://fernando.moretes.com/blog/cloudfront-vpc-origins-dual-stack-ipv6-ate-a-origem-privada-com" rel="noopener noreferrer"&gt;fernando.moretes.com&lt;/a&gt;. By Fernando F. Azevedo: Senior Solutions Architect.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>awscloud</category>
      <category>cloudfront</category>
      <category>vpcorigins</category>
      <category>ipv6</category>
    </item>
    <item>
      <title>Well-Architected Agent: architecture review becomes a weekly process</title>
      <dc:creator>Fernando Azevedo</dc:creator>
      <pubDate>Tue, 13 Oct 2026 18:24:40 +0000</pubDate>
      <link>https://dev.to/fernando_azevedo_6844e930/well-architected-agent-architecture-review-becomes-a-weekly-process-58o</link>
      <guid>https://dev.to/fernando_azevedo_6844e930/well-architected-agent-architecture-review-becomes-a-weekly-process-58o</guid>
      <description>&lt;p&gt;For years, the Well-Architected review was an event: a week-long workshop, a spreadsheet of high and medium risks, and then six months with nobody opening the report. On 10/01/2026 AWS put AWS Well-Architected Agent into preview, which replaces the event with a cycle: scheduled scans, up to 30 recommendations per run, weekly refresh. The question that interests me is not "is the agent right?", it's "who triages 30 findings a week, and what happens to what it cannot see?". After 16 years operating financial platforms on AWS, I've learned that a findings tool without a triage owner becomes just another red dashboard everyone learns to ignore.&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers that shape the design
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;100&lt;/strong&gt;: accounts per profile. scanning every commercial Region from us-east-1, us-east-2 or us-west-2&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;30&lt;/strong&gt;: recommendations per run. with a 7-day cooldown between scheduled runs of the same profile&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;5/dia&lt;/strong&gt;: IaC reviews per profile. zip up to 25 MB; S3 folder up to 100 MB, file up to 1 MB&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The signal: an aggregator with an opinion
&lt;/h2&gt;

&lt;p&gt;Strip the AI layer and what AWS shipped is an aggregator with an opinion. The related-services documentation is explicit: the agent ingests &lt;strong&gt;Trusted Advisor&lt;/strong&gt; findings as baseline signals, uses &lt;strong&gt;Compute Optimizer&lt;/strong&gt; rightsizing signals for EC2, Lambda and RDS, consumes &lt;strong&gt;Security Hub CSPM&lt;/strong&gt;, &lt;strong&gt;Resilience Hub&lt;/strong&gt; assessment data (with its RTO/RPO) and &lt;strong&gt;Cost Optimization Hub&lt;/strong&gt;. On top of that it adds three things: prioritization against goals you write, cross-pillar trade-off analysis, and ready remediation (console, updated IaC, &lt;code&gt;aws&lt;/code&gt; CLI commands and Systems Manager runbooks).&lt;/p&gt;

&lt;p&gt;Recommendations come at three levels. &lt;strong&gt;Resource:&lt;/strong&gt; one instance, one function, one database, with dollar impact where applicable. &lt;strong&gt;Application:&lt;/strong&gt; consolidated findings across related resources, a format the documentation itself marks as beta. &lt;strong&gt;Architecture:&lt;/strong&gt; generated from CloudFormation, Terraform or CDK templates sent as a zip or S3 URI, before deployment.&lt;/p&gt;

&lt;p&gt;Two operational details change result quality. Without &lt;strong&gt;Cost Explorer&lt;/strong&gt; enabled, the agent estimates cost from public pricing, which ignores Savings Plans, Reserved Instances and contractual discounts; in an account with an EDP, the "dollar impact" can be inflated enough to invert prioritization. And access requires a &lt;strong&gt;Business+&lt;/strong&gt; Support plan or higher: Business+ gets 2 profiles and 7 applications per profile; Enterprise On-Ramp, Enterprise and Unified Operations get 10 and 30. Developer and Business are excluded.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changes for architects
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Goals became configuration:&lt;/strong&gt; up to 10 goals per profile, plain text, one pillar each, and they order the queue. A badly written goal mis-prioritizes without raising any error.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Continuous review has fixed throughput:&lt;/strong&gt; 30 recommendations per run and a 7-day cooldown mean a triage ceiling per profile. The bottleneck becomes the team, not the tool.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shift-left with a quota:&lt;/strong&gt; 5 IaC reviews per day per profile cannot back a gate on every pull request. They fit on merge to the release branch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Criticality is declared, not inferred:&lt;/strong&gt; application context accepts &lt;code&gt;MISSION_CRITICAL&lt;/code&gt;, &lt;code&gt;BUSINESS_CRITICAL&lt;/code&gt;, &lt;code&gt;NON_CRITICAL&lt;/code&gt; and &lt;code&gt;TEST_DEVELOPMENT&lt;/code&gt;. Whoever skips it gets recommendations tuned for the average case.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Silence is not compliance:&lt;/strong&gt; the unsupported type list includes &lt;code&gt;AWS::WAFv2::WebACL&lt;/code&gt;, &lt;code&gt;AWS::Organizations::Policy&lt;/code&gt; and &lt;code&gt;AWS::IAM::RolePolicy&lt;/code&gt;. No finding there proves nothing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The blind spot that matters in a bank
&lt;/h2&gt;

&lt;p&gt;The quotas page carries a long table of CloudFormation resource types the agent does not analyze. Most are peripheral (IoT Analytics, DeviceFarm, Evidently). But a few are exactly the controls an auditor checks first in an environment under BACEN 4.893 or PCI-DSS:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;AWS::WAFv2::WebACL&lt;/code&gt;, &lt;code&gt;WebACLAssociation&lt;/code&gt;, &lt;code&gt;IPSet&lt;/code&gt; and &lt;code&gt;RuleGroup&lt;/code&gt;: the entire WAF edge.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AWS::Organizations::Policy&lt;/code&gt;: SCPs, that is, the organization's data perimeter.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AWS::IAM::RolePolicy&lt;/code&gt;, &lt;code&gt;UserPolicy&lt;/code&gt; and &lt;code&gt;GroupPolicy&lt;/code&gt;: inline policies, where the &lt;code&gt;"Action": "*"&lt;/code&gt; nobody remembers writing usually lives.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AWS::OpenSearchService::Domain&lt;/code&gt;, &lt;code&gt;AWS::Shield::Protection&lt;/code&gt; and Security Hub's own configuration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The failure mode here is silent. The report arrives clean on the security pillar, someone takes the screenshot to the risk committee, and the absence of WAF findings is read as a well-configured WAF. It isn't: it's an unanalyzed WAF.&lt;/p&gt;

&lt;p&gt;The mitigation is cheap and has to be explicit. Keep those controls covered by AWS Config conformance packs and Security Hub CSPM directly, outside the agent, and write in the governance document which types the agent does not cover and which tool covers each one. The list is marked preview and should shrink, so review it every quarter instead of assuming it is stable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the agent reads, where it writes
&lt;/h2&gt;

&lt;p&gt;Role chain in a dedicated account, input signals and remediation paths. Only the IaC path goes back through the pipeline.&lt;/p&gt;

&lt;h3&gt;
  
  
  🔐 AWS: Conta de tooling dedicada
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Agent profile us-east-1 · 10 metas (ai)&lt;/li&gt;
&lt;li&gt;Execution role trust wellarchitected.amazonaws.com (security)&lt;/li&gt;
&lt;li&gt;CloudTrail API wellarchitected:* (security)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🟧 AWS: Contas de workload (até 100)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Access role WellArchitectedAgentResourceScanning (security)&lt;/li&gt;
&lt;li&gt;Recursos todas as regiões comerciais (compute)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  📡 AWS: Sinais de entrada
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Trusted Advisor Security Hub CSPM (data)&lt;/li&gt;
&lt;li&gt;Compute Optimizer Cost Optimization Hub (data)&lt;/li&gt;
&lt;li&gt;Cost Explorer sem ele: preço público (data)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  📤 Saída: Remediação
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;IaC atualizado PR no repositório (ci)&lt;/li&gt;
&lt;li&gt;AWS CLI / SSM runbook só em sandbox (external)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Flows
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;profile -&amp;gt; exec: assumes&lt;/li&gt;
&lt;li&gt;exec -&amp;gt; access: sts:AssumeRole, ExternalId = profile ARN&lt;/li&gt;
&lt;li&gt;access -&amp;gt; res: config read&lt;/li&gt;
&lt;li&gt;ta -&amp;gt; profile: baseline findings&lt;/li&gt;
&lt;li&gt;co -&amp;gt; profile: rightsizing and cost&lt;/li&gt;
&lt;li&gt;ce -&amp;gt; profile: actual cost per resource&lt;/li&gt;
&lt;li&gt;profile -&amp;gt; ct: audit&lt;/li&gt;
&lt;li&gt;profile -&amp;gt; owner: 30 recommendations/week&lt;/li&gt;
&lt;li&gt;owner -&amp;gt; iac: accepts and versions&lt;/li&gt;
&lt;li&gt;owner -&amp;gt; cli: tests outside prod&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  IAM: the design is good, the default less so
&lt;/h2&gt;

&lt;p&gt;The access model deserves credit. Instead of a service-linked role, the agent uses customer-managed roles in a chain: an &lt;strong&gt;execution role&lt;/strong&gt; in the profile account, trusting &lt;code&gt;wellarchitected.amazonaws.com&lt;/code&gt; and holding only &lt;code&gt;sts:AssumeRole&lt;/code&gt;, and an &lt;strong&gt;access role&lt;/strong&gt; in each workload account, with the read-only &lt;code&gt;WellArchitectedAgentResourceScanning&lt;/code&gt; managed policy. The profile ARN is used as the external ID, which closes the confused deputy, and there is an &lt;code&gt;iam:PassRole&lt;/code&gt; check at creation. The documentation states neither role allows modifying resources. Onboarding is opt-in per account.&lt;/p&gt;

&lt;p&gt;That said, three adjustments I'd make before turning it on in production:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dedicated account:&lt;/strong&gt; AWS itself recommends hosting the profile in an account with no workloads. In a Control Tower environment, that is the tooling or audit account, never the management account.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trust by exact ARN:&lt;/strong&gt; use the execution role ARN, with the &lt;code&gt;service-role/&lt;/code&gt; path the console creates, not the account root. Roll out access roles via StackSets with the same name in every account (the common-name pattern the profile accepts) so that revoking is one operation, not a hundred.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Processing residency:&lt;/strong&gt; the profile can only live in &lt;code&gt;us-east-1&lt;/code&gt;, &lt;code&gt;us-east-2&lt;/code&gt; or &lt;code&gt;us-west-2&lt;/code&gt;, even though it scans &lt;code&gt;sa-east-1&lt;/code&gt;. Configuration metadata and tags leave the Region for analysis. If your tagging policy carries customer names or CPF numbers in tags (I've seen it), that becomes a conversation with the DPO under LGPD before it becomes one with the auditor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four remediation paths, four maintenance costs
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Criterion&lt;/th&gt;
&lt;th&gt;Console&lt;/th&gt;
&lt;th&gt;Updated IaC&lt;/th&gt;
&lt;th&gt;AWS CLI&lt;/th&gt;
&lt;th&gt;SSM runbook&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Creates drift&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes, unless the runbook updates the code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Review trail&lt;/td&gt;
&lt;td&gt;CloudTrail only&lt;/td&gt;
&lt;td&gt;Pull request with approval&lt;/td&gt;
&lt;td&gt;CloudTrail only&lt;/td&gt;
&lt;td&gt;SSM execution history&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Where I use it&lt;/td&gt;
&lt;td&gt;Understanding the finding&lt;/td&gt;
&lt;td&gt;Production&lt;/td&gt;
&lt;td&gt;Sandbox&lt;/td&gt;
&lt;td&gt;Repeatable action with no IaC state, e.g. orphaned snapshot&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  How I'd position it today
&lt;/h2&gt;

&lt;p&gt;The agent doesn't replace a human Well-Architected review, it changes what the human review has to cover. The mechanical work (oversized instance, bucket without versioning, single-AZ RDS tagged &lt;code&gt;MISSION_CRITICAL&lt;/code&gt;) leaves the workshop agenda. What remains for people is what the agent admittedly cannot see: SCPs, WAF, inline policies, and architecture decisions that show up in no configuration at all, like why a settlement flow is synchronous.&lt;/p&gt;

&lt;p&gt;In practice, three design decisions:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One profile per regulatory domain, not per team:&lt;/strong&gt; with 10 profiles on Enterprise, separate what has distinct SLOs and regimes (payments, data, internal platform). On Business+, with 2 profiles and 7 applications, the choice is harder: regulated production in one, everything else in the other.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Goals versioned in a repository:&lt;/strong&gt; plain-text goals are a ranking parameter. Treat them as code: a file in the platform repository, applied through the &lt;code&gt;wellarchitected&lt;/code&gt; namespace API, with review. A goal like "reduce cost" is weak; "reduce non-production compute cost by 20% without touching the core's 15-minute RTO" gives the agent a trade-off to respect.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A queue with an owner and an SLA:&lt;/strong&gt; 30 recommendations a week is about 120 a month per profile. Without an owner who marks items complete or suppresses them with a reason, the backlog grows linearly and the tool loses credibility in two months. I'd turn on cost and resilience first, where the cost of error is low, and security only later, with the unsupported list beside it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anti-patterns you can already predict
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Running the CLI command in production:&lt;/strong&gt; it fixes the finding and creates drift; the next &lt;code&gt;terraform apply&lt;/code&gt; reverts the fix and nobody notices until the next scan.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reading a clean security pillar as attestation:&lt;/strong&gt; with WAF, SCPs and inline policies out of scope, a clean report is partial coverage, not compliance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prioritizing by dollars without Cost Explorer:&lt;/strong&gt; the agent falls back to public pricing and ignores Savings Plans and EDP; the cost queue ends up sorted by a number that isn't yours.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Profile in the management account:&lt;/strong&gt; it concentrates the role that reaches 100 accounts in the place with the largest blast radius in the organization.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IaC gate on every PR:&lt;/strong&gt; 5 reviews per day per profile run out before lunch; the gate either fails open or blocks the team.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The goal is the new prompt:&lt;/strong&gt; The goals text defines the order of the remediation queue across 100 accounts. That is production configuration with effects on money and risk, written in natural language with no validation. Whoever edits goals needs the same change control as whoever edits SCPs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Curator's note:&lt;/strong&gt; I'd turn the agent on this week in a dedicated tooling account, cost and resilience only, with Cost Explorer enabled before the first run. Access roles go out through StackSets, trust pinned to the exact execution role ARN, and goals live in a versioned file. The lesson I carry from years of Trusted Advisor and Security Hub: the cost of a findings tool is not the license, it's the weekly triage someone has to sustain for years. If there is no named owner for the queue, I'd rather not turn it on.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Verdict
&lt;/h2&gt;

&lt;p&gt;Adopt now if you already have a Business+ plan or higher, Control Tower with a tooling account, and a platform team able to triage about 120 items a month per profile: the agent removes the mechanical work from reviews and delivers IaC remediation that fits your pipeline. Wait if your main need is security evidence for regulatory audit: while WAF, SCPs and inline policies are out of scope, it complements AWS Config and Security Hub CSPM, it does not replace them. Either way, production remediation goes only through the IaC path.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rating:&lt;/strong&gt; Adotar com escopo / Adopt with scope&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/blogs/aws/announcing-aws-well-architected-agent-an-ai-powered-intelligence-to-optimize-your-cloud-environment-preview/" rel="noopener noreferrer"&gt;AWS News Blog: Announcing AWS Well-Architected Agent (preview)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/wellarchitected/latest/userguide/agent.html" rel="noopener noreferrer"&gt;What is AWS Well-Architected Agent (preview)?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/wellarchitected/latest/userguide/security-iam-agent.html" rel="noopener noreferrer"&gt;AWS Well-Architected Agent access model (IAM)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/wellarchitected/latest/userguide/agent-quotas.html" rel="noopener noreferrer"&gt;AWS Well-Architected Agent quotas and limits&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/wellarchitected/latest/userguide/agent-related-services.html" rel="noopener noreferrer"&gt;AWS Well-Architected Agent related services&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/wellarchitected/latest/userguide/agent-concepts.html" rel="noopener noreferrer"&gt;AWS Well-Architected Agent concepts and terminology&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.sdxcentral.com/news/aws-unveils-well-architected-ai-agent-that-acts-like-a-cloud-architect/" rel="noopener noreferrer"&gt;SDxCentral: AWS unveils Well-Architected AI agent&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://fernando.moretes.com/blog/well-architected-agent-a-revisao-de-arquitetura-vira-processo-semanal" rel="noopener noreferrer"&gt;fernando.moretes.com&lt;/a&gt;. By Fernando F. Azevedo: Senior Solutions Architect.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>awscloud</category>
      <category>wellarchitected</category>
      <category>aiagents</category>
      <category>finops</category>
    </item>
    <item>
      <title>Sharing a GPU cluster on HyperPod: three designs and when each one pays off</title>
      <dc:creator>Fernando Azevedo</dc:creator>
      <pubDate>Sat, 10 Oct 2026 16:19:29 +0000</pubDate>
      <link>https://dev.to/fernando_azevedo_6844e930/sharing-a-gpu-cluster-on-hyperpod-three-designs-and-when-each-one-pays-off-3o50</link>
      <guid>https://dev.to/fernando_azevedo_6844e930/sharing-a-gpu-cluster-on-hyperpod-three-designs-and-when-each-one-pays-off-3o50</guid>
      <description>&lt;p&gt;The question that usually opens the shared GPU cluster discussion is "how do I split the machines across teams?". The question that decides whether the design survives is a different one: when the research team's training job gets evicted at 2 AM to make room for an inference endpoint, who finds out, how many GPU-hours were thrown away, and which cost center absorbs them? The reference architecture AWS published on 10/08/2026 for SageMaker HyperPod on EKS answers a good part of that. Here I put it side by side with the two alternatives I see most often and say under which conditions each one is worth its maintenance cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real choice: three ways to split GPUs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Option A, one shared HyperPod EKS cluster with Task Governance:&lt;/strong&gt; the design in the post. Each team gets a &lt;code&gt;hyperpod-ns-&amp;lt;team&amp;gt;&lt;/code&gt; namespace, its own SageMaker domain for Studio users, and a permission set in IAM Identity Center for CLI users. Fairness across teams belongs to Task Governance, built on Kueue: guaranteed quota per team, lending of idle capacity, and priority classes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option B, one cluster per team, ideally in separate accounts:&lt;/strong&gt; the boundary is the AWS account. There is no noisy neighbor because there is no neighbor. In exchange, each team carries its own capacity floor, and GPUs idle in one cluster cannot rescue a full queue in another.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option C, a shared cluster with namespaces and &lt;code&gt;ResourceQuota&lt;/code&gt; only:&lt;/strong&gt; this is what shows up when the platform starts as a single-team cluster and other teams trickle in. It works until the first week of contention, when the quota becomes a hard ceiling and arrival order decides who gets to train.&lt;/p&gt;

&lt;p&gt;The difference between the three is not the cost of standing up the cluster. It is what each one demands in continuous operation: reviewing quotas, debugging Kueue admission, maintaining the UID/GID webhook, reconciling chargeback. That is the cost that stays for years, and it is the one I compare on.&lt;/p&gt;

&lt;h2&gt;
  
  
  From login to GPU: where each boundary in design A is enforced
&lt;/h2&gt;

&lt;p&gt;Identity decides the namespace, the namespace decides the queue, the queue decides whether the pod runs on its own or borrowed quota, and the namespace becomes the chargeback key.&lt;/p&gt;

&lt;h3&gt;
  
  
  🔐 IAM Identity Center: identidade
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Permission set AWSReservedSSO_TeamA_* (security)&lt;/li&gt;
&lt;li&gt;SageMaker domain TeamA-domain + TeamA-role (security)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🟧 AWS: HyperPod EKS control plane
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;EKS access entries escopo: hyperpod-ns-team-a (security)&lt;/li&gt;
&lt;li&gt;Admission webhook UID/GID via sessionName (compute)&lt;/li&gt;
&lt;li&gt;DynamoDB mapa POSIX (data)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🤖 Task Governance: Kueue
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;LocalQueue hyperpod-ns-team-a-localqueue (messaging)&lt;/li&gt;
&lt;li&gt;ClusterQueue cota garantida + borrow limit (messaging)&lt;/li&gt;
&lt;li&gt;Idle resource sharing IdleResourceSharing: Enabled (messaging)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🟧 AWS: pool de GPU compartilhado
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Nós HyperPod health checks + auto-recovery (compute)&lt;/li&gt;
&lt;li&gt;FSx for Lustre /fsx/TeamA (storage)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  📤 Saída: custo e visibilidade
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Kubecost custo por namespace (data)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Flows
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;idp -&amp;gt; ps: federates groups&lt;/li&gt;
&lt;li&gt;user -&amp;gt; ps: aws sso login&lt;/li&gt;
&lt;li&gt;user -&amp;gt; dom: Studio&lt;/li&gt;
&lt;li&gt;ps -&amp;gt; ae: kubectl&lt;/li&gt;
&lt;li&gt;dom -&amp;gt; ae: execution role&lt;/li&gt;
&lt;li&gt;ae -&amp;gt; wh: pod created&lt;/li&gt;
&lt;li&gt;wh -&amp;gt; ddb: resolves UID/GID&lt;/li&gt;
&lt;li&gt;wh -&amp;gt; lq: queue-name label&lt;/li&gt;
&lt;li&gt;lq -&amp;gt; cq: admission&lt;/li&gt;
&lt;li&gt;cq -&amp;gt; idle: over quota: borrows&lt;/li&gt;
&lt;li&gt;cq -&amp;gt; gpu: admits pod&lt;/li&gt;
&lt;li&gt;gpu -&amp;gt; fsx: checkpoint&lt;/li&gt;
&lt;li&gt;gpu -&amp;gt; cost: usage per namespace&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Isolation: a namespace is an organizational boundary, not a security one
&lt;/h2&gt;

&lt;p&gt;The post itself states it plainly: a namespace is an isolation boundary, not a hard security boundary, and the design targets several teams within one company, not tenants who do not trust each other. I agree, and I add what that means in a regulated environment.&lt;/p&gt;

&lt;p&gt;Design A stacks four controls: EKS access entries scoped per namespace (one for the domain role, another for the &lt;code&gt;AWSReservedSSO_&amp;lt;permission-set&amp;gt;_&amp;lt;id&amp;gt;&lt;/code&gt; role), RBAC that returns &lt;code&gt;Forbidden&lt;/code&gt; on cross-namespace calls, a default-deny &lt;code&gt;NetworkPolicy&lt;/code&gt; per namespace, and S3 restricted to the team's prefix. All four run on the same kernel, the same NVIDIA drivers and the same control plane.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Silent failure number one:&lt;/strong&gt; a &lt;code&gt;NetworkPolicy&lt;/code&gt; without a CNI that enforces it is just YAML. If the Amazon VPC CNI does not have network policy enabled, &lt;code&gt;kubectl apply&lt;/code&gt; succeeds, the object exists, and not a single packet is blocked. Test with a team B pod trying to open a connection to a team A service before you call the boundary done.&lt;/p&gt;

&lt;p&gt;My rule is this. If the teams share the same regulatory regime and the same data classification, a namespace is enough. If one team trains on customer data under LGPD and another on synthetic data, or if a BACEN 4.893 auditor is going to ask for demonstrable segregation, the right boundary is the account, and the design goes back to B.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fairness: quota, borrowing, and who gets evicted
&lt;/h2&gt;

&lt;p&gt;Task Governance splits two policies. &lt;strong&gt;Compute prioritization&lt;/strong&gt; decides how idle capacity is handed out (first-come first-serve or fair-share) and how tasks are queued (arrival order or task ranking with priority classes). &lt;strong&gt;Compute allocation&lt;/strong&gt; sets, per team, the guaranteed quota, a fair-share weight from 0 to 100, and the lending posture: Lend and Borrow, Lend, or Don't Lend. The borrow limit can be a percentage, up to 10,000% of quota, or absolute per instance type.&lt;/p&gt;

&lt;p&gt;The documentation example shows the mechanics: team A with a quota of 6 using 2, team B with a quota of 5 using 4, a 4-unit job lands on team B and 3 units are borrowed from A. What the documentation also says, and what defines operations, is that a task running on borrowed quota can be preempted by a higher-priority task when capacity runs out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Silent failure number two:&lt;/strong&gt; a job without &lt;code&gt;kueue.x-k8s.io/priority-class&lt;/code&gt; gets the lowest priority. An inference endpoint deployed without the label becomes the first eviction candidate in the cluster.&lt;/p&gt;

&lt;p&gt;Preemption has a price in GPU-hours. A 16-GPU training run on borrowed capacity with a checkpoint every 30 minutes loses up to 8 GPU-hours per eviction, 4 on average. If the cluster evicts that job twice a day, that is roughly 240 GPU-hours a month thrown away without tripping a single alert. That is why I treat borrowed capacity like spot: only jobs with checkpoints on FSx and idempotent resume run on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chargeback: who pays for idle and borrowed GPUs
&lt;/h2&gt;

&lt;p&gt;The post uses Kubecost grouped by namespace, and since the namespace is already the team, no extra workload tagging is needed. That works for the question "how much did team A consume". On its own it does not answer two questions that show up at the first month-end close.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The borrowed GPU:&lt;/strong&gt; when team B runs on team A's quota, the pod lives in B's namespace. Charging B is correct, but team A will look at the report and see it paid for a guaranteed quota someone else used. Decide upfront whether the guaranteed quota is a fixed price (team A pays for the reservation, used or not) or usage-based. Both rules are defensible; changing the rule mid-quarter is not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The idle GPU:&lt;/strong&gt; capacity not allocated to any quota needs an owner in the report. Split Cost Allocation Data for EKS, which started covering GPU, Trainium and Inferentia on 09/02/2025, splits cost per pod and reports unused capacity separately, with a 9:1 GPU versus CPU and memory weighting in the documentation example. That example is an EC2 instance. HyperPod nodes are &lt;code&gt;ml.*&lt;/code&gt; instances billed through SageMaker, so confirm on your bill whether that data shows up before you swap Kubecost for CUR. I would keep Kubecost as the chargeback source and use CUR to reconcile the total.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three designs side by side
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Criterion&lt;/th&gt;
&lt;th&gt;A: shared HyperPod + Task Governance&lt;/th&gt;
&lt;th&gt;B: one cluster per team&lt;/th&gt;
&lt;th&gt;C: namespaces + ResourceQuota&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Isolation boundary&lt;/td&gt;
&lt;td&gt;Namespace + access entry + NetworkPolicy&lt;/td&gt;
&lt;td&gt;AWS account&lt;/td&gt;
&lt;td&gt;Namespace + RBAC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One team's idle GPU&lt;/td&gt;
&lt;td&gt;Lent, with limits and preemption&lt;/td&gt;
&lt;td&gt;Stays idle&lt;/td&gt;
&lt;td&gt;Stays idle (quota is a hard cap)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Contention&lt;/td&gt;
&lt;td&gt;Fair-share by 0–100 weight + task ranking&lt;/td&gt;
&lt;td&gt;None across teams&lt;/td&gt;
&lt;td&gt;Arrival order&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Chargeback&lt;/td&gt;
&lt;td&gt;Per namespace, needs a borrowing rule&lt;/td&gt;
&lt;td&gt;Account bill, trivial&lt;/td&gt;
&lt;td&gt;Per namespace&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Parts to maintain&lt;/td&gt;
&lt;td&gt;Quota policies, UID/GID webhook, Kubecost, dashboards&lt;/td&gt;
&lt;td&gt;N clusters, N upgrades, N pipelines&lt;/td&gt;
&lt;td&gt;Few, until the first dispute&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;When it fails badly&lt;/td&gt;
&lt;td&gt;Job without priority-class or checkpoint&lt;/td&gt;
&lt;td&gt;Low utilization becomes fixed cost&lt;/td&gt;
&lt;td&gt;Large team monopolizes the cluster&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Decision matrix
&lt;/h2&gt;

&lt;h3&gt;
  
  
  A: shared HyperPod + Task Governance
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One team's idle GPU becomes another's capacity, with &lt;code&gt;IdleResourceSharing&lt;/code&gt; covering even unallocated capacity&lt;/li&gt;
&lt;li&gt;Explicit priority: production inference preempts experiments&lt;/li&gt;
&lt;li&gt;One driver and EKS upgrade for every team&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A namespace will not pass a strong-segregation audit&lt;/li&gt;
&lt;li&gt;The UID/GID webhook and its DynamoDB map become yours to run&lt;/li&gt;
&lt;li&gt;Preemption wastes GPU-hours when jobs do not checkpoint&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Verdict:&lt;/strong&gt; Default for teams in one company under one regime&lt;/p&gt;

&lt;h3&gt;
  
  
  B: one cluster per team
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Segregation provable by account, IAM and bill&lt;/li&gt;
&lt;li&gt;A bad upgrade's blast radius stays within one team&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each team pays its own GPU floor, even when idle&lt;/li&gt;
&lt;li&gt;Platform effort multiplied by N&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Verdict:&lt;/strong&gt; When data or regulation differ across teams&lt;/p&gt;

&lt;h3&gt;
  
  
  C: namespaces + ResourceQuota
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nothing new to learn&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No borrowing, no cross-team priority, no fair-share&lt;/li&gt;
&lt;li&gt;GPU disputes become meetings, not policy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Verdict:&lt;/strong&gt; Only as a step until the second team arrives&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Guaranteed quota is not guaranteed capacity:&lt;/strong&gt; The documentation says to keep the sum of quotas below cluster capacity: 20 instances, quotas summing to under 20. But HyperPod pulls unhealthy nodes out of rotation, and idle resource sharing already excludes &lt;code&gt;NotReady&lt;/code&gt; or &lt;code&gt;unschedulable&lt;/code&gt; nodes from its math. Team quotas do not shrink with them. In a 16-node cluster with 2 nodes recovering, quotas summing to 16 promise capacity that does not exist at that moment. I keep at least one node per instance type outside any quota.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I would do:&lt;/strong&gt; I would start with design A, but with three rules written down before the first job: any manifest without &lt;code&gt;kueue.x-k8s.io/priority-class&lt;/code&gt; is rejected by an admission policy, every job running on borrowed quota writes checkpoints to FSx at a known interval, and the billing rule for borrowed quota is signed off by the cost center owners. I would also make the UID/GID webhook's &lt;code&gt;failurePolicy&lt;/code&gt; an explicit decision: &lt;code&gt;Fail&lt;/code&gt; so the cluster stops scheduling when DynamoDB does not answer, &lt;code&gt;Ignore&lt;/code&gt; only if you accept pods with the wrong POSIX identity writing to &lt;code&gt;/fsx&lt;/code&gt;. I have seen shared platforms die not from a technical limit, but because the first unexplained eviction made a team ask for its own cluster. The lesson: in a shared cluster, predictability is worth more than utilization.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Verdict
&lt;/h2&gt;

&lt;p&gt;Use &lt;strong&gt;Option A (shared HyperPod with Task Governance)&lt;/strong&gt; when the teams belong to the same company, handle data with the same classification, and someone owns the quota policies and Kueue observability (&lt;code&gt;kubectl describe workload&lt;/code&gt; has to become routine, not archaeology). Use &lt;strong&gt;Option B (cluster per account)&lt;/strong&gt; when one team operates under LGPD with customer data and another does not, or when the audit requires segregation by account; accept paying for idle GPUs as the price of isolation. Treat Option C as a transitional state: as soon as a second team competes for GPUs, move to A. The gain in design A is not having a single cluster, it is lending idle GPUs without negotiating in a meeting, and it only pays off if evictions are predictable and chargeback is agreed upfront.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rating:&lt;/strong&gt; A, com regras de admissão e checkpoint /&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/blogs/machine-learning/share-gpu-clusters-across-teams-with-isolation-and-fairness-using-amazon-sagemaker-hyperpod/" rel="noopener noreferrer"&gt;AWS ML Blog: Share GPU clusters across teams with isolation and fairness using Amazon SageMaker HyperPod (08/10/2026)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/sagemaker/latest/dg/sagemaker-hyperpod-eks-operate-console-ui-governance-policies.html" rel="noopener noreferrer"&gt;Amazon SageMaker AI docs: HyperPod task governance policies&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/blogs/machine-learning/best-practices-for-amazon-sagemaker-hyperpod-task-governance/" rel="noopener noreferrer"&gt;AWS ML Blog: Best practices for Amazon SageMaker HyperPod task governance&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/cur/latest/userguide/example-accelerated-instances.html" rel="noopener noreferrer"&gt;AWS Data Exports docs: Example of split cost allocation data for accelerated instances&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/about-aws/whats-new/2025/09/split-cost-allocation-data-amazon-eks-nvidia-amd-gpu-trainium-inferentia-ec2" rel="noopener noreferrer"&gt;AWS What's New: Split Cost Allocation Data for Amazon EKS supports NVIDIA &amp;amp; AMD GPU, Trainium, and Inferentia (02/09/202&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://fernando.moretes.com/blog/cluster-de-gpu-compartilhado-no-hyperpod-tres-desenhos-e-quando-cada" rel="noopener noreferrer"&gt;fernando.moretes.com&lt;/a&gt;. By Fernando F. Azevedo: Senior Solutions Architect.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aiagents</category>
      <category>sagemakerhyperpod</category>
      <category>eks</category>
      <category>gpu</category>
    </item>
    <item>
      <title>Critical or non-critical: the failure contract of ECS daemons</title>
      <dc:creator>Fernando Azevedo</dc:creator>
      <pubDate>Thu, 08 Oct 2026 18:00:58 +0000</pubDate>
      <link>https://dev.to/fernando_azevedo_6844e930/critical-or-non-critical-the-failure-contract-of-ecs-daemons-505i</link>
      <guid>https://dev.to/fernando_azevedo_6844e930/critical-or-non-critical-the-failure-contract-of-ecs-daemons-505i</guid>
      <description>&lt;p&gt;Every container cluster carries a layer of software nobody asked for and everybody needs: the log shipper, the EDR agent, the metrics exporter, the mesh proxy. For years that layer was treated as a packaging detail, one more sidecar in the task definition, a DaemonSet on Kubernetes. The launch of non-critical daemons in Amazon ECS Managed Daemons, on September 3, 2026, exposes the question that was hiding underneath the packaging: when that agent dies, what should happen to the machine hosting it? The answer is now a parameter (&lt;code&gt;critical&lt;/code&gt;), and parameters get versioned, reviewed, and gotten wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: cross-cutting agents are coupling disguised as convenience
&lt;/h2&gt;

&lt;p&gt;The sidecar-per-task model solves distribution and fails at economics and governance. An instance running 40 tasks runs 40 copies of the log shipper; at 128 MiB each, that is 5 GiB of RAM spent duplicating one function. Worse than the waste is the drift: the agent version becomes the property of each application team, and the security team finds out during an audit that three services still run a seven-month-old image.&lt;/p&gt;

&lt;p&gt;ECS already had the &lt;code&gt;DAEMON&lt;/code&gt; scheduling strategy for the EC2 launch type, but that is placement only, one daemon per instance, with no guarantee that it is up &lt;em&gt;before&lt;/em&gt; the first application task serves traffic. For telemetry that is a hole in the graph. For a runtime security agent, it is a window in which the instance processes transactions without coverage.&lt;/p&gt;

&lt;p&gt;Managed Daemons, GA on April 1, 2026 for ECS Managed Instances capacity providers, closed that hole by inverting the relationship: the instance only transitions the application task to &lt;code&gt;RUNNING&lt;/code&gt; after the daemon is running. The price of that guarantee was rigid coupling, any daemon failure drained and replaced the instance. Correct for a compliance agent. Absurd for a metrics exporter. The September launch does not add a new capability; it undoes a coupling that should never have been the only option.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anatomy: three parts, two guarantees, one switch
&lt;/h2&gt;

&lt;p&gt;The pattern has three distinct resources. First, a &lt;strong&gt;daemon task definition&lt;/strong&gt;: its own resource type with its own ARN (&lt;code&gt;daemon-task-definition/...&lt;/code&gt;), separate from your application task definition. Second, a &lt;strong&gt;daemon&lt;/strong&gt;, created with &lt;code&gt;CreateDaemon&lt;/code&gt;, binding that definition to a cluster and to one or more ECS Managed Instances capacity providers. Third, the EC2 instances provisioned by those capacity providers, on each of which ECS keeps exactly one daemon task.&lt;/p&gt;

&lt;p&gt;The first guarantee is &lt;strong&gt;ordering&lt;/strong&gt;: ECS provisions the instance, starts the daemon task, and only then promotes the application task to &lt;code&gt;RUNNING&lt;/code&gt;. Independent measurements published in April 2026 showed the daemon reaching &lt;code&gt;RUNNING&lt;/code&gt; 18 to 21 seconds ahead of the application, with the instance held in &lt;code&gt;REGISTERING&lt;/code&gt; during that window. &lt;strong&gt;That ordering applies to every daemon, critical or not.&lt;/strong&gt; This is the point I most often see misread: marking a daemon non-critical does not cost you the guarantee that the agent comes up before traffic; it only costs you the guarantee that it &lt;em&gt;stays&lt;/em&gt; up.&lt;/p&gt;

&lt;p&gt;The second guarantee is &lt;strong&gt;remediation&lt;/strong&gt;, and that is what the &lt;code&gt;critical&lt;/code&gt; switch governs. Note how ECS remediates: it does not restart the container, it drains and replaces the whole instance. That is deliberately blunt, an agent that died may have left eBPF probes detached, network rules half-applied, buffers lost. Replacing the instance is the only cheap way back to a known state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Instance lifecycle and the criticality branch
&lt;/h2&gt;

&lt;p&gt;The startup ordering (left) is identical in both modes. The &lt;code&gt;critical&lt;/code&gt; parameter only decides what happens after the daemon fails, and the non-critical branch is only safe if the observability arm is wired up.&lt;/p&gt;

&lt;h3&gt;
  
  
  🟦 AWS: Provisioning &amp;amp; startup order
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Managed Instances capacity provider (compute)&lt;/li&gt;
&lt;li&gt;EC2 instance state: REGISTERING (compute)&lt;/li&gt;
&lt;li&gt;Daemon task exactly 1 per instance (compute)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🔀 Failure contract, critical parameter
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;critical = true | false per-daemon, default true (security)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🟥 critical=true, fail closed
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Registration blocked instance never goes ACTIVE (security)&lt;/li&gt;
&lt;li&gt;DRAINING + replace ~5 min per wave, 25% default (compute)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🟩 critical=false, fail open
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Instance stays ACTIVE running tasks untouched (compute)&lt;/li&gt;
&lt;li&gt;Placement continues registration never blocked (compute)&lt;/li&gt;
&lt;li&gt;Coverage gap silent unless observed (security)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🟧 Observability, the other half of the pattern
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;EventBridge Daemon service action (messaging)&lt;/li&gt;
&lt;li&gt;Service action log critical + non-critical (data)&lt;/li&gt;
&lt;li&gt;CloudWatch alarm + bake time rollback (data)&lt;/li&gt;
&lt;li&gt;Coverage SLO % instances with healthy daemon (data)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Flows
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;svc -&amp;gt; cp: 1. pending task&lt;/li&gt;
&lt;li&gt;cp -&amp;gt; ec2: 2. provisions EC2&lt;/li&gt;
&lt;li&gt;ec2 -&amp;gt; daemon: 3. daemon first (~18-21s)&lt;/li&gt;
&lt;li&gt;daemon -&amp;gt; gate: 4. failed / stopped / unhealthy&lt;/li&gt;
&lt;li&gt;gate -&amp;gt; block: fails to start&lt;/li&gt;
&lt;li&gt;gate -&amp;gt; drain: fails after ACTIVE&lt;/li&gt;
&lt;li&gt;gate -&amp;gt; active: critical=false&lt;/li&gt;
&lt;li&gt;active -&amp;gt; place: new tasks land&lt;/li&gt;
&lt;li&gt;place -&amp;gt; gap: no agent coverage&lt;/li&gt;
&lt;li&gt;gate -&amp;gt; eb: event in both modes&lt;/li&gt;
&lt;li&gt;gate -&amp;gt; log: service action log&lt;/li&gt;
&lt;li&gt;eb -&amp;gt; alarm: rule → SNS/Lambda&lt;/li&gt;
&lt;li&gt;alarm -&amp;gt; drain: circuit breaker rollback&lt;/li&gt;
&lt;li&gt;eb -&amp;gt; slo: feeds the coverage error budget&lt;/li&gt;
&lt;li&gt;gap -&amp;gt; slo: only visible if measured&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What actually differs between the two modes
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Criterion&lt;/th&gt;
&lt;th&gt;Critical (default)&lt;/th&gt;
&lt;th&gt;Non-critical (critical=false)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Daemon fails to start&lt;/td&gt;
&lt;td&gt;Instance never completes registration; no application task is placed on it&lt;/td&gt;
&lt;td&gt;Registration is never blocked; application tasks launch immediately&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Daemon stops or goes unhealthy later&lt;/td&gt;
&lt;td&gt;ECS drains and replaces the whole instance (auto-repair)&lt;/td&gt;
&lt;td&gt;Instance stays ACTIVE; existing tasks are untouched&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Placement of new tasks&lt;/td&gt;
&lt;td&gt;Stops on the affected instance&lt;/td&gt;
&lt;td&gt;Continues normally, agent or no agent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Startup ordering&lt;/td&gt;
&lt;td&gt;Daemon before the application&lt;/td&gt;
&lt;td&gt;Daemon before the application, identical&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Signal emitted on failure&lt;/td&gt;
&lt;td&gt;EventBridge event + service action log&lt;/td&gt;
&lt;td&gt;EventBridge event + service action log, identical&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Residual risk you take on&lt;/td&gt;
&lt;td&gt;Fleet churn: an unstable agent turns into cascading instance replacement&lt;/td&gt;
&lt;td&gt;Blind spot: coverage degrades silently if nobody consumes the event&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  When to use each mode: the rule I apply
&lt;/h2&gt;

&lt;p&gt;The rule fits in one line: &lt;strong&gt;mark as critical the agent whose absence changes what the system is allowed to do; mark as non-critical the agent whose absence only changes what you can see.&lt;/strong&gt; A runtime EDR, a network policy enforcer, an mTLS sidecar, or a file-integrity agent required by PCI DSS fall in the first group, without them the instance should not be eligible to process a transaction. An OTel metrics collector, a node exporter, a continuous profiler fall in the second.&lt;/p&gt;

&lt;p&gt;The nuance lives in the log router. In financial environments the log often &lt;em&gt;is&lt;/em&gt; the audit trail: "what you can see" becomes "what you can prove", and Fluent Bit stops being auxiliary. Do not classify by tool category; classify by the regulatory obligation that stream underwrites. If the agent's local buffer survives the outage window without dropping events, non-critical is defensible; if it drops them, it is critical.&lt;/p&gt;

&lt;p&gt;The design corollary is what pays off most in practice: because criticality is per daemon, and a capacity provider accepts multiple daemons, &lt;strong&gt;do not package agents with different contracts into the same task definition&lt;/strong&gt;. A task definition bundling the EDR and the metrics collector forces the stricter contract on both, and turns a collector memory leak into fleet replacement. Two daemons, two release cycles, two owners, two criticalities.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Non-critical" is a control-plane term, not a business term. The parameter describes what ECS does to the instance, not how much that agent matters to you.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What you pay for classifying wrong, in both directions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Too critical.&lt;/strong&gt; A bad agent revision makes the task flap, and every flap becomes an instance replacement. On a 60-instance fleet with the drain percentage at its default of 25%, that is 15 simultaneous replacements per wave; at the measured ~4m50s per wave, a one-line agent change costs roughly 20 minutes of fleet rotation. The direct bill impact is modest and computable: the Managed Instances management fee for a &lt;code&gt;c6a.2xlarge&lt;/code&gt; is US$0.037/hour per instance (Oregon), so 60 instances add up to ~US$1,620/month in fee alone, plus EC2 and the overlap during draining. The expensive bill is elsewhere: application task rescheduling, drained connections, cold caches, cold JIT.&lt;/p&gt;

&lt;p&gt;Two defaults conspire here. Bake time defaults to &lt;code&gt;0&lt;/code&gt; minutes, meaning that once the last wave finishes, the window in which the circuit breaker watches alarms to roll back is nil. A regression that only shows up under load sails right through.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Too non-critical.&lt;/strong&gt; Fail-open emits no pain: the instance stays ACTIVE, the application SLO does not move, and agent coverage decays in silence. Add the documented operational trap: daemon configuration settings are &lt;strong&gt;not persisted between updates&lt;/strong&gt;, and &lt;code&gt;critical&lt;/code&gt; defaults to &lt;code&gt;true&lt;/code&gt;. An &lt;code&gt;UpdateDaemon&lt;/code&gt; fired by a pipeline without an explicit &lt;code&gt;--no-critical&lt;/code&gt; re-arms fleet-wide churn that nobody asked for. It is exactly the kind of change that only surfaces at the next incident.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anti-patterns this launch makes easy
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Marking everything non-critical to "stop the churn", you did not remove the failure, you just muted the alarm it used to pull.&lt;/li&gt;
&lt;li&gt;Flipping to non-critical without first wiring the EventBridge rule for daemon events to a destination that wakes somebody up. Fail-open with no event consumer is a compliance blind spot that looks like stability.&lt;/li&gt;
&lt;li&gt;Packaging the security agent and the telemetry agent in the same daemon task definition, forcing a single failure contract onto software with opposite risk profiles.&lt;/li&gt;
&lt;li&gt;Managing daemons via ad-hoc CLI calls: since &lt;code&gt;UpdateDaemon&lt;/code&gt; does not persist settings and &lt;code&gt;critical&lt;/code&gt; reverts to the &lt;code&gt;true&lt;/code&gt; default, every partial call is a silent contract change.&lt;/li&gt;
&lt;li&gt;Keeping bake time at 0 while configuring alarms, paying the cognitive cost of the circuit breaker without buying the observation window that makes it useful.&lt;/li&gt;
&lt;li&gt;Treating the critical daemon as a substitute for application health checking. It repairs agent coverage, not business health; a healthy agent on a disk-full instance is still "healthy".&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A Well-Architected reading of the decision
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;security&lt;/strong&gt;: Fail-closed remains the correct default for enforcement agents: a critical daemon keeps the instance from registering without coverage. Govern who may loosen it with IAM on &lt;code&gt;ecs:CreateDaemon&lt;/code&gt;/&lt;code&gt;ecs:UpdateDaemon&lt;/code&gt;, scoped by daemon ARN and conditioned on tags, so the security team owns the security daemon and the platform team owns the rest.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;reliability&lt;/strong&gt;: Non-critical removes a real correlated failure mode: an agent regression hitting every instance at once used to produce simultaneous fleet replacement. In exchange, agent recovery stops being automatic, plan explicit repair.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Reference configuration I would take to production:&lt;/strong&gt; Two daemons per capacity provider: &lt;code&gt;sec-runtime&lt;/code&gt; with &lt;code&gt;critical: true&lt;/code&gt; and &lt;code&gt;obs-otel&lt;/code&gt; with &lt;code&gt;critical: false&lt;/code&gt;. Drain percentage at 10 (not 25) so a bad wave hits 10% of the fleet, and bake time between 15 and 30 minutes with at least two CloudWatch alarms wired to the circuit breaker, one on application errors, one on the agent's own health. One EventBridge rule capturing daemon service action and daemon deployment state change events, targeting SNS for the critical daemon and a queue/Lambda feeding the coverage SLO for the non-critical one. Every daemon declared in full in IaC, never by partial CLI call, with drift detection, because &lt;code&gt;UpdateDaemon&lt;/code&gt; reverts whatever you omit. And check the instance profile: it needs &lt;code&gt;AmazonECSInstanceRolePolicyForManagedInstances&lt;/code&gt;; the legacy &lt;code&gt;AmazonEC2ContainerServiceforEC2Role&lt;/code&gt; policy stops the daemon from starting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Curator's note:&lt;/strong&gt; I have watched a memory leak in an observability agent turn into node rotation during a settlement window, and the post-mortem did not name the leak as root cause, it named the fact that nobody had ever written down which agents were allowed to die. Since then I require an explicit criticality field in the ADR for any agent before it lands on the platform, with the owner and the regulatory obligation it underwrites next to it. This launch finally gives me a place in the control plane to materialize that decision instead of leaving it implicit in a runbook. What I would not do is use &lt;code&gt;critical: false&lt;/code&gt; as a painkiller: if an agent fails often enough to hurt, the agent is the problem, and marking it non-critical merely trades a noisy incident for a silent gap. My default stays critical, and every exception needs a coverage SLO to get approved.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Verdict
&lt;/h2&gt;

&lt;p&gt;Adopt it, and treat &lt;code&gt;critical&lt;/code&gt; as an architectural decision, not a tuning flag. Keep the critical default for enforcement agents (EDR, network policy, mTLS, file integrity) and for log routers that underwrite an audit trail. Move to non-critical only metrics, profiling and telemetry whose temporary loss is not a loss of evidence, and only after the EventBridge rule has a consumer and the coverage SLO is published. Split agents with different contracts into different daemons, drop the drain percentage to 10, raise bake time to 15–30 minutes, and declare everything in IaC, because &lt;code&gt;UpdateDaemon&lt;/code&gt; silently reverts omissions to &lt;code&gt;critical: true&lt;/code&gt;. If your cluster is not on Managed Instances yet, this launch does not change your roadmap; if it is, it closes the last reasonable excuse for keeping an agent sidecar in every task definition.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rating:&lt;/strong&gt; Adopt, with the switch written down&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/about-aws/whats-new/2026/09/ecs-managed-daemons-non-critical/" rel="noopener noreferrer"&gt;AWS What's New: Amazon ECS Managed Daemons now support non-critical daemons (Sep 3, 2026)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonECS/latest/developerguide/managed-daemons.html" rel="noopener noreferrer"&gt;Amazon ECS Developer Guide: Amazon ECS Managed Daemons (daemon criticality)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonECS/latest/developerguide/managed-daemons-create-manage.html" rel="noopener noreferrer"&gt;Amazon ECS Developer Guide: Creating and managing daemons (drain percentage, bake time, UpdateDaemon)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs_cwe_events.html" rel="noopener noreferrer"&gt;Amazon ECS Developer Guide: Amazon ECS events (daemon service action, daemon deployment state change)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/about-aws/whats-new/2026/04/amazon-ecs-managed-daemons/" rel="noopener noreferrer"&gt;AWS What's New: Amazon ECS announces Managed Daemons for ECS Managed Instances (Apr 1, 2026)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/ecs/pricing/" rel="noopener noreferrer"&gt;Amazon ECS pricing: ECS Managed Instances management fee&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://shinyaz.com/en/blog/2026/04/02/ecs-managed-daemons-verification" rel="noopener noreferrer"&gt;shinyaz: Measuring ECS Managed Daemons startup order guarantees and rolling deployment behavior&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/ecs/managed-instances/" rel="noopener noreferrer"&gt;Amazon ECS Managed Instances, product overview&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://fernando.moretes.com/blog/daemon-critico-ou-nao-critico-o-contrato-de-falha-do-ecs-amazon-ecs-m" rel="noopener noreferrer"&gt;fernando.moretes.com&lt;/a&gt;. By Fernando F. Azevedo: Senior Solutions Architect.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>awscloud</category>
      <category>ecs</category>
      <category>managedinstances</category>
      <category>containers</category>
    </item>
    <item>
      <title>AMIs with a contract: anatomy of EC2's new instance-type block</title>
      <dc:creator>Fernando Azevedo</dc:creator>
      <pubDate>Thu, 08 Oct 2026 18:00:24 +0000</pubDate>
      <link>https://dev.to/fernando_azevedo_6844e930/amis-with-a-contract-anatomy-of-ec2s-new-instance-type-block-3nfc</link>
      <guid>https://dev.to/fernando_azevedo_6844e930/amis-with-a-contract-anatomy-of-ec2s-new-instance-type-block-3nfc</guid>
      <description>&lt;p&gt;Um AMI sempre foi um contrato verbal. O artefato sabe exatamente de que hardware ele precisa, driver ENA, NVMe, arquitetura do binário, extensão de CPU que a JVM compilou, e nada disso estava escrito em lugar nenhum que o &lt;code&gt;RunInstances&lt;/code&gt; pudesse ler. A partir de 4 de setembro de 2026 dá para escrever: o dono do AMI declara &lt;code&gt;SupportedInstanceTypes&lt;/code&gt;, &lt;code&gt;UnsupportedInstanceTypes&lt;/code&gt; ou os dois, e o EC2 bloqueia o lançamento fora da lista. Sem custo adicional, em todas as regiões. A leitura fácil é "menos launch quebrado". A leitura que interessa é outra, o ponto de enforcement mudou de lugar, e com ele mudou quem paga a conta da manutenção.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: the contract existed, it just wasn't executable
&lt;/h2&gt;

&lt;p&gt;Every platform team that publishes golden AMIs has lived this scene. The hardened image was built for &lt;code&gt;x86_64&lt;/code&gt;, with a statically compiled EDR agent and a kernel that only loads its network driver on a Nitro host. Someone creates an Auto Scaling group with attribute-based instance type selection asking for 4 vCPUs and 16 GiB, leaves Spot diversification wide, which is the correct recommendation for filling capacity, and EC2 picks a Graviton family because it matches the attributes and is the cheapest right now.&lt;/p&gt;

&lt;p&gt;The instance &lt;strong&gt;comes up&lt;/strong&gt;. That detail is what makes the failure mode hurt. It doesn't die at &lt;code&gt;RunInstances&lt;/code&gt;; it dies later, with no console, no network, nothing but a health check running out. The ASG waits the default 300-second grace period, marks the instance unhealthy, terminates it and tries again, and since the allocation decision hasn't changed, it picks the same pool. You get a launch/terminate loop billed at a 60-second minimum per instance created, an alarm firing at 2 AM, and a graph that looks exactly like a capacity shortage.&lt;/p&gt;

&lt;p&gt;Every defense available lived on the caller side: an &lt;code&gt;ec2:InstanceType&lt;/code&gt; condition in an IAM policy, an SCP at the Organization, &lt;code&gt;ExcludedInstanceTypes&lt;/code&gt; inside a fleet's &lt;code&gt;InstanceRequirements&lt;/code&gt;. All correct, all sharing one structural defect, they describe what &lt;strong&gt;that principal&lt;/strong&gt; may launch, not what &lt;strong&gt;that image&lt;/strong&gt; can survive. Share the AMI with 40 accounts and you replicate the rule 40 times, in 40 places that drift apart.&lt;/p&gt;

&lt;h2&gt;
  
  
  The contract travels with the artifact, and fails at the gate, not at the health check
&lt;/h2&gt;

&lt;p&gt;Reference design: the pipeline derives the list from a capability query, writes the specification onto the AMI, and &lt;code&gt;RunInstances&lt;/code&gt; becomes the single evaluation point for every account that received the image.&lt;/p&gt;

&lt;h3&gt;
  
  
  🏗️ Pipeline de imagem, build time
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;EC2 Image Builder pipeline do golden AMI (ci)&lt;/li&gt;
&lt;li&gt;DescribeInstanceTypes filtro por arquitetura + hypervisor (compute)&lt;/li&gt;
&lt;li&gt;ReplaceImageInstanceTypeSpecification lista explícita, gerada (ci)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🟧 AWS, plano de controle do EC2
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;AMI InstanceTypeSpecification (storage)&lt;/li&gt;
&lt;li&gt;CopyImage / share especificação preservada (storage)&lt;/li&gt;
&lt;li&gt;RunInstances avalia tipo × contrato (security)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🚀 Consumidores, launch time
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Auto Scaling group ABIS + Spot diversificado (compute)&lt;/li&gt;
&lt;li&gt;Launch template versão do AMI fixada (compute)&lt;/li&gt;
&lt;li&gt;Instância lançada tipo dentro do contrato (compute)&lt;/li&gt;
&lt;li&gt;InvalidParameterCombination lançamento bloqueado (external)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🔍 Sinais, o que você tem que instrumentar
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;CloudTrail errorCode em RunInstances (data)&lt;/li&gt;
&lt;li&gt;Scaling activity falha registrada no ASG (messaging)&lt;/li&gt;
&lt;li&gt;CloudWatch metric filter por imageId (data)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Flows
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;plat -&amp;gt; builder: publishes the golden AMI&lt;/li&gt;
&lt;li&gt;builder -&amp;gt; capq: asks what the hardware does&lt;/li&gt;
&lt;li&gt;capq -&amp;gt; setspec: compatible types, enumerated&lt;/li&gt;
&lt;li&gt;setspec -&amp;gt; ami: writes both lists&lt;/li&gt;
&lt;li&gt;ami -&amp;gt; copy: cross-region / cross-account&lt;/li&gt;
&lt;li&gt;ami -&amp;gt; gate: contract read at launch&lt;/li&gt;
&lt;li&gt;asg -&amp;gt; lt: scales out&lt;/li&gt;
&lt;li&gt;lt -&amp;gt; gate: RunInstances&lt;/li&gt;
&lt;li&gt;gate -&amp;gt; ok: type allowed&lt;/li&gt;
&lt;li&gt;gate -&amp;gt; blocked: type off the list&lt;/li&gt;
&lt;li&gt;blocked -&amp;gt; ct: auditable error&lt;/li&gt;
&lt;li&gt;blocked -&amp;gt; sa: capacity not delivered&lt;/li&gt;
&lt;li&gt;ct -&amp;gt; alarm: metric filter&lt;/li&gt;
&lt;li&gt;sa -&amp;gt; alarm&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Anatomy: two lists, one evaluation order, zero state
&lt;/h2&gt;

&lt;p&gt;The attribute is &lt;code&gt;InstanceTypeSpecification&lt;/code&gt; and it carries &lt;code&gt;SupportedInstanceTypes&lt;/code&gt; and &lt;code&gt;UnsupportedInstanceTypes&lt;/code&gt;. The logic is short enough to fit in an ADR: with no specification, everything passes (that's the default, and that's why nothing broke on the 4th); with only the unsupported list, everything passes except what matches; with the supported list set, the type must match the supported list &lt;strong&gt;and&lt;/strong&gt; must not match the unsupported one. Denial always wins.&lt;/p&gt;

&lt;p&gt;You write it with &lt;code&gt;aws ec2 replace-image-instance-type-specification --image-id ami-... --instance-type-specification '{"SupportedInstanceTypes": ["t3.*","a2.*"], "UnsupportedInstanceTypes": ["t3.micro"]}'&lt;/code&gt;. Omitting the parameter removes the restriction. You read it back through &lt;code&gt;describe-images&lt;/code&gt;, which returns the &lt;code&gt;InstanceTypeSpecification&lt;/code&gt; field with the patterns as stored. A blocked launch returns &lt;code&gt;InvalidParameterCombination&lt;/code&gt; from &lt;code&gt;RunInstances&lt;/code&gt;, with a message pointing at &lt;code&gt;DescribeImages&lt;/code&gt; as the place to look.&lt;/p&gt;

&lt;p&gt;Four properties change the design, and none of them are in the announcement:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;Replace&lt;/code&gt;, not &lt;code&gt;Modify&lt;/code&gt;:&lt;/strong&gt; the operation swaps the whole specification. There is no add or remove, it's read-modify-write with no conditional version. Two pipelines patching the same image in parallel produce last-writer-wins &lt;strong&gt;silently&lt;/strong&gt;, and the loser only finds out when a legitimate launch gets blocked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Only the owner writes.&lt;/strong&gt; An account that received the shared AMI consumes the contract and does not negotiate it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;CopyImage&lt;/code&gt; preserves the specification.&lt;/strong&gt; That's what makes the pattern worth it in a multi-Region topology: you declare once and the &lt;code&gt;sa-east-1&lt;/code&gt; replica is born with the contract.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Marketplace is mutually exclusive.&lt;/strong&gt; You can't set a specification on an AMI listed in AWS Marketplace, and you can't list an AMI that already has one. If you're an ISV shipping an appliance, this feature does not exist for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three ways to constrain instance type, and what each one actually covers
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Criterion&lt;/th&gt;
&lt;th&gt;IAM / SCP &lt;code&gt;ec2:InstanceType&lt;/code&gt; condition&lt;/th&gt;
&lt;th&gt;
&lt;code&gt;ExcludedInstanceTypes&lt;/code&gt; in ABIS&lt;/th&gt;
&lt;th&gt;
&lt;code&gt;InstanceTypeSpecification&lt;/code&gt; on the AMI&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Where the rule lives&lt;/td&gt;
&lt;td&gt;In the principal's policy, per account or OU&lt;/td&gt;
&lt;td&gt;In the fleet or ASG configuration&lt;/td&gt;
&lt;td&gt;On the artifact, once, in the owning account&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Applies to launches from another account&lt;/td&gt;
&lt;td&gt;No, that policy is theirs, not yours&lt;/td&gt;
&lt;td&gt;No, the consumer configures the fleet&lt;/td&gt;
&lt;td&gt;Yes: EC2 evaluates it regardless of caller&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Survives CopyImage and sharing&lt;/td&gt;
&lt;td&gt;Irrelevant, it doesn't follow the image&lt;/td&gt;
&lt;td&gt;Irrelevant, it doesn't follow the image&lt;/td&gt;
&lt;td&gt;Yes, preserved on copy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;When the failure shows up&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;UnauthorizedOperation&lt;/code&gt; at launch&lt;/td&gt;
&lt;td&gt;Never, the type simply isn't chosen&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;InvalidParameterCombination&lt;/code&gt; at launch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maintenance cost&lt;/td&gt;
&lt;td&gt;High: replicate and audit across N accounts&lt;/td&gt;
&lt;td&gt;Medium: one edit per fleet that exists&lt;/td&gt;
&lt;td&gt;Low per place, high per hardware generation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Where the glob betrays you: a string is not an architecture predicate
&lt;/h2&gt;

&lt;p&gt;Both lists accept &lt;code&gt;*&lt;/code&gt;. The docs give the examples: &lt;code&gt;t3.*&lt;/code&gt; matches the whole t3 family, &lt;code&gt;g5.*&lt;/code&gt; matches g5, &lt;code&gt;*.12xlarge&lt;/code&gt; matches any 12xlarge in any family, &lt;code&gt;*xlarge&lt;/code&gt; matches any type xlarge or larger. It looks expressive. This is where I'd stop and ask the question that matters: &lt;strong&gt;are you describing hardware, or are you describing names?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What the AMI actually requires is a capability: &lt;code&gt;arm64&lt;/code&gt; versus &lt;code&gt;x86_64&lt;/code&gt;, Nitro versus Xen hypervisor, presence of a GPU, presence of local NVMe. What the specification evaluates is a pattern over the instance type's string. The two coincide &lt;em&gt;today&lt;/em&gt; because AWS names Graviton families with a &lt;code&gt;g&lt;/code&gt; and local-storage families with a &lt;code&gt;d&lt;/code&gt;. They coincide by convention, and a naming convention is not a contract: &lt;code&gt;c6gn&lt;/code&gt;, &lt;code&gt;c7gd&lt;/code&gt;, &lt;code&gt;m7i-flex&lt;/code&gt;, &lt;code&gt;u7in-32tb&lt;/code&gt; and &lt;code&gt;mac2-m2pro&lt;/code&gt; already show a scheme full of exceptions, suffixes and infixes that no simple glob covers. A &lt;code&gt;*g.*&lt;/code&gt; written to exclude Graviton is a heuristic, and a hand-written heuristic, in a file nobody revisits, is debt that accrues interest.&lt;/p&gt;

&lt;p&gt;The second trap is temporal, and it's the expensive one. &lt;strong&gt;An explicit allow-list blocks every future generation by construction.&lt;/strong&gt; New families reach new Regions constantly; the day the next generation shows up in your Spot pool, the fleet you deliberately left wide, because wide diversification is the correct practice for Spot capacity, will skip exactly the cheapest, most available family. You get no error: the ASG simply doesn't choose it, price creeps up, interruption rate creeps up, and nobody connects that to a JSON written eight months ago. It's the same tension behind &lt;code&gt;OnDemandMaxPricePercentageOverLowestPrice&lt;/code&gt;, which ships with a 20% default headroom precisely so it doesn't narrow your pool without telling you.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Generate the list, don't write the list:&lt;/strong&gt; The only way to keep the contract honest is to derive it from a capability query in the same build that produced the image: &lt;code&gt;aws ec2 describe-instance-types --filters Name=processor-info.supported-architectures,Values=x86_64 Name=hypervisor,Values=nitro --query 'InstanceTypes[].InstanceType'&lt;/code&gt;, feeding the result straight into &lt;code&gt;replace-image-instance-type-specification&lt;/code&gt;. For fleets that already think in attributes, &lt;code&gt;GetInstanceTypesFromInstanceRequirements&lt;/code&gt; plays the same role and lets you preview the set before writing it. The list ends up explicit, verbose and ugly, which is exactly what you want, because it is &lt;strong&gt;regenerated on every build&lt;/strong&gt;, and your lag behind the AWS catalog becomes your pipeline cadence instead of the memory of whoever wrote the glob.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The hard block on incident day, and the signal nobody instruments
&lt;/h2&gt;

&lt;p&gt;The documentation is explicit: EC2 enforces the specification as a hard block, and launch templates and Auto Scaling groups that reference the AMI can fail if the configured instance type isn't allowed. That isn't an implementation detail, it's the pattern's central trade. You converted a slow, expensive failure (zombie instance, health check, loop) into a fast, cheap one (&lt;code&gt;InvalidParameterCombination&lt;/code&gt; at &lt;code&gt;RunInstances&lt;/code&gt;). Fast and cheap wins on 95% of days. On the other 5%, you're in the middle of a zonal capacity event, &lt;code&gt;price-capacity-optimized&lt;/code&gt; has finally found a pool with room, and your contract says no.&lt;/p&gt;

&lt;p&gt;In financial systems that has a name: you traded correctness risk for availability risk, and you did it on purpose. When the AMI genuinely won't boot on that hardware, the trade is trivially good, the instance was never going to serve traffic. When the list is merely stale, you invented an unavailability mode that didn't exist before, in exactly the window where elasticity matters. That's why a &lt;strong&gt;generated allow-list&lt;/strong&gt; and a &lt;strong&gt;minimal deny-list&lt;/strong&gt; aren't aesthetic preferences: one errs toward letting through, the other errs toward blocking.&lt;/p&gt;

&lt;p&gt;The hole is observability. The specification doesn't affect running instances, emits no metric of its own, and shows up on no dashboard by default. The auditable signal is the &lt;code&gt;RunInstances&lt;/code&gt; event in CloudTrail carrying an &lt;code&gt;errorCode&lt;/code&gt;, build a metric filter grouped by &lt;code&gt;requestParameters.imageId&lt;/code&gt; and alarm on any block, because in steady state the expected rate is &lt;strong&gt;zero&lt;/strong&gt;. A recurring block isn't the control working; it's a launch template pointing at an AMI whose contract changed underneath it. And add the read side to the runbook: &lt;code&gt;describe-images&lt;/code&gt; with its &lt;code&gt;InstanceTypeSpecification&lt;/code&gt; field is the first thing to check when a launch fails on a parameter, the error message itself sends you there, and whoever is on call at 2 AM needs to know that before opening a support case.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to get this wrong
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Applying the contract to an already-shared AMI without an inventory of consumers&lt;/strong&gt;: you can't see the launch templates in the other 40 accounts, and the block only surfaces on their next scale-out, which is probably a traffic spike, not a deploy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hand-writing the list&lt;/strong&gt;: a human-curated glob is a snapshot of the instance catalog on the day it was written, and the catalog changes every week.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Two pipelines writing the same AMI's specification&lt;/strong&gt;: &lt;code&gt;ReplaceImageInstanceTypeSpecification&lt;/code&gt; replaces everything and has no conditional write; the second one erases the first with no error.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Using this as a cost control&lt;/strong&gt;: blocking &lt;code&gt;*.24xlarge&lt;/code&gt; to contain spend is FinOps policy disguised as compatibility, that belongs in an SCP with &lt;code&gt;ec2:InstanceType&lt;/code&gt; or in &lt;code&gt;OnDemandMaxPricePercentageOverLowestPrice&lt;/code&gt;, where exceptions have an owner and a process.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mistaking this for pipeline validation&lt;/strong&gt;: the contract fails at launch, not at build. It does not replace a test that boots the image on each allowed family before promoting the AMI to production.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Reading the pattern through the pillars
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;operational-excellence&lt;/strong&gt;: The contract becomes generated code in the same pipeline as the AMI, versioned with it. Add &lt;code&gt;describe-images&lt;/code&gt; to the runbook: the error message points there, and it's the first diagnostic step.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;security&lt;/strong&gt;: Constraining the list is neither isolation nor least privilege, only the owner writes it and the rule follows the image, but it does not replace an IAM condition or an SCP for governing who launches what.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;reliability&lt;/strong&gt;: A real gain by killing the launch/terminate loop from incompatible AMIs; a real risk of failing to scale during a capacity event if the list is stale. An alarm with an expected rate of zero is what separates the two.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;cost&lt;/strong&gt;: The feature costs nothing and cuts the waste of instances that booted only to die at the health check, but a narrow allow-list pushes the fleet into pricier pools while emitting no signal at all.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What I'd actually do:&lt;/strong&gt; I'd turn this on first for the AMIs that carry a genuine physical limit, an &lt;code&gt;arm64&lt;/code&gt; image, an image whose driver only exists on Nitro, an inference image that assumes a GPU, and leave general-purpose AMIs alone, where the permissive default is already right. On every image I restrict, I'd generate the list from &lt;code&gt;describe-instance-types&lt;/code&gt; inside the build and fail the pipeline if it comes back empty, because an empty list from a broken query blocks everything silently. And I'd put a zero-expected-rate alarm on the &lt;code&gt;RunInstances&lt;/code&gt; &lt;code&gt;errorCode&lt;/code&gt; in CloudTrail the same day, not next sprint. The lesson that cost me, on financial-grade platforms: any control that blocks something and doesn't count how often it blocked eventually becomes a root cause nobody can name. The block is the easy part; the counter is what saves you at 2 AM.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/about-aws/whats-new/2026/09/ec2-images-supported-instances/" rel="noopener noreferrer"&gt;AWS What's New: Amazon EC2 now supports specifying compatible instance types on AMIs (Sep 4, 2026)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ami-allowed-instance-types.html" rel="noopener noreferrer"&gt;Amazon EC2 User Guide: AMI allowed instance types (evaluation logic, wildcards, considerations)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_ReplaceImageInstanceTypeSpecification.html" rel="noopener noreferrer"&gt;Amazon EC2 API Reference: ReplaceImageInstanceTypeSpecification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/cli/latest/reference/ec2/replace-image-instance-type-specification.html" rel="noopener noreferrer"&gt;AWS CLI Reference, ec2 replace-image-instance-type-specification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/autoscaling/ec2/userguide/create-mixed-instances-group-attribute-based-instance-type-selection.html" rel="noopener noreferrer"&gt;EC2 Auto Scaling, attribute-based instance type selection, price and performance protection&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-fleet-attribute-based-instance-type-selection.html" rel="noopener noreferrer"&gt;Amazon EC2 User Guide, attribute-based instance type selection for EC2 Fleet and Spot Fleet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_GetInstanceTypesFromInstanceRequirements.html" rel="noopener noreferrer"&gt;Amazon EC2 API Reference: GetInstanceTypesFromInstanceRequirements&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Verdict
&lt;/h2&gt;

&lt;p&gt;Adopt it, narrowly scoped, with the list generated. Use &lt;code&gt;InstanceTypeSpecification&lt;/code&gt; when three conditions hold together: the AMI has a real hardware incompatibility rather than a cost preference, you don't control every account that launches the image, and the pipeline that builds the AMI can derive the list from a capability query on every build. Missing any one of them, an &lt;code&gt;ec2:InstanceType&lt;/code&gt; condition in an SCP remains the right tool, it errs toward people who know how to ask for an exception. Don't use this as a FinOps control, don't hand-write globs, and don't enable it on a shared AMI before running &lt;code&gt;describe-images&lt;/code&gt; against the launch templates already pointing at it. The feature costs nothing and the permissive default guarantees nothing breaks until you act; the price is entirely maintenance, and it comes due every time AWS ships a new family. If your team has nowhere to hang that maintenance, the pattern is still correct and the answer is still no.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rating:&lt;/strong&gt; Adopt with scope&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://fernando.moretes.com/blog/ami-com-contrato-anatomia-do-novo-bloqueio-de-tipos-no-ec2-amazon-ec2-n" rel="noopener noreferrer"&gt;fernando.moretes.com&lt;/a&gt;. By Fernando F. Azevedo: Senior Solutions Architect.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>awscloud</category>
      <category>ec2</category>
      <category>ami</category>
      <category>goldenimage</category>
    </item>
    <item>
      <title>3LO in Bedrock Managed KB: 5-minute setup, 1 irreversible decision</title>
      <dc:creator>Fernando Azevedo</dc:creator>
      <pubDate>Thu, 08 Oct 2026 18:00:20 +0000</pubDate>
      <link>https://dev.to/fernando_azevedo_6844e930/3lo-in-bedrock-managed-kb-5-minute-setup-1-irreversible-decision-4nae</link>
      <guid>https://dev.to/fernando_azevedo_6844e930/3lo-in-bedrock-managed-kb-5-minute-setup-1-irreversible-decision-4nae</guid>
      <description>&lt;p&gt;The question that matters in this announcement isn't "how long does it take to connect SharePoint?", it's "who can read what you indexed once it's connected?". The user-managed setup (3LO) AWS shipped on September 4, 2026 for SharePoint, OneDrive and Confluence in Amazon Bedrock Managed Knowledge Base answers the first question very well: you click &lt;strong&gt;Sign in&lt;/strong&gt;, authorize with your corporate account, and Bedrock creates the Secrets Manager secret with the refresh token for you. The second question is where I spent three weeks.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the five-minute path actually trades away
&lt;/h2&gt;

&lt;p&gt;The SharePoint setup page is explicit and worth reading twice: &lt;em&gt;user-managed setup does not support document-level access control (ACL)&lt;/em&gt;. All indexed content is accessible to anyone who can query the knowledge base, and individual SharePoint permissions are not enforced. The same sentence shows up on the Confluence page, word for word.&lt;/p&gt;

&lt;p&gt;This isn't a bug or a temporary gap, it follows directly from the flow. In 3LO, Bedrock stores a &lt;strong&gt;delegated refresh token&lt;/strong&gt;, issued to you, carrying four permissions: &lt;code&gt;Sites.Read.All&lt;/code&gt; and &lt;code&gt;User.Read&lt;/code&gt; on Microsoft Graph, &lt;code&gt;offline_access&lt;/code&gt; to renew the token, and &lt;code&gt;AllSites.Read&lt;/code&gt; on the SharePoint Online API. These are &lt;em&gt;delegated&lt;/em&gt; permissions, inherited from what your account can see. The crawler walks into the tenant wearing your identity and copies everything it can reach into an index that has no notion of who is who.&lt;/p&gt;

&lt;p&gt;The path that does support ACL is a different one: &lt;code&gt;ENTRA_ID_APP_ONLY&lt;/code&gt; for SharePoint, &lt;code&gt;ENTRA_APP_ID&lt;/code&gt; for OneDrive, &lt;code&gt;SERVICE_ACCOUNT&lt;/code&gt; for Google Drive, &lt;code&gt;BASIC&lt;/code&gt; for Confluence. Those are &lt;strong&gt;application&lt;/strong&gt; permissions, with admin consent, and for SharePoint with ACLs they require &lt;code&gt;Sites.FullControl.All&lt;/code&gt;, or &lt;code&gt;Sites.Selected&lt;/code&gt; with a per-site &lt;code&gt;fullcontrol&lt;/code&gt; grant, because &lt;code&gt;Sites.Read.All&lt;/code&gt; is not enough to read item-level permissions.&lt;/p&gt;

&lt;p&gt;The trade, stated plainly: 3LO swaps a two-week ticket with the identity team for an index with no internal border. For a prototype over a public engineering wiki, that's a good deal. For the credit policy repository, it's an incident waiting for a date.&lt;/p&gt;

&lt;h2&gt;
  
  
  The journey: from a 3LO prototype to a production knowledge base
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Week 0, the prototype is born in the console, asking nobody&lt;/strong&gt;: An analyst holding &lt;code&gt;bedrock:GetDataSourceAuthorizationUrl&lt;/code&gt;, &lt;code&gt;bedrock:CreateDataSourceToken&lt;/code&gt; and &lt;code&gt;secretsmanager:CreateSecret&lt;/code&gt; completes the flow alone. Bedrock creates the secret at &lt;code&gt;arn:aws:secretsmanager:{region}:{account}:secret:bedrock-managedkb-oauth/{prefix}/{connector}/{uuid}&lt;/code&gt; and the data source comes up with &lt;code&gt;authType=MANAGED_OAUTH2&lt;/code&gt;. No ticket, no tenant ID, no review.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Week 1, the gate nobody read&lt;/strong&gt;: The Entra App-Only page states what locks the whole journey: the decision to enable ACLs &lt;em&gt;cannot be changed after the data source is created&lt;/em&gt;. And 3LO doesn't offer the option at all. Meaning: there is no in-place upgrade from &lt;code&gt;MANAGED_OAUTH2&lt;/code&gt; to ACL, there is a new data source and a full re-ingestion.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Week 2, inventory of what was already copied&lt;/strong&gt;: Before building the new path, answer what the old one already indexed. The crawl followed the identity of whoever signed in; if that person is senior, the reach is theirs. List the included sites and spaces, cross them against your data classification, and treat the current index as a copy repository, because that's what it is.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Week 3, app registration and certificate&lt;/strong&gt;: Here the ticket 3LO had eliminated comes back. A Global Administrator or Privileged Role Administrator registers the single-tenant application with no redirect URI, adds the &lt;strong&gt;application&lt;/strong&gt; permissions and grants admin consent. You generate a pair with &lt;code&gt;openssl req -x509 -newkey rsa:2048&lt;/code&gt;, upload the &lt;code&gt;.cer&lt;/code&gt; to Entra and the &lt;code&gt;.p12&lt;/code&gt; bundle to S3 with SSE. The &lt;code&gt;.p12&lt;/code&gt; is mandatory when ACLs are on.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Week 4, secret, service role and the new data source&lt;/strong&gt;: The secret carries &lt;code&gt;clientId&lt;/code&gt; and &lt;code&gt;certificatePassword&lt;/code&gt;, always set the password explicitly; if you omit it, Bedrock opens the bundle using the client ID as the password. The service role gets &lt;code&gt;s3:GetObject&lt;/code&gt; on the certificate object with an &lt;code&gt;aws:ResourceAccount&lt;/code&gt; condition, plus &lt;code&gt;kms:Decrypt&lt;/code&gt; if you used a CMK. The data source is created with &lt;code&gt;certificateS3Path&lt;/code&gt;, which &lt;code&gt;ENTRA_ID_APP_ONLY&lt;/code&gt; requires even with ACLs off.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Week 5, the application starts proving who is asking&lt;/strong&gt;: &lt;code&gt;Retrieve&lt;/code&gt; gains &lt;code&gt;userContext.userId&lt;/code&gt; with the user's universal email. Without it, ACL-enabled data sources return zero results, excellent as a fail-safe, terrible as a production surprise. Authentication stays yours: Bedrock filters by the identity you assert, it does not verify that the assertion is true.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Week 6, delete the old path, including the secret&lt;/strong&gt;: Delete the &lt;code&gt;MANAGED_OAUTH2&lt;/code&gt; data source and the orphaned &lt;code&gt;bedrock-managedkb-oauth/*&lt;/code&gt; secret, it still holds a valid refresh token. On the Microsoft side, revoke by disabling or deleting the service principal under &lt;strong&gt;Enterprise applications&lt;/strong&gt;; on Atlassian the grant is per site, so every &lt;code&gt;*.atlassian.net&lt;/code&gt; needs its own revocation.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The two phases and the immutable gate between them
&lt;/h2&gt;

&lt;p&gt;3LO is fast because it ends in a delegated refresh token; production is slow because it starts from a certificate and admin consent. The gate in the middle isn't a step, it's a decision you can only make once per data source.&lt;/p&gt;

&lt;h3&gt;
  
  
  🧪 Fase 1: Protótipo (3LO, minutos)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Console Bedrock KB GetDataSourceAuthorizationUrl (frontend)&lt;/li&gt;
&lt;li&gt;Secrets Manager bedrock-managedkb-oauth/.../uuid (security)&lt;/li&gt;
&lt;li&gt;Data source authType=MANAGED_OAUTH2 (data)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🔐 Entra ID / Atlassian, consentimento
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Consentimento delegado Sites.Read.All + offline_access (external)&lt;/li&gt;
&lt;li&gt;Admin consent do tenant obrigatório se o tenant restringe apps (external)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🚧 O portão imutável
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;aclEnabled não muda após CreateDataSource (security)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🟧 AWS: Fase 2: Produção (2LO + ACL)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Entra app registration application perms + Sites.FullControl.All (security)&lt;/li&gt;
&lt;li&gt;Amazon S3 certificate.p12 (SSE) (storage)&lt;/li&gt;
&lt;li&gt;Data source ENTRA_ID_APP_ONLY + ACL (data)&lt;/li&gt;
&lt;li&gt;Managed KB conteúdo + permissões crawladas (ai)&lt;/li&gt;
&lt;li&gt;Retrieve userContext.userId = e-mail (ai)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  👤 Sua aplicação, autenticação verificada
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;App corporativa OIDC valida o usuário final (compute)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Flows
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;analista -&amp;gt; signin: 1. Sign in&lt;/li&gt;
&lt;li&gt;signin -&amp;gt; consent: 2. OAuth popup&lt;/li&gt;
&lt;li&gt;consent -&amp;gt; adminconsent: blocked without consent&lt;/li&gt;
&lt;li&gt;signin -&amp;gt; secret3lo: 3. stores refresh token&lt;/li&gt;
&lt;li&gt;secret3lo -&amp;gt; ds3lo: 4. generated secretArn&lt;/li&gt;
&lt;li&gt;ds3lo -&amp;gt; gate: 5. no ACL, permanently&lt;/li&gt;
&lt;li&gt;gate -&amp;gt; dsprod: 6. new data source + re-ingestion&lt;/li&gt;
&lt;li&gt;entraapp -&amp;gt; certs3: .p12 (private key)&lt;/li&gt;
&lt;li&gt;certs3 -&amp;gt; dsprod: certificateS3Path&lt;/li&gt;
&lt;li&gt;dsprod -&amp;gt; kb: content and ACL crawl&lt;/li&gt;
&lt;li&gt;app -&amp;gt; retrieve: already-verified identity&lt;/li&gt;
&lt;li&gt;retrieve -&amp;gt; kb: pre-retrieval filter&lt;/li&gt;
&lt;li&gt;kb -&amp;gt; retrieve: real-time check at the source&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Before and after, in numbers the docs confirm
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;4&lt;/strong&gt;: delegated permissions granted on the 3LO path. &lt;code&gt;Sites.Read.All&lt;/code&gt;, &lt;code&gt;User.Read&lt;/code&gt;, &lt;code&gt;offline_access&lt;/code&gt; and &lt;code&gt;AllSites.Read&lt;/code&gt;, read access to everything the signing account can see, renewable indefinitely.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;0&lt;/strong&gt;: documents filtered by permission under 3LO. No ACL means no &lt;code&gt;userContext&lt;/code&gt;: whoever queries the knowledge base reads the entire index, regardless of their SharePoint access.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;365 dias&lt;/strong&gt;: default certificate validity on the production path. An expired certificate makes every sync fail. Rotation becomes a runbook item with an owner and a date, not a memory.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Identity: email is the key, and there is no translation
&lt;/h2&gt;

&lt;p&gt;Getting to the ACL path solves less than it looks, and it solves it in a very specific way. Bedrock Managed Knowledge Base uses &lt;strong&gt;email as the universal identifier&lt;/strong&gt;: there is no alias resolution and no cross-identity-provider mapping. The &lt;code&gt;userId&lt;/code&gt; you pass to &lt;code&gt;Retrieve&lt;/code&gt; must match exactly the email associated with that user in each connected data source. If your corporate IdP issues &lt;code&gt;fernando.azevedo@company.com&lt;/code&gt; and Confluence stores &lt;code&gt;fazevedo@company.com&lt;/code&gt;, the match fails silently and the user simply gets no results from that source. Silence is the worst failure mode a search system can have: nobody files a ticket over an incomplete answer, they file over a wrong one.&lt;/p&gt;

&lt;p&gt;Groups run on a different ruler. They're identified however the source represents them, name, ID, whatever, and user→group membership comes from the crawl. The docs are blunt: &lt;strong&gt;group memberships are as fresh as the last sync&lt;/strong&gt;. If a permission changed between syncs, the pre-retrieval filter doesn't know. The saving grace is the second layer: for SharePoint, OneDrive, Google Drive and Confluence there's real-time verification against the source, which catches exactly that window. S3 and Custom have no such net, because their ACLs come from a metadata file you supply.&lt;/p&gt;

&lt;p&gt;Two notes that change your application design: third-party IdP credentials are cached for up to &lt;strong&gt;1 hour&lt;/strong&gt;, and permission changes are eventually consistent, typically within a few minutes. So "I revoked access now" and "the RAG stopped showing it" are not the same instant. In a BACEN- or LGPD-bound environment, that gap belongs in the design document, not in the audit findings.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two paths, side by side
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Criterion&lt;/th&gt;
&lt;th&gt;Prototype: &lt;code&gt;MANAGED_OAUTH2&lt;/code&gt; (3LO)&lt;/th&gt;
&lt;th&gt;Production: &lt;code&gt;ENTRA_ID_APP_ONLY&lt;/code&gt; (2LO)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Who authorizes&lt;/td&gt;
&lt;td&gt;The user, in the popup, unless the tenant restricts third-party apps, in which case admin consent is required again&lt;/td&gt;
&lt;td&gt;Global Administrator or Privileged Role Administrator, with explicit admin consent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stored credential&lt;/td&gt;
&lt;td&gt;Delegated refresh token, in a secret Bedrock creates itself&lt;/td&gt;
&lt;td&gt;A &lt;code&gt;.p12&lt;/code&gt; certificate in S3 plus &lt;code&gt;clientId&lt;/code&gt;/&lt;code&gt;certificatePassword&lt;/code&gt; in a secret you create&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Document-level access control&lt;/td&gt;
&lt;td&gt;Not supported, and the flag can't change afterwards&lt;/td&gt;
&lt;td&gt;Supported, with pre-retrieval filtering and real-time verification&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Crawl reach&lt;/td&gt;
&lt;td&gt;Whatever the signing account can see, variable, invisible from the AWS console&lt;/td&gt;
&lt;td&gt;The whole tenant with &lt;code&gt;Sites.Read.All&lt;/code&gt;/&lt;code&gt;Sites.FullControl.All&lt;/code&gt;, or site by site with &lt;code&gt;Sites.Selected&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What breaks&lt;/td&gt;
&lt;td&gt;The offboarding of whoever authorized; a token revoked in Entra; content shared after the sync, which needs a resync&lt;/td&gt;
&lt;td&gt;An expired certificate (every sync fails); a forgotten per-site grant for a new site, which is not retroactive&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The failure modes that only show up later
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The token has an owner, and owners resign.&lt;/strong&gt; The 3LO refresh token belongs to a person. When they leave and the account is disabled, the sync stops, and the index stays right there, holding whatever content they could reach. The production path has no such coupling: the application authenticates with a certificate, with no human sign-in at crawl time or query time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fail-closed is good, but it looks like a bug.&lt;/strong&gt; ACL-aware retrieval fails closed: a group-resolution error, a real-time verification timeout or an internal error means the document is not returned. The response may carry fewer results than the requested &lt;code&gt;maxResults&lt;/code&gt;, and Bedrock does &lt;strong&gt;not&lt;/strong&gt; backfill. Your application must treat partial responses as normal, and tell "zero because of ACL" apart from "zero because of relevance", the API signals resolution failures precisely for that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A document without an ACL is an invisible document.&lt;/strong&gt; In an ACL-enabled data source, a document whose permissions weren't extracted goes to nobody. Absence is treated as restricted, not public. After the migration, expect a recall drop and investigate that before blaming the embedding model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The secret prefix is your only IAM lever, and the default is ambiguous.&lt;/strong&gt; The 3LO IAM page says that without a prefix Bedrock uses &lt;code&gt;default&lt;/code&gt;; the connector pages say the created secret uses the &lt;code&gt;bedrock-managedkb-oauth&lt;/code&gt; prefix. Don't build a policy on that assumption: supply an explicit &lt;strong&gt;secret name prefix&lt;/strong&gt; at sign-in and write the policy with &lt;code&gt;Resource&lt;/code&gt; scoped to it &lt;em&gt;before&lt;/em&gt; anyone runs the flow. That's the difference between &lt;code&gt;secretsmanager:CreateSecret&lt;/code&gt; on a predictable path and a &lt;code&gt;Resource: "*"&lt;/code&gt; nobody will review later.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The risk isn't 3LO, it's the 3LO nobody planned for:&lt;/strong&gt; Two Bedrock permissions (&lt;code&gt;GetDataSourceAuthorizationUrl&lt;/code&gt; and &lt;code&gt;CreateDataSourceToken&lt;/code&gt;) plus &lt;code&gt;secretsmanager:CreateSecret&lt;/code&gt; are enough for anyone with console access to index the corporate SharePoint into a knowledge base with no internal border, without touching a single identity process. If your production account already grants those actions by wildcard in some "Bedrock developer" policy, you don't have a new feature, you have a new path to accidentally authorized exfiltration. Deny &lt;code&gt;bedrock:CreateDataSourceToken&lt;/code&gt; outside sandbox accounts before you discuss RAG architecture.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Anti-patterns I've watched grow out of this flow
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Promoting the prototype by switching &lt;code&gt;authType&lt;/code&gt;&lt;/strong&gt;: it doesn't exist. The ACL flag is immutable per data source, so promoting means creating a new data source and re-ingesting, plan the re-ingestion cost and the duplicate-index window.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authorizing 3LO with the architect's account&lt;/strong&gt;: the more senior the authorizer, the wider the crawl. If you're going to use 3LO, use an account with deliberately narrow access to the in-scope sites.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treating ACL awareness as authorization&lt;/strong&gt;: the docs themselves say it's filtering, not a security boundary. Bedrock does not authenticate end users, it trusts the &lt;code&gt;userId&lt;/code&gt; your application asserts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unblocking user-installed apps in Atlassian "just for a minute"&lt;/strong&gt;: while that toggle is open, any user can authorize any OAuth app across the entire site. Have the admin authorize directly instead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Leaving the 3LO secret behind during cleanup&lt;/strong&gt;: deleting the data source doesn't delete the secret, and it holds a refresh token that still renews.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Well-Architected lens on the decision
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;security&lt;/strong&gt;: Deny &lt;code&gt;bedrock:CreateDataSourceToken&lt;/code&gt; in production accounts via SCP; require a secret name prefix and scope &lt;code&gt;Resource&lt;/code&gt; to it; prefer &lt;code&gt;Sites.Selected&lt;/code&gt; over &lt;code&gt;Sites.FullControl.All&lt;/code&gt; whenever the site list is known; store the &lt;code&gt;.p12&lt;/code&gt; with SSE-KMS and &lt;code&gt;kms:Decrypt&lt;/code&gt; restricted to the service role.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;reliability&lt;/strong&gt;: A certificate with the default 365-day validity takes every sync down when it expires, alarm 45 days ahead. Per-site grants are not retroactive: a new site without a grant simply isn't crawled, with no visible error on the data source.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Let the prototype exist, in an account where it can't grow up
&lt;/h2&gt;

&lt;p&gt;The obvious temptation after reading all this is to block 3LO and be done. I wouldn't. The friction it removes is real: without it, every RAG experiment over the internal wiki turns into a two-week ticket with the identity team, and the practical outcome is that nobody experiments, or worse, somebody exports the wiki into a bucket and builds RAG on the side, with no crawler, no sync and none of the checks we just discussed.&lt;/p&gt;

&lt;p&gt;The design I recommend is topological, not political. &lt;strong&gt;One sandbox account&lt;/strong&gt; where &lt;code&gt;bedrock:GetDataSourceAuthorizationUrl&lt;/code&gt; and &lt;code&gt;bedrock:CreateDataSourceToken&lt;/code&gt; are allowed, with no classified data and a short lifecycle, the whole index dies in N days by process, not by memory. &lt;strong&gt;Production accounts&lt;/strong&gt; with those two actions denied by SCP, which makes 3LO disappear from the menu without a case-by-case argument.&lt;/p&gt;

&lt;p&gt;What crosses that border isn't the data source; it's the learning. The prototype answers the genuinely expensive questions: which Confluence spaces hold content that resolves real doubts, which chunking strategy survives an 80-page policy document, whether agentic retrieval with &lt;code&gt;maxAgentIteration&lt;/code&gt; above the default of 5 changes the answer enough to justify the cost. None of that depends on ACLs. When the answer is "worth it", then you open the ticket with the Entra team knowing exactly which sites to request under &lt;code&gt;Sites.Selected&lt;/code&gt;, and the ticket is shorter because you arrive with a scope, not an intention.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What I'd do on Monday:&lt;/strong&gt; Before enabling anything, I'd grep the production account's policies for &lt;code&gt;bedrock:*&lt;/code&gt;, the odds that the two new actions are already granted by wildcard are high, and no IAM review done before September 2026 could have accounted for them. Then I'd create the sandbox account with the inverted SCP, because blocking production without offering the right place just pushes the problem outside my field of view. I learned that the expensive way on a financial platform: the architecture decision that looks like security is almost always about ergonomics, people route around inconvenient controls with a creativity no policy anticipates. And I'd keep the rule I apply to every new connector: if the flag can't be changed later, it isn't configuration, it's design, and design goes into an ADR, with the trade-off written and signed before &lt;code&gt;CreateDataSource&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Verdict
&lt;/h2&gt;

&lt;p&gt;Use user-managed setup (3LO) when all three conditions hold together: the content is already readable by the knowledge base's entire audience, the index lives in an account with no classified data, and it has an agreed expiration date. Outside that, go straight to &lt;code&gt;ENTRA_ID_APP_ONLY&lt;/code&gt; for SharePoint, &lt;code&gt;ENTRA_APP_ID&lt;/code&gt; for OneDrive or &lt;code&gt;BASIC&lt;/code&gt; for Confluence, not because 3LO is poorly built, but because the ACL flag can't change after &lt;code&gt;CreateDataSource&lt;/code&gt;, and today's five-minute shortcut costs a full re-ingestion plus an Entra-team ticket later. This announcement isn't about authentication; it's about where your organization's access boundary exists, and 3LO answers that it stops existing inside the index. That's a legitimate choice for a prototype and an audit finding for everything else.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rating:&lt;/strong&gt; Adote em sandbox, negue em produção&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/about-aws/whats-new/2026/09/amazon-bedrock-managed-knowledge-base-user-managed-setup-sharepoint-onedrive-confluence/" rel="noopener noreferrer"&gt;AWS What's New: Bedrock Managed Knowledge Base introduces user-managed setup for SharePoint, OneDrive, and Confluence (&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock/latest/userguide/kb-managed-sharepoint-3lo-setup.html" rel="noopener noreferrer"&gt;Amazon Bedrock User Guide: SharePoint user-managed setup (3LO)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock/latest/userguide/kb-managed-confluence-3lo-setup.html" rel="noopener noreferrer"&gt;Amazon Bedrock User Guide: Confluence user-managed setup (3LO)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock/latest/userguide/kb-managed-3lo-setup.html" rel="noopener noreferrer"&gt;Amazon Bedrock User Guide: IAM permissions for user-managed setup (3LO)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock/latest/userguide/kb-managed-acl.html" rel="noopener noreferrer"&gt;Amazon Bedrock User Guide: Access Control Lists awareness enablement&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock/latest/userguide/kb-test-retrieve-acl.html" rel="noopener noreferrer"&gt;Amazon Bedrock User Guide: ACL-aware retrieval on managed knowledge bases (userContext)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock/latest/userguide/kb-managed-sharepoint-entra-setup.html" rel="noopener noreferrer"&gt;Amazon Bedrock User Guide: Set up Microsoft Entra ID App-Only authentication for SharePoint&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/blogs/machine-learning/build-enterprise-search-for-agents-with-amazon-bedrock-managed-knowledge-base/" rel="noopener noreferrer"&gt;AWS ML Blog: Build enterprise search for agents with Amazon Bedrock Managed Knowledge Base&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://fernando.moretes.com/blog/3lo-no-bedrock-managed-kb-5-minutos-de-setup-1-decisao-irreversivel-amazon-bedro" rel="noopener noreferrer"&gt;fernando.moretes.com&lt;/a&gt;. By Fernando F. Azevedo: Senior Solutions Architect.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aiagents</category>
      <category>bedrock</category>
      <category>rag</category>
      <category>iam</category>
    </item>
    <item>
      <title>RAG on ServiceNow: the connector that erases user criteria</title>
      <dc:creator>Fernando Azevedo</dc:creator>
      <pubDate>Thu, 08 Oct 2026 17:59:45 +0000</pubDate>
      <link>https://dev.to/fernando_azevedo_6844e930/rag-on-servicenow-the-connector-that-erases-user-criteria-2bnb</link>
      <guid>https://dev.to/fernando_azevedo_6844e930/rag-on-servicenow-the-connector-that-erases-user-criteria-2bnb</guid>
      <description>&lt;p&gt;The documentation for Amazon Bedrock Managed Knowledge Base's ServiceNow connector carries two sentences that look like implementation detail when read apart. The first tells you to grant &lt;code&gt;knowledge_admin&lt;/code&gt; to the service account, and explains why: the role bypasses per-knowledge-base user criteria restrictions. The second warns that ServiceNow data sources don't support document-level ACLs, every authenticated user who can query the knowledge base sees every crawled document. Together they describe an enterprise assistant that answers anything for anyone. This is the retro of the pilot where I found that out the cheap way: in staging, four hours after the first sync.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happened
&lt;/h2&gt;

&lt;h2&gt;
  
  
  What happened
&lt;/h2&gt;

&lt;p&gt;I stood the connector up the day it shipped, September 4, 2026, against a staging instance loaded with a real export: roughly 12,000 knowledge articles spread across six separate knowledge bases, and 40,000 active catalog items. The goal was to measure sync time and retrieval quality for an internal IT assistant, the exact use case AWS names in the announcement.&lt;/p&gt;

&lt;p&gt;I followed the documentation line by line. The &lt;code&gt;glide.oauth.inbound.client.credential.grant_type.enabled&lt;/code&gt; property set to &lt;code&gt;true&lt;/code&gt;. A service account flagged &lt;em&gt;Web service access only&lt;/em&gt;, no interactive sign-in. The OAuth application registered through the interceptor page, not by inserting straight into &lt;code&gt;oauth_entity&lt;/code&gt;, as the guide itself warns. An Inbound Authentication Profile wired to a REST API Access Policy over &lt;code&gt;now/table&lt;/code&gt;, because without it the token is valid and the Table API still returns &lt;code&gt;401&lt;/code&gt;. Step 3 asks for exactly two roles: &lt;code&gt;knowledge_admin&lt;/code&gt; and &lt;code&gt;catalog_admin&lt;/code&gt;. I assigned both, and ServiceNow inherited around fourteen contained roles.&lt;/p&gt;

&lt;p&gt;The sync finished. Then I asked the question I ask of every enterprise RAG before anything ships: I queried the assistant about a subject I knew was restricted to one group on the source instance. The answer came back whole, citing the article, for an identity that held no role there at all. It wasn't a hallucination, it was correct retrieval of a document ServiceNow would never have rendered for that user.&lt;/p&gt;

&lt;p&gt;The pilot stopped there. What follows is the retro, blameless: the configuration was right per the documentation, and that is precisely the point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Timeline
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;T+0h, service account created with the roles from the guide&lt;/strong&gt;: &lt;code&gt;knowledge_admin&lt;/code&gt; and &lt;code&gt;catalog_admin&lt;/code&gt; assigned per step 3. The documentation note says, in plain letters, that these roles bypass per-KB and per-catalog user criteria restrictions. I read it and complied.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;T+0h20: OAuth verified with curl&lt;/strong&gt;: &lt;code&gt;POST /oauth_token.do&lt;/code&gt; with &lt;code&gt;grant_type=client_credentials&lt;/code&gt;, then &lt;code&gt;GET /api/now/table/kb_knowledge?sysparm_limit=1&lt;/code&gt;. HTTP 200 with data. AWS's verification table reads an empty array as a symptom of an insufficient role, meaning success here is already success &lt;em&gt;with bypass&lt;/em&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;T+0h35, data source created with no filter&lt;/strong&gt;: &lt;code&gt;crawlKnowledgeArticles&lt;/code&gt;, &lt;code&gt;crawlServiceCatalogs&lt;/code&gt; and both attachment fields set to &lt;code&gt;true&lt;/code&gt;; &lt;code&gt;filterConfiguration&lt;/code&gt; omitted. With no filter the connector crawls every published article and every active item the account can see, which is now everything.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;T+3h50, sync completes slower than planned&lt;/strong&gt;: The troubleshooting page explains it: without &lt;code&gt;inclusionServiceCatalogSysIds&lt;/code&gt;, the crawl sweeps the 100,000-plus active items of an enterprise instance. I had 40,000 and it still took hours.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;T+4h05, the control query returns restricted content&lt;/strong&gt;: A natural-language question, no &lt;code&gt;userContext&lt;/code&gt;, about an article restricted to one group. Chunk and citation came back normally. Nothing in the trace flags an anomaly, because nothing anomalous happened.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;T+4h15, data source deleted, retro opened&lt;/strong&gt;: Index destroyed in staging, with no end-user exposure. The real cost of the incident was one day of work and a few dollars of storage, the design, not so much.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Root cause: two controls removed at different points of the path:&lt;/strong&gt; ServiceNow's user criteria isn't ignored at query time, it is ignored at &lt;strong&gt;ingestion&lt;/strong&gt;, by a role the documentation instructs you to assign, and never rebuilt afterwards, because the ServiceNow connector isn't in Managed Knowledge Base's ACL matrix. SharePoint, OneDrive, Google Drive and Confluence support pre-retrieval filtering plus real-time verification; S3 and Custom support pre-retrieval filtering from metadata you supply; Web Crawler and ServiceNow support neither. The rule that seals the hole is unforgiving: non-ACL data sources in a mixed knowledge base return results to every user, with or without &lt;code&gt;userContext&lt;/code&gt; on the &lt;code&gt;Retrieve&lt;/code&gt; call. This is not a connector bug, it is its published contract, and designing around it is your job.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Where user criteria dies
&lt;/h2&gt;

&lt;p&gt;The path of a restricted article, from the &lt;code&gt;kb_knowledge&lt;/code&gt; table to the assistant's answer. The two red points in the drawing aren't failures: they are documented behaviors that, added together, remove the only access control the source ever had.&lt;/p&gt;

&lt;h3&gt;
  
  
  🏢 ServiceNow, origem do conteúdo
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;kb_knowledge ~12k artigos, user criteria por KB (external)&lt;/li&gt;
&lt;li&gt;sc_cat_item ~40k itens ativos + anexos (external)&lt;/li&gt;
&lt;li&gt;svc account (2LO) knowledge_admin + catalog_admin (security)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🟧 AWS: Ingestão
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Secrets Manager clientId / clientSecret / instanceUrl (security)&lt;/li&gt;
&lt;li&gt;ServiceNow connector v1 incremental por sys_updated_on (compute)&lt;/li&gt;
&lt;li&gt;Managed Knowledge Base chunks + embeddings, sem ACL (ai)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🟧 AWS: Recuperação
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Retrieve / AgenticRetrieveStream userContext sem efeito aqui (ai)&lt;/li&gt;
&lt;li&gt;Assistente de TI autentica no IdP corporativo (compute)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🔐 Fronteira que sobra depois do retro
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;KB 'publica' crawlPublicKnowledgeArticlesOnly = true (storage)&lt;/li&gt;
&lt;li&gt;KB 'restrita' sys IDs isolados + IAM por grupo (storage)&lt;/li&gt;
&lt;li&gt;IAM na chamada Retrieve Condition por tag de audiência (security)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Flows
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;svc -&amp;gt; kb: 1. knowledge_admin bypasses user criteria&lt;/li&gt;
&lt;li&gt;svc -&amp;gt; cat: 2. catalog_admin bypasses catalog restrictions&lt;/li&gt;
&lt;li&gt;sm -&amp;gt; conn: secret in the same Region as the KB&lt;/li&gt;
&lt;li&gt;conn -&amp;gt; svc: 2LO token + Table API /now/table&lt;/li&gt;
&lt;li&gt;conn -&amp;gt; mkb: 3. ingests text and metadata, permissions not&lt;/li&gt;
&lt;li&gt;mkb -&amp;gt; ret: hybrid search, top-k over everything&lt;/li&gt;
&lt;li&gt;emp -&amp;gt; app: natural-language question&lt;/li&gt;
&lt;li&gt;app -&amp;gt; ret: 4. query with no per-document boundary&lt;/li&gt;
&lt;li&gt;ret -&amp;gt; app: chunk + citation from any crawled article&lt;/li&gt;
&lt;li&gt;conn -&amp;gt; kbpub: fix: public articles only&lt;/li&gt;
&lt;li&gt;conn -&amp;gt; kbres: fix: single-audience sys IDs&lt;/li&gt;
&lt;li&gt;app -&amp;gt; iam: fix: role per audience, not per app&lt;/li&gt;
&lt;li&gt;iam -&amp;gt; kbres: bedrock:Retrieve conditioned by tag&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The fix: scope at ingestion, because there is none at query time
&lt;/h2&gt;

&lt;h2&gt;
  
  
  The fix: scope at ingestion, because there is none at query time
&lt;/h2&gt;

&lt;p&gt;When the access boundary doesn't exist at retrieval time, it has to exist earlier, in what enters the index. Four concrete changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One knowledge base per audience, not one per system.&lt;/strong&gt; &lt;code&gt;Retrieve&lt;/code&gt; takes a &lt;code&gt;knowledgeBaseId&lt;/code&gt;; that is the only unit IAM can see. I split into two bases: one fed with &lt;code&gt;crawlPublicKnowledgeArticlesOnly&lt;/code&gt; set to &lt;code&gt;true&lt;/code&gt;, another with &lt;code&gt;inclusionKnowledgeBaseSysIds&lt;/code&gt; listing only the sys IDs whose audience matches an IdP group. The 10,000-knowledge-bases-per-account-per-Region quota isn't the constraint here; the constraint is operational, which is why granularity stopped at audience rather than group.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Authorization moves back into the application.&lt;/strong&gt; The &lt;code&gt;bedrock:Retrieve&lt;/code&gt; permission is now granted through a distinct role per audience, with a &lt;code&gt;Condition&lt;/code&gt; on the knowledge base's resource tag. The service answering the user resolves the group from the IdP token and assumes the matching role. That is more code than I would like, and it is the price of using a source with no ACL.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;sys ID filtering stopped being an optimization.&lt;/strong&gt; &lt;code&gt;inclusionServiceCatalogSysIds&lt;/code&gt; and &lt;code&gt;inclusionKnowledgeArticleCategorySysIds&lt;/code&gt; are scope control and sync-time control at once: the troubleshooting page attributes hours-long syncs precisely to their absence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Protected deletion.&lt;/strong&gt; I enabled &lt;code&gt;deletionProtectionConfiguration&lt;/code&gt; with &lt;code&gt;deletionProtectionThreshold&lt;/code&gt; at 15. If a sync tries to remove more than 15% of the index, which a user criteria mistake at the source triggers easily, the delete phase is skipped instead of emptying the assistant in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  ACL support by connector, what it changes in the design
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Criterion&lt;/th&gt;
&lt;th&gt;Pre-retrieval ACL filter&lt;/th&gt;
&lt;th&gt;Real-time verification&lt;/th&gt;
&lt;th&gt;What it forces on you&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ServiceNow&lt;/td&gt;
&lt;td&gt;Not supported&lt;/td&gt;
&lt;td&gt;Not supported&lt;/td&gt;
&lt;td&gt;Segregate per knowledge base and authorize in IAM/the application; never mix audiences in one index.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SharePoint / OneDrive&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;Requires app-only auth (&lt;code&gt;ENTRA_ID_APP_ONLY&lt;/code&gt; / &lt;code&gt;ENTRA_APP_ID&lt;/code&gt;) and an email identical to the source's.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google Drive&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;Domain-wide delegation with &lt;code&gt;SERVICE_ACCOUNT&lt;/code&gt;; permission changes between syncs are caught at verification.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Confluence&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;Admin token for the real-time check, with &lt;code&gt;BASIC&lt;/code&gt; auth, rotation becomes a runbook item.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Amazon S3 / Custom&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;Not supported&lt;/td&gt;
&lt;td&gt;The ACL is your own file/metadata: permission freshness becomes your pipeline's responsibility.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Web Crawler&lt;/td&gt;
&lt;td&gt;Not supported&lt;/td&gt;
&lt;td&gt;Not applicable&lt;/td&gt;
&lt;td&gt;Public content only; anything else is a leak by construction.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Identity: the detail that decides whether the mitigation works
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Identity: the detail that decides whether the mitigation works
&lt;/h2&gt;

&lt;p&gt;Even on connectors that do carry ACLs, Managed Knowledge Base's identity model is narrower than most enterprise designs assume, worth knowing before you promise fine-grained segregation to the risk team.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The universal identifier is email.&lt;/strong&gt; &lt;code&gt;userContext.userId&lt;/code&gt; on &lt;code&gt;Retrieve&lt;/code&gt; is always the user's email, and it must match exactly the email associated with that user in each connected data source. There is no alias resolution and no cross-identity-provider mapping. If the company carries two domains from an acquisition, half the people get zero results, silently, which is the worst failure mode available.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Groups come from the last sync.&lt;/strong&gt; User-to-group membership is crawled at ingestion and resolved at query time from that snapshot. Real-time verification covers the window between syncs on the four connectors that support it; third-party IdP credentials are cached for up to one hour, and permission changes are eventually consistent, typically within minutes. For a termination, that is exposure time, treat revocation as an IdP and application event, not a knowledge base one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Missing ACL means inaccessible, not public.&lt;/strong&gt; A document with no extracted permissions in an ACL-enabled source is returned to nobody, and evaluation fails closed: a group-resolution error yields fewer results, never more. That is the correct behavior, and it is also the most common explanation for "the assistant got dumber after the deploy".&lt;/p&gt;

&lt;p&gt;And the sentence the documentation insists on repeating: ACL-aware is filtering, not authorization. Authentication is yours.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading it through the pillars
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;security&lt;/strong&gt;: The connector concentrates power in one service account holding &lt;code&gt;knowledge_admin&lt;/code&gt; and &lt;code&gt;catalog_admin&lt;/code&gt;, the documentation explicitly asks you &lt;strong&gt;not&lt;/strong&gt; to add &lt;code&gt;admin&lt;/code&gt;, &lt;code&gt;itil&lt;/code&gt; or &lt;code&gt;snc_read_only&lt;/code&gt;, and that restriction is the little least-privilege you get. The Secrets Manager secret holds &lt;code&gt;clientId&lt;/code&gt;, &lt;code&gt;clientSecret&lt;/code&gt; and &lt;code&gt;instanceUrl&lt;/code&gt; in the same Region as the knowledge base; rotating means regenerating the client secret in the Application Registry, which is shown only once. Treat &lt;code&gt;knowledgeBaseId&lt;/code&gt; as a security boundary and condition &lt;code&gt;bedrock:Retrieve&lt;/code&gt; by tag.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;reliability&lt;/strong&gt;: Incremental sync keys on &lt;code&gt;sys_updated_on&lt;/code&gt;, so an article restored from backup at the source may never return to the index if the timestamp doesn't move. &lt;code&gt;deletionProtectionThreshold&lt;/code&gt; (default 15%) keeps a bad sync from emptying the index. The ceilings that matter on a bad day: 200 data sources per knowledge base, 50 concurrent ingestion jobs, 10 TB of raw data per knowledge base, 600 RPM of &lt;code&gt;Retrieve&lt;/code&gt; with a 25 RPS burst, and 300 RPM of &lt;code&gt;AgenticRetrieveStream&lt;/code&gt; per account.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Anti-patterns this retro put in writing
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One knowledge base for the whole company:&lt;/strong&gt; mixing ServiceNow (no ACL) with SharePoint (ACL) in the same index lets the weakest source set the access level, non-ACL documents return to everyone, even with &lt;code&gt;userContext&lt;/code&gt; on the call.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treating &lt;code&gt;userContext&lt;/code&gt; as access control:&lt;/strong&gt; it filters by the identity you assert, verifying nothing. Without upstream authentication it is a query parameter, not a boundary.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Crawling with no &lt;code&gt;filterConfiguration&lt;/code&gt; on an enterprise instance:&lt;/strong&gt; it turns into an hours-long sync over 100,000-plus catalog items and fills the index with content nobody will ask about, at US$ 5.00 per GB per month to keep it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Registering the OAuth application by inserting straight into &lt;code&gt;oauth_entity&lt;/code&gt;:&lt;/strong&gt; the documentation requires the interceptor page; the shortcut yields a record that authenticates and then fails at the Table API, and you debug &lt;code&gt;401&lt;/code&gt; in the wrong place.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trusting source-side user criteria as an outbound control:&lt;/strong&gt; it is evaluated per user session inside ServiceNow. An integration account with &lt;code&gt;knowledge_admin&lt;/code&gt; doesn't violate it, it is simply never subjected to it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What now gets measured before any release
&lt;/h2&gt;

&lt;h2&gt;
  
  
  What now gets measured before any release
&lt;/h2&gt;

&lt;p&gt;The retro doesn't end at a configuration fix; it ends at a signal. A RAG leak throws no error, shows no latency anomaly and never lands on an availability dashboard, it shows up as a useful answer for the wrong person. Three things joined the release checklist.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A canary query suite.&lt;/strong&gt; A small set of questions whose correct answer is &lt;em&gt;silence&lt;/em&gt;, one per audience, run against each knowledge base after every sync. It is access regression testing, it runs in seconds and it costs US$ 1.00 per thousand calls. The alternative is learning about it from the ombudsman.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Document counts per sync, with an expected band.&lt;/strong&gt; The &lt;code&gt;statistics&lt;/code&gt; object from &lt;code&gt;GetIngestionJob&lt;/code&gt; reports how many documents were added, failed and removed. A sharp swing usually means a scope change at the source, a category moved, a catalog reactivated, before it means a platform problem. With &lt;code&gt;deletionProtectionThreshold&lt;/code&gt; enabled, the sync that would skip its delete phase is exactly the event you want alarming.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The origin of cited chunks.&lt;/strong&gt; Because the assistant returns citations, the &lt;code&gt;dataSourceId&lt;/code&gt; of each retrieved chunk is free telemetry. If a restricted-audience knowledge base shows up in an open-channel answer, the alarm fires without anyone having to read the answer.&lt;/p&gt;

&lt;p&gt;The connector saves weeks of hand-rolled pipeline, what it does not save is the design of who may see what. That part was never outsourceable.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Curator's note:&lt;/strong&gt; If I were putting this into production in a financial-grade environment tomorrow, I'd start with a single knowledge base fed by &lt;code&gt;crawlPublicKnowledgeArticlesOnly&lt;/code&gt; set to &lt;code&gt;true&lt;/code&gt; and nothing else, the set of articles already public to every employee, and only then open additional bases per audience, each with its own role and tag. The hard-won lesson this pilot reinforced: when a connector erases the source's permission model, the damage doesn't surface as a failure, it surfaces as quality. The assistant gets &lt;em&gt;better&lt;/em&gt; when it leaks, because it has more context, and that is why nobody files a ticket. I learned to read the ACL support matrix before the connector's feature list, and to treat the sentence "all authenticated users who can query the knowledge base can see all crawled content" as an architecture requirement, not a footnote.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/about-aws/whats-new/2026/09/amazon-bedrock-managed-knowledge-base-servicenow-native-data-source-connector/" rel="noopener noreferrer"&gt;AWS What's New: Amazon Bedrock Managed Knowledge Base now supports ServiceNow as a native data source connector (Sep 4,&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock/latest/userguide/kb-managed-ds-servicenow.html" rel="noopener noreferrer"&gt;Amazon Bedrock User Guide: ServiceNow data source (supported features, ACL warning)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock/latest/userguide/kb-managed-servicenow-oauth2-setup.html" rel="noopener noreferrer"&gt;Amazon Bedrock User Guide: Set up OAuth 2.0 Client Credentials authentication for ServiceNow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock/latest/userguide/kb-managed-ds-servicenow-connect.html" rel="noopener noreferrer"&gt;Amazon Bedrock User Guide: Connect a ServiceNow data source (connector parameters, sys ID filters)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock/latest/userguide/kb-managed-acl.html" rel="noopener noreferrer"&gt;Amazon Bedrock User Guide: Access Control Lists awareness enablement (connector support matrix)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock/latest/userguide/kb-managed-sync.html" rel="noopener noreferrer"&gt;Amazon Bedrock User Guide: Sync a data source and set a sync schedule&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock/latest/userguide/kb-managed-quotas.html" rel="noopener noreferrer"&gt;Amazon Bedrock User Guide: Service quotas for managed knowledge bases&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/blogs/machine-learning/build-enterprise-search-for-agents-with-amazon-bedrock-managed-knowledge-base/" rel="noopener noreferrer"&gt;AWS Machine Learning Blog: Build enterprise search for agents with Amazon Bedrock Managed Knowledge Base&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Verdict
&lt;/h2&gt;

&lt;p&gt;Adopt the connector when the ServiceNow content you want indexed is already visible to the assistant's entire audience, public IT knowledge bases, an open service catalog, unrestricted HR FAQs, and when the cost of maintaining your own Table API pipeline, pagination, attachments and &lt;code&gt;sys_updated_on&lt;/code&gt; incremental sync is real for your team. In that slice it is a good deal: weeks less code, native scheduling, and bulk-deletion protection by configuration. Don't adopt it as-is when the instance uses user criteria to separate audiences, when risk, legal or fraud runbook articles live in the same &lt;code&gt;kb_knowledge&lt;/code&gt;, or when a regulatory requirement forces you to trace who could see what, because then the boundary isn't in the connector, it's in your knowledge base layout and the IAM around &lt;code&gt;Retrieve&lt;/code&gt;. What AWS shipped is not outsourced access control, it is outsourced ingestion, and those two keep charging their maintenance cost to whoever always paid it: you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rating:&lt;/strong&gt; Adotar com escopo explícito / Adopt with&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://fernando.moretes.com/blog/rag-sobre-servicenow-o-conector-que-apaga-o-user-criteria-amazon-bedro" rel="noopener noreferrer"&gt;fernando.moretes.com&lt;/a&gt;. By Fernando F. Azevedo: Senior Solutions Architect.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aiagents</category>
      <category>bedrock</category>
      <category>rag</category>
      <category>servicenow</category>
    </item>
    <item>
      <title>ECS Early Success Criteria: where your deployment rollback ends</title>
      <dc:creator>Fernando Azevedo</dc:creator>
      <pubDate>Thu, 08 Oct 2026 17:59:42 +0000</pubDate>
      <link>https://dev.to/fernando_azevedo_6844e930/ecs-early-success-criteria-where-your-deployment-rollback-ends-37ff</link>
      <guid>https://dev.to/fernando_azevedo_6844e930/ecs-early-success-criteria-where-your-deployment-rollback-ends-37ff</guid>
      <description>&lt;p&gt;The question Amazon ECS just handed back to you is not how long your deployment takes, it is the point past which you accept having no automatic rollback. Since September 4, 2026, &lt;code&gt;earlySuccessCriteria&lt;/code&gt; lets you close a rolling deployment once a fraction of tasks is healthy, and the documentation says out loud what that costs: after the deployment completes, the circuit breaker and alarm-based rollback no longer roll the service back.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three numbers that define the feature
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;90/100&lt;/strong&gt;: healthy tasks to complete the deployment. With &lt;code&gt;healthyPercent&lt;/code&gt; 90 and a desired count of 100. The remaining ten launch through regular service scaling, outside the deployment lifecycle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2 semanas / 2 we&lt;/strong&gt;: cleanup window in DEFERRED mode. ECS tries to drain source revision tasks for up to two weeks after success. A task protected beyond that is never cleaned up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;0&lt;/strong&gt;: new deployment statuses. The lifecycle stays &lt;code&gt;IN_PROGRESS&lt;/code&gt; → &lt;code&gt;SUCCESSFUL&lt;/code&gt;. Nothing in EventBridge or CloudTrail tells an early success apart from a full one.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The signal: the deployment stopped being a binary contract
&lt;/h2&gt;

&lt;p&gt;Until now, an ECS rolling deployment only completed when four things were true at once: the target revision reached 100% of the desired count with every task running and healthy, neither the circuit breaker nor a CloudWatch alarm triggered a rollback, the alarm bake time elapsed, and the source revision tasks were cleaned up. That is an all-or-nothing contract, designed for a homogeneous fleet that launches in seconds.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;earlySuccessCriteria&lt;/code&gt;, available for the &lt;code&gt;ROLLING&lt;/code&gt; strategy in all commercial Regions and GovCloud (US), splits that contract in two. You declare a &lt;code&gt;healthyPercent&lt;/code&gt;, the fraction of the desired count that must be running and healthy on the target revision, and ECS completes the deployment there. With a desired count of 100 and &lt;code&gt;healthyPercent&lt;/code&gt; 90, the deployment turns &lt;code&gt;SUCCESSFUL&lt;/code&gt; at task 90; the remaining ten launch afterwards through regular service scaling.&lt;/p&gt;

&lt;p&gt;The motivating case is easy to recognize: GPU-accelerated inference. The tail of a fleet on specialized capacity does not launch at the speed of its head, the variable is hardware availability, not your container. The deployment stays open waiting for two tasks that depend on instance supply, the pipeline behind it queues, and the next deploy never starts. The same applies to the other side of the trade: services with long-lived connections, whose source revision is slow to drain and holds the deployment open for a reason that has nothing to do with the health of the new code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where protection ends: the deployment timeline
&lt;/h2&gt;

&lt;p&gt;The same deployment, split by the success boundary. Left of it everything is reversible; right of it nothing is.&lt;/p&gt;

&lt;h3&gt;
  
  
  🟧 AWS ECS, janela protegida (rollback ativo)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;earlySuccessCriteria enable=true, healthyPercent=90 (compute)&lt;/li&gt;
&lt;li&gt;1ª task saudável / first healthy task pré-condição da avaliação (compute)&lt;/li&gt;
&lt;li&gt;Gate: 90 de 100 tasks arredondado p/ cima / rounded up (compute)&lt;/li&gt;
&lt;li&gt;Deployment circuit breaker BOUNDED_PERCENT 50 → 3..200 (security)&lt;/li&gt;
&lt;li&gt;CloudWatch alarm + bakeTimeInMinutes (security)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🔀 Cleanup da revisão fonte / source revision cleanup
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;BLOCKING drena antes do SUCCESSFUL (messaging)&lt;/li&gt;
&lt;li&gt;DEFERRED SUCCESSFUL antes de drenar (messaging)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🟩 Fora do deployment, sem rollback / no rollback
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Service scaling 10 tasks restantes / remaining (compute)&lt;/li&gt;
&lt;li&gt;Tasks da revisão fonte scale-in protection ≤ 2880 min (compute)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  📡 Sinais / observability
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;DescribeServiceDeployments snapshot pós-sucesso (data)&lt;/li&gt;
&lt;li&gt;DescribeServices contagem viva / live counts (data)&lt;/li&gt;
&lt;li&gt;EventBridge IN_PROGRESS → SUCCESSFUL (messaging)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Flows
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;cicd -&amp;gt; cfg: applies deployment-configuration&lt;/li&gt;
&lt;li&gt;cfg -&amp;gt; first: ECS waits for 1 healthy task&lt;/li&gt;
&lt;li&gt;first -&amp;gt; gate: counts healthy tasks&lt;/li&gt;
&lt;li&gt;cb -&amp;gt; gate: can roll back up to here&lt;/li&gt;
&lt;li&gt;alarm -&amp;gt; gate: bake time before completion&lt;/li&gt;
&lt;li&gt;gate -&amp;gt; blocking: cleanup = BLOCKING&lt;/li&gt;
&lt;li&gt;gate -&amp;gt; deferred: cleanup = DEFERRED&lt;/li&gt;
&lt;li&gt;blocking -&amp;gt; scaling: success after draining&lt;/li&gt;
&lt;li&gt;deferred -&amp;gt; scaling: success before draining&lt;/li&gt;
&lt;li&gt;deferred -&amp;gt; drain: async drain, up to 2 weeks&lt;/li&gt;
&lt;li&gt;scaling -&amp;gt; ds: only source of real counts&lt;/li&gt;
&lt;li&gt;gate -&amp;gt; dsd: freezes the counters&lt;/li&gt;
&lt;li&gt;gate -&amp;gt; eb: same event as always&lt;/li&gt;
&lt;li&gt;cicd -&amp;gt; eb: unblocks the next deploy&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What changes for whoever designs the service
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;ROLLING&lt;/code&gt; only:&lt;/strong&gt; the &lt;code&gt;BLUE_GREEN&lt;/code&gt;, &lt;code&gt;LINEAR&lt;/code&gt; and &lt;code&gt;CANARY&lt;/code&gt; strategies have no &lt;code&gt;earlySuccessCriteria&lt;/code&gt;, they already solve the same problem through traffic shifting, at a different cost.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;minimumHealthyPercent&lt;/code&gt; closes the range:&lt;/strong&gt; &lt;code&gt;healthyPercent&lt;/code&gt; is an integer between 0 and 100 and must sit between the service &lt;code&gt;minimumHealthyPercent&lt;/code&gt; and 100. With the 100% default for replica services, there is no range left.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It rounds up:&lt;/strong&gt; a desired count of 3 at 50% completes at 2 tasks, not 1. A desired count of 10 at 80% completes at 8. On a small service the gain is zero.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;There is no shortcut to zero:&lt;/strong&gt; ECS launches at least one task on the target revision and waits for it to become healthy before evaluating the percentage. Broken code does not complete early, it fails.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Once complete, it is final:&lt;/strong&gt; the circuit breaker and alarm-based rollback stop acting after success, including while the remaining tasks launch. A completed deployment cannot be stopped.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The line you are moving is the rollback line
&lt;/h2&gt;

&lt;p&gt;Reading this as a faster deploy is reading it wrong. &lt;code&gt;healthyPercent&lt;/code&gt; is not an accelerator, it is a declaration of where automatic protection ends. The documentation is explicit: after ECS completes the deployment, the circuit breaker and alarm-based rollback no longer roll the service back, including while the remaining tasks launch through regular service scaling.&lt;/p&gt;

&lt;p&gt;Translated to on-call terms: with a desired count of 100 and &lt;code&gt;healthyPercent&lt;/code&gt; 90, the last ten tasks launch without a net. If the ninety-fifth fails for a reason that only shows under real load, a connection pool running out, a secret that only resolves in one AZ, a per-instance license cap: ECS replaces it with the same target revision, repeatedly, without returning to the previous one. The failure mode is not the deployment failing; it is the deployment passing and the service degrading slowly, with the pipeline already green and the team already on another task.&lt;/p&gt;

&lt;p&gt;The circuit breaker still protects what is left of the window, and its sensitivity matters. The default &lt;code&gt;BOUNDED_PERCENT&lt;/code&gt; type multiplies the value (default 50) by the desired count and clamps the result between 3 and 200: with 100 desired tasks that is 50 tolerated failures before rollback; with 400, the 200 cap applies. Worse, with &lt;code&gt;resetOnHealthyTask&lt;/code&gt; at &lt;code&gt;true&lt;/code&gt;, the default, the counter resets on every healthy task, so interleaved failures never accumulate. Shortening the protected window with &lt;code&gt;healthyPercent&lt;/code&gt; while leaving the circuit breaker on its permissive default is applying two relaxations at once and calling it speed.&lt;/p&gt;

&lt;h2&gt;
  
  
  BLOCKING or DEFERRED: same flag, two different risks
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Criterion&lt;/th&gt;
&lt;th&gt;BLOCKING&lt;/th&gt;
&lt;th&gt;DEFERRED&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;When it turns SUCCESSFUL&lt;/td&gt;
&lt;td&gt;After &lt;code&gt;healthyPercent&lt;/code&gt;, the bake time and the cleanup of source revision tasks.&lt;/td&gt;
&lt;td&gt;After &lt;code&gt;healthyPercent&lt;/code&gt; and the bake time. Cleanup happens outside the deployment.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Source revision tasks&lt;/td&gt;
&lt;td&gt;Already gone when the pipeline gets its green light.&lt;/td&gt;
&lt;td&gt;May keep running; ECS tries to drain them for up to two weeks.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Capacity cost&lt;/td&gt;
&lt;td&gt;Predictable: both revisions coexist only inside the deployment window.&lt;/td&gt;
&lt;td&gt;Two revisions billing at once for an indeterminate period, within &lt;code&gt;maximumPercent&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Risk you accept&lt;/td&gt;
&lt;td&gt;A longer deployment if draining has a long tail; CI/CD tool timeouts.&lt;/td&gt;
&lt;td&gt;Traffic served by old code after the deploy is declared successful.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Use when&lt;/td&gt;
&lt;td&gt;This is your default. Pick it unless you have a written reason not to.&lt;/td&gt;
&lt;td&gt;Long-lived connections, game sessions, async jobs, or task scale-in protection with short expiry.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Choosing the number with arithmetic, not faith
&lt;/h2&gt;

&lt;p&gt;Three constraints close the range before any opinion enters. First: &lt;code&gt;healthyPercent&lt;/code&gt; must be greater than or equal to the service &lt;code&gt;minimumHealthyPercent&lt;/code&gt;. If you kept the 100% replica-service default, there is no range at all, lowering &lt;code&gt;minimumHealthyPercent&lt;/code&gt; is a prerequisite, and it governs how many tasks the scheduler may stop during the swap, which is a capacity decision, not a speed one.&lt;/p&gt;

&lt;p&gt;Second: the value is rounded up against the desired count. With 3 tasks at 50%, the deployment completes at 2. With 10 at 80%, at 8. On a small service each percentage point is worth a whole task, and the time saved trends to zero: &lt;code&gt;earlySuccessCriteria&lt;/code&gt; only pays on a large fleet or on slow capacity.&lt;/p&gt;

&lt;p&gt;Third: ECS launches at least one task on the target revision and waits for it to become healthy before evaluating the percentage. And a task only counts as healthy after passing the health checks you configured, with a load balancer that means the target group health check, and with an essential container that defines a health check, both.&lt;/p&gt;

&lt;p&gt;From there it is a risk decision, and a conditional one. Use 90% or more when the service is stateless behind an ALB and the health check exercises a real dependency, not a &lt;code&gt;/healthz&lt;/code&gt; that returns 200 without touching anything. Stay at 100%, or leave the feature off, when the tail of tasks carries state, a shard or a per-instance license. And if the motivation is a slow pipeline, measure first: in a rolling deployment the time usually lives in &lt;code&gt;healthCheckGracePeriodSeconds&lt;/code&gt;, in the container health check &lt;code&gt;startPeriod&lt;/code&gt; and in the target group deregistration delay, none of which the success rule changes.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;DEFERRED has an expiry date:&lt;/strong&gt; In &lt;code&gt;DEFERRED&lt;/code&gt; mode, ECS tries to clean up source revision tasks for up to two weeks after success, and it cannot stop tasks with active scale-in protection. Protection caps at 2880 minutes (48 hours) per call, but it is renewable: a worker that renews its own protection in a loop keeps the old revision alive past the window and ECS gives up. Add the 3-hour CloudFormation &lt;code&gt;update-stack&lt;/code&gt; timeout and you have two revisions billing at once, serving traffic, with nobody watching, because the deployment that created them is already marked successful. Instrument the age of source revision tasks via &lt;code&gt;DescribeServices&lt;/code&gt;; there is no ready-made alarm for it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What the dashboard starts saying, and what it stops saying
&lt;/h2&gt;

&lt;p&gt;The feature invents no new status. The lifecycle stays &lt;code&gt;IN_PROGRESS&lt;/code&gt; → &lt;code&gt;SUCCESSFUL&lt;/code&gt;, the EventBridge service deployment state change events and CloudTrail records are the same, and a deployment that completed early shows up normally when you filter &lt;code&gt;ListServiceDeployments&lt;/code&gt; by &lt;code&gt;SUCCESSFUL&lt;/code&gt;. That is excellent for compatibility and terrible for auditing: no consumer of your event bus can tell rolled out fully apart from rolled out to 90% and handed the rest to autoscaling. If your change process needs that distinction, and under BACEN, PCI-DSS or SOX it does, the source is &lt;code&gt;DescribeServiceDeployments&lt;/code&gt;, which returns the early success criteria configuration alongside the deployment.&lt;/p&gt;

&lt;p&gt;There is a silent trap in the counters. While the deployment is in progress, &lt;code&gt;DescribeServiceDeployments&lt;/code&gt; returns live counts; after it completes, it returns a snapshot from the moment of completion, in both cleanup modes. A dashboard reading task count from there will show 90 forever, even with 100 tasks running and even with the source revision still draining. The live count lives in &lt;code&gt;DescribeServices&lt;/code&gt;, and nowhere else.&lt;/p&gt;

&lt;p&gt;So the alarm that matters moves. Before, an EventBridge rule with &lt;code&gt;eventName&lt;/code&gt; equal to &lt;code&gt;SERVICE_DEPLOYMENT_FAILED&lt;/code&gt; covered most of the release risk; now it covers only the window up to &lt;code&gt;healthyPercent&lt;/code&gt;. What deserves an alarm instead is the gap between &lt;code&gt;desiredCount&lt;/code&gt; and &lt;code&gt;runningCount&lt;/code&gt; on the target revision a few minutes after success, the number of task replacements per revision, and the age of source revision tasks in &lt;code&gt;DEFERRED&lt;/code&gt; mode. None of those three comes for free.&lt;/p&gt;

&lt;h2&gt;
  
  
  How this will be used wrong
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;healthyPercent&lt;/code&gt; as a pipeline accelerator:&lt;/strong&gt; dropping to 50% because the build is slow buys pipeline minutes with half the fleet launching without automatic rollback. The time was in the health check and draining timers, not in the success rule.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One value for every service via a shared Terraform module:&lt;/strong&gt; the right number depends on desired count, capacity cost and whether the service carries state. A global &lt;code&gt;healthyPercent&lt;/code&gt; is the definition of a setting nobody reviews.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;DEFERRED&lt;/code&gt; combined with scale-in protection renewed in a loop:&lt;/strong&gt; old tasks become permanent, ECS gives up after two weeks, and you find out from the invoice rather than the dashboard.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keeping the release dashboard on &lt;code&gt;DescribeServiceDeployments&lt;/code&gt;:&lt;/strong&gt; after success the counters become a snapshot. The panel stays green and frozen while the tail of the fleet does whatever it wants.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What actually changed:&lt;/strong&gt; What changed is not the duration of the deployment, it is who answers for the tail. With &lt;code&gt;earlySuccessCriteria&lt;/code&gt;, the launch of the last tasks moves from the deployment plane, which has rollback, a circuit breaker and a failure event, to the service scaling plane, which has none of the three. It is the same choice you already make when you set an SLO: accepting known error in exchange for speed. The difference is that here the choice becomes an integer in an infrastructure-as-code file, and an integer in an IaC file does not get a risk review, it gets a syntax review.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Curation note:&lt;/strong&gt; Today I would enable this on exactly one kind of service: inference on specialized capacity, where the tail of tasks depends on hardware availability rather than on my code, and even then with &lt;code&gt;healthyPercent&lt;/code&gt; at 90, &lt;code&gt;sourceServiceRevisionCleanup&lt;/code&gt; at &lt;code&gt;BLOCKING&lt;/code&gt;, and the circuit breaker switched to a &lt;code&gt;COUNT&lt;/code&gt; &lt;code&gt;thresholdConfiguration&lt;/code&gt; with a low value, precisely because the protected window got shorter. On a payments service I would not enable it: the gain is pipeline minutes and the loss is automatic rollback under real load, which is exactly the control I cannot rebuild at 2 AM. The lesson that cost me dearly on financial platforms: every mechanism that unblocks the pipeline sends its bill on the first failure that only appears with production traffic, and the bill arrives on-call, not in the retro. Before touching the success criterion I measure where the deployment actually spends time, when the answer is grace period and deregistration delay, &lt;code&gt;healthyPercent&lt;/code&gt; would have changed nothing and the risk would have been taken for free.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/about-aws/whats-new/2026/09/amazon-ecs-deployments-early-success/" rel="noopener noreferrer"&gt;Amazon ECS introduces Early Success Criteria for service deployments (AWS What's New, 04/09/2026)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonECS/latest/developerguide/early-success-criteria.html" rel="noopener noreferrer"&gt;Complete Amazon ECS rolling deployments early with early success criteria&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_DeploymentEarlySuccessCriteria.html" rel="noopener noreferrer"&gt;DeploymentEarlySuccessCriteria: Amazon ECS API Reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_DeploymentConfiguration.html" rel="noopener noreferrer"&gt;DeploymentConfiguration: Amazon ECS API Reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonECS/latest/developerguide/deployment-circuit-breaker.html" rel="noopener noreferrer"&gt;How the Amazon ECS deployment circuit breaker detects failures&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-scale-in-protection.html" rel="noopener noreferrer"&gt;Protect your Amazon ECS tasks from being terminated by scale-in events&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonECS/latest/developerguide/deployment-type-ecs.html" rel="noopener noreferrer"&gt;Deploy Amazon ECS services by replacing tasks (rolling update)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://muhammadraza.me/2026/what-actually-happens-during-an-ecs-rolling-deployment/" rel="noopener noreferrer"&gt;What Actually Happens During an ECS Rolling Deployment: Muhammad Raza&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Verdict
&lt;/h2&gt;

&lt;p&gt;Enable &lt;code&gt;earlySuccessCriteria&lt;/code&gt; when the tail of your deployment depends on capacity you do not control: GPU, specialized instances, long-lived connection draining, and when the service is stateless behind a health check that exercises a real dependency. In that case: &lt;code&gt;healthyPercent&lt;/code&gt; between 90 and 95, &lt;code&gt;BLOCKING&lt;/code&gt; as the default, &lt;code&gt;DEFERRED&lt;/code&gt; only where long-lived connections or short, monitored scale-in protection exist, the circuit breaker switched to &lt;code&gt;COUNT&lt;/code&gt; to compensate for the shorter protected window, and a new alarm on the gap between &lt;code&gt;desiredCount&lt;/code&gt; and &lt;code&gt;runningCount&lt;/code&gt; a few minutes after success. Do not enable it as a cure for a slow pipeline: rolling deployment time lives in the health check and draining timers, and swapping the success rule only moves risk past the rollback line. The &lt;code&gt;enable: false&lt;/code&gt; default remains right for most services, this is a well-designed exception feature, with its boundary documented out loud, not a new baseline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rating:&lt;/strong&gt; Ligar por exceção, não por padrão / Enab&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://fernando.moretes.com/blog/ecs-early-success-criteria-onde-termina-o-rollback-do-seu-deploy-amazon-ecs-i" rel="noopener noreferrer"&gt;fernando.moretes.com&lt;/a&gt;. By Fernando F. Azevedo: Senior Solutions Architect.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>awscloud</category>
      <category>ecs</category>
      <category>deployments</category>
      <category>cicd</category>
    </item>
    <item>
      <title>Fable 5.1 on Bedrock: data retention is now an architecture decision</title>
      <dc:creator>Fernando Azevedo</dc:creator>
      <pubDate>Thu, 08 Oct 2026 17:59:06 +0000</pubDate>
      <link>https://dev.to/fernando_azevedo_6844e930/fable-51-on-bedrock-data-retention-is-now-an-architecture-decision-5cef</link>
      <guid>https://dev.to/fernando_azevedo_6844e930/fable-51-on-bedrock-data-retention-is-now-an-architecture-decision-5cef</guid>
      <description>&lt;p&gt;The September 7 AWS Weekly Roundup filed Claude Fable 5.1 as one more model launch between EC2 R9g and Amazon Linux 2027. It isn't. For the first time on a frontier model in Bedrock, the key that unlocks access lives neither in IAM nor in the Marketplace, it lives in an account-scoped data retention setting that you have to flip through the API, because there is no console UI for it. I spent the week reading the &lt;code&gt;data-retention&lt;/code&gt; and &lt;code&gt;abuse-detection&lt;/code&gt; docs line by line, and the conclusion is uncomfortable for anyone operating under regulation: enabling Fable 5.1 is a landing-zone decision, not a product-team decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually changed, and what didn't
&lt;/h2&gt;

&lt;p&gt;Bedrock has always sold itself on two security models: &lt;strong&gt;zero operator access&lt;/strong&gt; (no service operator can read model input or output) and &lt;strong&gt;zero data retention&lt;/strong&gt; by default. The abuse detection doc still asserts both. What Anthropic introduced with the &lt;strong&gt;Covered Model&lt;/strong&gt; category is a named exception: for Claude Fable 5 and Fable 5.1, &lt;em&gt;all&lt;/em&gt; traffic is retained for up to 30 days for automated offline abuse detection, and classifier-flagged traffic is subject to human review performed by AWS.&lt;/p&gt;

&lt;p&gt;Two things that did not change, worth repeating because a hurried reading gets both wrong. First: &lt;strong&gt;your content does not go to Anthropic.&lt;/strong&gt; The &lt;code&gt;provider_data_share&lt;/code&gt; mode is legacy, and the doc itself states that sharing content with model providers is not supported today, it grants a permission nobody exercises. Second: &lt;strong&gt;nothing changes for Claude models released before Fable 5.&lt;/strong&gt; The doc is explicit, and the mechanism is elegant: each model declares its own &lt;code&gt;allowed_modes&lt;/code&gt;, so an account set to &lt;code&gt;aws_review&lt;/code&gt; that also calls Opus 4.8 retains nothing from Opus: Opus permits &lt;code&gt;none&lt;/code&gt;, and &lt;code&gt;none&lt;/code&gt; wins for it.&lt;/p&gt;

&lt;p&gt;The friction isn't what AWS does with the data. It's that the authorization to do it lives in a scope most teams don't control.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the effective mode resolves and where the model gets blocked
&lt;/h2&gt;

&lt;p&gt;Resolution is hierarchical: the first scope whose value is not &lt;code&gt;inherit&lt;/code&gt; wins. If the effective mode lands below the model's minimum, the model shows up as &lt;code&gt;status: "unavailable"&lt;/code&gt; in the model list, not a permission error, but the model quietly leaving your catalog.&lt;/p&gt;

&lt;h3&gt;
  
  
  🔐 Governança, o que a organização permite
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;SCP Deny em bedrock:DataRetentionMode (security)&lt;/li&gt;
&lt;li&gt;IAM PutAccountDataRetention · UpdateProject (security)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🟧 AWS, resolução do modo efetivo
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Projeto mais específico · pode ser inherit (compute)&lt;/li&gt;
&lt;li&gt;Conta PUT /v1/data_retention (compute)&lt;/li&gt;
&lt;li&gt;Padrão do modelo somente leitura (data)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🤖 Bedrock, a trava de disponibilidade
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;allowed_modes none &amp;lt; default &amp;lt; aws_review &amp;lt; provider_data_share (ai)&lt;/li&gt;
&lt;li&gt;Claude Fable 5.1 exige aws_review ou superior (ai)&lt;/li&gt;
&lt;li&gt;status: unavailable status_reason explica o motivo (external)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  📦 Fronteira de retenção: 30 dias dentro da AWS
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Prompts e saídas até 30 dias · região de destino do CRIS (storage)&lt;/li&gt;
&lt;li&gt;Detecção de abuso offline automatizada · todo o tráfego (security)&lt;/li&gt;
&lt;li&gt;Revisão humana pela AWS só tráfego marcado por classificador (security)&lt;/li&gt;
&lt;li&gt;Enterprise Frontier Safeguards ZDR até 31/12/2026 (security)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Flows
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;scp -&amp;gt; acct: caps which modes can be written&lt;/li&gt;
&lt;li&gt;iam -&amp;gt; proj: who may change the scope&lt;/li&gt;
&lt;li&gt;proj -&amp;gt; acct: if inherit, defer&lt;/li&gt;
&lt;li&gt;acct -&amp;gt; mdef: if inherit, defer&lt;/li&gt;
&lt;li&gt;acct -&amp;gt; gate: effective mode&lt;/li&gt;
&lt;li&gt;app -&amp;gt; gate: inference call&lt;/li&gt;
&lt;li&gt;gate -&amp;gt; fable: mode ≥ aws_review&lt;/li&gt;
&lt;li&gt;gate -&amp;gt; unavail: mode none or default&lt;/li&gt;
&lt;li&gt;fable -&amp;gt; store: writes input and output&lt;/li&gt;
&lt;li&gt;store -&amp;gt; auto: scans full volume&lt;/li&gt;
&lt;li&gt;auto -&amp;gt; human: escalates classifier hits&lt;/li&gt;
&lt;li&gt;efs -&amp;gt; store: suspends retention while eligible&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The four modes, and what each one costs in governance
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;What happens to the data&lt;/th&gt;
&lt;th&gt;Does Fable 5.1 work?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;none&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Zero retention. Nothing is written to durable storage. On the Responses API, &lt;code&gt;store=true&lt;/code&gt; is rejected.&lt;/td&gt;
&lt;td&gt;No, the request is blocked.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;default&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The model's own policy applies. If ZDR applied before, it still does. &lt;code&gt;store=false&lt;/code&gt; does not guarantee zero retention.&lt;/td&gt;
&lt;td&gt;No, it shows up as &lt;code&gt;unavailable&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;aws_review&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Retained up to 30 days inside the AWS boundary; AWS may review. The provider receives nothing.&lt;/td&gt;
&lt;td&gt;Yes, this is the minimum required.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;provider_data_share&lt;/code&gt; (legacy)&lt;/td&gt;
&lt;td&gt;In practice identical handling to &lt;code&gt;aws_review&lt;/code&gt;: provider sharing is not supported today.&lt;/td&gt;
&lt;td&gt;Yes, it sits higher in the ordering and already satisfies it.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inherit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No opinion at that scope; defers to a broader one. It is the default for new accounts and projects.&lt;/td&gt;
&lt;td&gt;Depends on what sits above.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The account became the isolation boundary, and your SCP already said no
&lt;/h2&gt;

&lt;p&gt;If you work under BACEN 4.893, PCI-DSS or LGPD, odds are your organization already has an SCP denying anything that writes prompts to durable storage. The Bedrock docs even publish that SCP as an example, denying &lt;code&gt;bedrock-mantle:PutAccountDataRetention&lt;/code&gt; whenever &lt;code&gt;bedrock-mantle:DataRetentionMode&lt;/code&gt; is anything other than &lt;code&gt;none&lt;/code&gt;. That SCP is correct, and it makes Fable 5.1 permanently unavailable across the whole OU.&lt;/p&gt;

&lt;p&gt;What bothers me isn't the block; it's the granularity. The mode resolves across &lt;strong&gt;project → account → model default&lt;/strong&gt;, and the only practical command today is &lt;code&gt;aws bedrock put-account-data-retention --mode aws_review&lt;/code&gt;, which applies to the entire account. Turning Fable 5.1 on in the shared production account authorizes AWS human review over &lt;em&gt;every&lt;/em&gt; workload that runs on a model requiring review, indefinitely, until someone remembers to turn it off. Project scope exists precisely to avoid that, and it is the only reason I would consider enabling this without cutting a new account.&lt;/p&gt;

&lt;p&gt;The practical recommendation inverts the instinct: &lt;strong&gt;don't ask for an SCP exception, ask for an account.&lt;/strong&gt; A dedicated account, inside its own OU with an SCP that permits &lt;code&gt;aws_review&lt;/code&gt; and nothing beyond it, gives you a named blast radius, a separate bill, and a one-line audit answer. An exception carved into a production OU's SCP is debt nobody audits later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data residency: outside the US, the Global profile is not a choice
&lt;/h2&gt;

&lt;p&gt;This is the part every piece of coverage I read missed, and it is the most serious one for anyone operating in Brazil or Europe. The Fable 5.1 model card lists the &lt;code&gt;bedrock-runtime&lt;/code&gt; endpoint with &lt;strong&gt;In-Region unsupported in every Region on earth&lt;/strong&gt;. The Geo profile (&lt;code&gt;us.anthropic.claude-fable-5-1&lt;/code&gt;) exists only in the US, Canada and GovCloud. In &lt;code&gt;sa-east-1&lt;/code&gt;, &lt;code&gt;eu-central-1&lt;/code&gt;, &lt;code&gt;eu-west-1&lt;/code&gt; and everywhere else, the only open door is the Global profile: &lt;code&gt;global.anthropic.claude-fable-5-1&lt;/code&gt;, which routes inference anywhere in the world.&lt;/p&gt;

&lt;p&gt;Now pair that with a sentence in the abuse detection doc: if cross-region inference is enabled, retained inputs and outputs are stored &lt;strong&gt;in the destination Region&lt;/strong&gt;, meaning wherever the request was processed. Put the two together and the result is blunt: a bank in São Paulo calling Fable 5.1 is authorizing 30-day retention of prompts in a Region it did not pick and cannot predict per request. That is not a latency footnote, it is a data-residency assertion that belongs in your DPIA before any proof of concept.&lt;/p&gt;

&lt;p&gt;For residency-constrained shops the options are short and none are free: run through the US Geo profile and classify the data as transferred, negotiate Enterprise Frontier Safeguards eligibility while it still grants ZDR, or accept that Fable 5.1 stays outside the regulated perimeter and serves workloads with no personal data, code generation over an internal repository, for instance.&lt;/p&gt;

&lt;h2&gt;
  
  
  The playbook for tomorrow morning
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Find out which mode your accounts are already in&lt;/strong&gt;: Before discussing anything, run &lt;code&gt;GET /v1/data_retention&lt;/code&gt; on every account with Bedrock enabled. Older accounts sitting on &lt;code&gt;provider_data_share&lt;/code&gt; are already above &lt;code&gt;aws_review&lt;/code&gt; in the ordering, they gain Fable 5.1 access without anyone having approved anything. That finding usually changes the meeting.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Pick the scope before the mode&lt;/strong&gt;: Project beats account, account beats model default. If you need a pilot, set &lt;code&gt;data_retention&lt;/code&gt; on the project through &lt;code&gt;POST /v1/organization/projects/{project_id}&lt;/code&gt; and leave the account on &lt;code&gt;inherit&lt;/code&gt; or &lt;code&gt;none&lt;/code&gt;. Never start at the account.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Write the SCP before flipping the mode&lt;/strong&gt;: The &lt;code&gt;bedrock:DataRetentionMode&lt;/code&gt; condition key (and &lt;code&gt;bedrock-mantle:DataRetentionMode&lt;/code&gt;) exists for exactly this. An SCP that permits &lt;code&gt;aws_review&lt;/code&gt; and denies &lt;code&gt;provider_data_share&lt;/code&gt; stops anyone from escalating back to the legacy mode out of habit.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Pin the inference profile in code&lt;/strong&gt;: There is no In-Region endpoint. Explicitly choose between &lt;code&gt;us.anthropic.claude-fable-5-1&lt;/code&gt; and &lt;code&gt;global.anthropic.claude-fable-5-1&lt;/code&gt; and document the reason next to the data that flows through it. Leaving that to an ownerless environment variable is how global routing reaches production unreviewed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Fix your sampling parameters before the first deploy&lt;/strong&gt;: &lt;code&gt;temperature&lt;/code&gt; must be 1.0 or unset, &lt;code&gt;top_p&lt;/code&gt; 0.99 or unset, the two cannot be sent together, and &lt;code&gt;top_k&lt;/code&gt; is unsupported. A wrapper that copies your Sonnet parameters breaks on the first call, and it breaks in production, not in the unit test that mocks the client.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Treat &lt;code&gt;stop_reason: "refusal"&lt;/code&gt; as a primary path&lt;/strong&gt;: The model returns HTTP 200 with &lt;code&gt;stop_reason: "refusal"&lt;/code&gt; and a &lt;code&gt;stop_details&lt;/code&gt; object carrying the category. The docs warn that refusal rates are materially higher than on previous Claude models. Code that only inspects the HTTP status will hand the user an empty response and log nothing.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Put December 31, 2026 on the calendar&lt;/strong&gt;: Enterprise Frontier Safeguards makes eligible customers ZDR &lt;strong&gt;through December 31, 2026&lt;/strong&gt;. After that, all traffic returns to being retained for up to 30 days for automated offline abuse detection. If your compliance sign-off depends on ZDR, it has an expiry date.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The cache checkpoint that fails silently:&lt;/strong&gt; Fable 5.1 prompt caching accepts at most 4 checkpoints per request, with a 5-minute or 1-hour TTL, across the &lt;code&gt;system&lt;/code&gt;, &lt;code&gt;messages&lt;/code&gt; and &lt;code&gt;tools&lt;/code&gt; fields. The expensive detail: the minimum is &lt;strong&gt;512 tokens per checkpoint&lt;/strong&gt;, and below that caching is disabled with no error at all, the response comes back normal, with cache counters at zero. In an agent that runs for hours, that is the difference between reusing your system prompt and paying for it at every step. Instrument &lt;code&gt;cacheReadInputTokens&lt;/code&gt; and &lt;code&gt;cacheWriteInputTokens&lt;/code&gt; as first-class CloudWatch metrics, not as log fields: with no alarm, a refactor that shortened the system prompt by 40 tokens disappears into the noise and surfaces only on the invoice.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The real cost: adaptive thinking, billed refusals, and an endpoint without guardrails
&lt;/h2&gt;

&lt;p&gt;Fable 5.1's adaptive reasoning is &lt;strong&gt;always on and cannot be disabled&lt;/strong&gt;; what you control is the effort level: &lt;code&gt;low&lt;/code&gt;, &lt;code&gt;medium&lt;/code&gt;, &lt;code&gt;high&lt;/code&gt;, &lt;code&gt;xhigh&lt;/code&gt;, &lt;code&gt;max&lt;/code&gt;, defaulting to &lt;code&gt;high&lt;/code&gt;. A public Converse test with &lt;code&gt;{"output_config":{"effort":"max"}}&lt;/code&gt; showed the jump: 410 thinking tokens against zero on the default call, with latency moving from ~8.3s to ~13.7s. Thinking tokens are billed output tokens. A miscalibrated &lt;code&gt;effort&lt;/code&gt; inside an agent loop doesn't raise your bill by 10%, it raises it by multiples, and the only evidence is a &lt;code&gt;usage&lt;/code&gt; block nobody aggregates.&lt;/p&gt;

&lt;p&gt;The second cost leak is refusal. Prompt-stage refusals, blocked before inference begins, are not billed. Mid-stream refusals, after partial output, &lt;strong&gt;are billed for the tokens generated before the block&lt;/strong&gt;. With a materially higher refusal rate coming from the cybersecurity and life-sciences classifiers, a vulnerability-analysis pipeline can pay for output the user never saw.&lt;/p&gt;

&lt;p&gt;And there is an endpoint choice that reshapes the whole architecture: on &lt;code&gt;bedrock-mantle&lt;/code&gt;, Fable 5.1 supports &lt;strong&gt;neither Guardrails nor Knowledge Bases nor Agents nor the Converse API&lt;/strong&gt;: only streaming, prompt caching and token counting. Bedrock Guardrails exist only through &lt;code&gt;bedrock-runtime&lt;/code&gt;. If you need your own content filter in front of the model, there is no decision to make; &lt;code&gt;bedrock-runtime&lt;/code&gt; is the only path, and it's the one the docs recommend for new applications anyway.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anti-patterns already taking root in week one
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Flipping &lt;code&gt;aws_review&lt;/code&gt; on the shared production account&lt;/strong&gt;: the mode is account-scoped, not per request. One CLI line authorizes retention and review over every future workload on any model requiring review, including workloads not yet written.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treating legacy &lt;code&gt;provider_data_share&lt;/code&gt; as harmless because nothing is shared&lt;/strong&gt;: it is the &lt;em&gt;most permissive&lt;/em&gt; mode in the ordering. An account sitting on it since 2024 unlocks Covered Models with nobody having reviewed the decision.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Assuming &lt;code&gt;global.&lt;/code&gt; is just a latency optimization&lt;/strong&gt;: outside the US and Canada it is the only option, and it decides which Region holds your prompts for 30 days. That is a residency decision disguised as a model ID prefix.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reusing your Sonnet client with &lt;code&gt;temperature: 0.2&lt;/code&gt; and &lt;code&gt;top_k&lt;/code&gt;&lt;/strong&gt;: Fable 5.1 requires &lt;code&gt;temperature&lt;/code&gt; at 1.0 or unset and does not support &lt;code&gt;top_k&lt;/code&gt;. Determinism through low temperature is not a lever here; use output structure and validation instead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Counting on Guardrails at the &lt;code&gt;bedrock-mantle&lt;/code&gt; endpoint&lt;/strong&gt;: they do not exist there. Discovering it after the security design has been signed off costs a redesign, not a patch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measuring cost without separating thinking tokens&lt;/strong&gt;: reasoning cannot be turned off and the default is &lt;code&gt;high&lt;/code&gt;. Without &lt;code&gt;effort&lt;/code&gt; and &lt;code&gt;usage&lt;/code&gt; as dimensions on your cost metric, you cannot explain the bill even a day later.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Questions that came up in week one
&lt;/h2&gt;

&lt;h3&gt;
  
  
  My account is on &lt;code&gt;provider_data_share&lt;/code&gt;. Do I need to migrate to use Fable 5.1?
&lt;/h3&gt;

&lt;p&gt;No. Since &lt;code&gt;provider_data_share&lt;/code&gt; sits above &lt;code&gt;aws_review&lt;/code&gt; in the ordering, it already satisfies the requirement and nothing breaks. It is still worth migrating: &lt;code&gt;aws_review&lt;/code&gt; describes the behavior that actually happens, and in an audit report the gap between "we authorized sharing with the provider" and "we authorized review by AWS" is the gap between a question and a meeting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use Fable 5.1 and keep zero data retention?
&lt;/h3&gt;

&lt;p&gt;Only by exception. The Enterprise Frontier Safeguards program grants eligible customers ZDR through December 31, 2026, and the roadmap mentions customer-managed keys and bring-your-own-bucket storage for abuse detection. Outside it, ZDR eligibility for Claude models is evaluated per account and per model, and Anthropic is the decision maker, talk to their representative, not only to AWS.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where do I configure this in the console?
&lt;/h3&gt;

&lt;p&gt;Nowhere. The docs state explicitly that at launch there is no console UI for data retention: API or SDK only. In practice that's a feature: it forces the change through infrastructure as code and leaves a CloudTrail trail, instead of becoming one click by whoever has console access.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does turning on &lt;code&gt;aws_review&lt;/code&gt; make my other models start retaining data?
&lt;/h3&gt;

&lt;p&gt;No. Each model declares its own &lt;code&gt;allowed_modes&lt;/code&gt;, and a model that permits &lt;code&gt;none&lt;/code&gt;: Opus 4.8, for instance, retains nothing no matter how permissive the account setting is. The mode states what you &lt;em&gt;allow&lt;/em&gt;, not what happens. Even so, isolate by project: the audit will ask what you permitted, not what occurred.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What I would do on Monday:&lt;/strong&gt; I would not carve an exception into the production OU's SCP: I would open an account. A dedicated account, its own OU, an SCP that permits exactly &lt;code&gt;aws_review&lt;/code&gt; and nothing above it, &lt;code&gt;global.anthropic.claude-fable-5-1&lt;/code&gt; pinned in code with a comment saying why, and no customer data crossing into it. The hard-won lesson behind that came from elsewhere: every exception I ever carved into a production OU policy outlived the project that justified it and became an orphan permission nobody could explain two years later. An account is revocable, carries its own bill, and answers the audit in one line. And before any pilot I would run &lt;code&gt;GET /v1/data_retention&lt;/code&gt; across every account with Bedrock enabled: I'd bet at least one has been on &lt;code&gt;provider_data_share&lt;/code&gt; forever, holding Covered Model access nobody approved.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Verdict
&lt;/h2&gt;

&lt;p&gt;Fable 5.1 delivers on the axis that matters for long-horizon agents: a 1M-token context window, 128K max output, a June 2026 knowledge cutoff, and reasoning effort configurable across five levels. If your workload is code generation and review over an internal repository with no personal data, adopt it, in a dedicated account, with the project set to &lt;code&gt;aws_review&lt;/code&gt;, the inference profile pinned, and &lt;code&gt;stop_reason: "refusal"&lt;/code&gt; handled as a normal path. If your workload touches customer data under LGPD or BACEN and you sit outside the US, the verdict inverts, and it's a condition rather than an opinion: the only available profile is Global, the 30-day retention lands in whichever Region processed the request, and that belongs in a signed impact assessment before the first call. It isn't the model that is unavailable to regulated environments, it's the retention mode that doesn't yet have the granularity regulated environments require. Enterprise Frontier Safeguards is the bridge, and it has an expiry date: December 31, 2026.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rating:&lt;/strong&gt; Adote com escopo isolado, bloqueado por&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/blogs/aws/aws-weekly-roundup-claude-fable-5-1-on-aws-amazon-linux-2027-preview-aws-certified-ai-business-strategist-and-more-september-7-2026/" rel="noopener noreferrer"&gt;AWS Weekly Roundup: Claude Fable 5.1 on AWS, Amazon Linux 2027 preview, and more (Sept 7, 2026)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock/latest/userguide/data-retention.html" rel="noopener noreferrer"&gt;Amazon Bedrock User Guide: Data retention (modes, ordering, SCP condition keys)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock/latest/userguide/model-card-anthropic-claude-fable-5-1.html" rel="noopener noreferrer"&gt;Amazon Bedrock User Guide: Claude Fable 5.1 model card (context, effort, regions, caching)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock/latest/userguide/abuse-detection.html" rel="noopener noreferrer"&gt;Amazon Bedrock User Guide: Abuse detection and Enterprise Frontier Safeguards ZDR window&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/blogs/machine-learning/introducing-claude-fable-5-1-on-aws/" rel="noopener noreferrer"&gt;Introducing Claude Fable 5.1 on AWS: AWS Artificial Intelligence Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/about-aws/whats-new/2026/09/claude-fable-5-1-aws/" rel="noopener noreferrer"&gt;What's New: Claude Fable 5.1 is now available on AWS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/blogs/aws/anthropic-claude-fable-5-on-aws-mythos-class-capabilities-with-built-in-safeguards-now-available/" rel="noopener noreferrer"&gt;Anthropic Claude Fable 5 on AWS: Mythos-class capabilities with built-in safeguards&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.classmethod.jp/en/articles/bedrock-claude-fable-5-1-converse/" rel="noopener noreferrer"&gt;DevelopersIO: Calling Claude Fable 5.1 from the Converse API (effort levels, caching gotchas)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://fernando.moretes.com/blog/fable-5-1-no-bedrock-retencao-de-dados-virou-decisao-de-arquitetura-aws-weekly-r" rel="noopener noreferrer"&gt;fernando.moretes.com&lt;/a&gt;. By Fernando F. Azevedo: Senior Solutions Architect.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aiagents</category>
      <category>bedrock</category>
      <category>anthropic</category>
      <category>governance</category>
    </item>
  </channel>
</rss>
