<?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: Scott Burgholzer</title>
    <description>The latest articles on DEV Community by Scott Burgholzer (@scottburgholzer).</description>
    <link>https://dev.to/scottburgholzer</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%2F1330421%2F4d92c203-ead3-4683-a414-01b6a4e9c1de.jpg</url>
      <title>DEV Community: Scott Burgholzer</title>
      <link>https://dev.to/scottburgholzer</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/scottburgholzer"/>
    <language>en</language>
    <item>
      <title>Stop Deploying Blind: Add a Blast Radius Gate to Your CI/CD Pipeline</title>
      <dc:creator>Scott Burgholzer</dc:creator>
      <pubDate>Thu, 09 Jul 2026 03:10:20 +0000</pubDate>
      <link>https://dev.to/aws-builders/stop-deploying-blind-add-a-blast-radius-gate-to-your-cicd-pipeline-231m</link>
      <guid>https://dev.to/aws-builders/stop-deploying-blind-add-a-blast-radius-gate-to-your-cicd-pipeline-231m</guid>
      <description>&lt;p&gt;&lt;em&gt;Series: Introducing &lt;a href="https://github.com/sburgholzer/BlastRadius" rel="noopener noreferrer"&gt;Blast Radius&lt;/a&gt; — See What Breaks Before You Deploy&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What We Are Building
&lt;/h2&gt;

&lt;p&gt;This article shows you how to use Blast Radius in your own CI/CD pipeline. By the end, you'll have a deployment gate that automatically analyzes infrastructure changes, scores downstream risk, and blocks merges that exceed your threshold.&lt;/p&gt;

&lt;p&gt;Blast Radius comments on your PR with an analysis table (Highest Score, Affected Resources, AI Recommendation, Confidence) plus an AI-generated summary.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9gs37tnxk6c7v7tw3oov.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9gs37tnxk6c7v7tw3oov.png" alt="Example of a Blast Radius PR Comment" width="800" height="661"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are currently two paths to use Blast Radius:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Action:&lt;/strong&gt; fastest and auto-comments on PRs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Raw CLI:&lt;/strong&gt; works in any CI system such as GitLab, Jenkins, CircleCI, or whatever you're running.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Blast Radius is meant for your infrastructure. It works on any stage: dev, testing, production, or any stages you may use. It calls live AWS APIs (Config and Resource Explorer) against your actual account. The results reflect real dependency relationships, not static analysis.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites — Deploy the Backend
&lt;/h2&gt;

&lt;p&gt;The following must be enabled in your AWS Account before deploying the backend:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AWS Config&lt;/strong&gt; must be enabled and recording in the region you deploy to. This is where Blast Radius reads dependency relationships. &lt;a href="https://docs.aws.amazon.com/config/latest/developerguide/gs-console.html" rel="noopener noreferrer"&gt;Here's how to enable AWS Config.&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resource Explorer&lt;/strong&gt; must be enabled. It's the fallback for resources Config doesn't have relationships for. &lt;a href="https://docs.aws.amazon.com/resource-explorer/latest/userguide/getting-started-setting-up.html" rel="noopener noreferrer"&gt;Here's how to enable Resource Explorer.&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CDK Bootstrap&lt;/strong&gt; must be run via &lt;code&gt;cdk bootstrap&lt;/code&gt; in your account and region you are deploying to.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optional: Bedrock model access&lt;/strong&gt; if you want AI summaries. You may choose any model via cross-region inference profile. Without enabling Bedrock model access, threshold gates will still work but the AI gate will not.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Deploy Steps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/sburgholzer/BlastRadius.git
&lt;span class="nb"&gt;cd &lt;/span&gt;BlastRadius
npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm run build
&lt;span class="nb"&gt;cd &lt;/span&gt;packages/infra
cdk deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates an API Gateway, Step Functions state machine, Lambda functions, DynamoDB tables, S3 bucket, and a CloudFront distribution for the frontend.&lt;/p&gt;

&lt;p&gt;When the deployment finishes, grab the API URL from the CDK output. That's the only value you need for the rest of this article.&lt;/p&gt;

&lt;p&gt;To enable AI summaries, set &lt;code&gt;enableBedrockSummary&lt;/code&gt; in the CDK props. You can also configure &lt;code&gt;resultsRetentionDays&lt;/code&gt; for S3 lifecycle management. More detail on these options is in the README.&lt;/p&gt;

&lt;h2&gt;
  
  
  GitHub Action — The Five-Minute Path
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Terraform Workflow
&lt;/h3&gt;

&lt;p&gt;Terraform is the simplest path. Generate the plan, convert it to JSON, and pass it to the action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform plan -out=plan.out&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform show -json plan.out &amp;gt; plan.json&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sburgholzer/BlastRadius@v0.1.0&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform-plan&lt;/span&gt;
    &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;plan.json&lt;/span&gt;
    &lt;span class="na"&gt;threshold&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;75&lt;/span&gt;
    &lt;span class="na"&gt;ai-gate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;api-url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.BLAST_RADIUS_URL }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The action reads the JSON plan, runs the analysis, and comments the results on your PR.&lt;/p&gt;

&lt;h3&gt;
  
  
  CDK Workflow
&lt;/h3&gt;

&lt;p&gt;CDK requires a few more steps. Blast Radius needs to know what CloudFormation will actually do — the changeset actions, not just what the template looks like. A &lt;code&gt;cdk synth&lt;/code&gt; alone doesn't give you that; you have to ask CloudFormation directly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Generate CloudFormation changeset&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;npx cdk synth 2&amp;gt;/dev/null&lt;/span&gt;
    &lt;span class="s"&gt;cp cdk.out/MyStack.template.json template.json&lt;/span&gt;
    &lt;span class="s"&gt;CHANGESET_NAME="blast-radius-$(date +%s)"&lt;/span&gt;
    &lt;span class="s"&gt;aws cloudformation create-change-set \&lt;/span&gt;
      &lt;span class="s"&gt;--stack-name MyStack \&lt;/span&gt;
      &lt;span class="s"&gt;--change-set-name $CHANGESET_NAME \&lt;/span&gt;
      &lt;span class="s"&gt;--template-body file://template.json \&lt;/span&gt;
      &lt;span class="s"&gt;--capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM CAPABILITY_AUTO_EXPAND \&lt;/span&gt;
      &lt;span class="s"&gt;--change-set-type UPDATE \&lt;/span&gt;
      &lt;span class="s"&gt;--output json&lt;/span&gt;
    &lt;span class="s"&gt;aws cloudformation wait change-set-create-complete \&lt;/span&gt;
      &lt;span class="s"&gt;--stack-name MyStack \&lt;/span&gt;
      &lt;span class="s"&gt;--change-set-name $CHANGESET_NAME&lt;/span&gt;
    &lt;span class="s"&gt;aws cloudformation describe-change-set \&lt;/span&gt;
      &lt;span class="s"&gt;--stack-name MyStack \&lt;/span&gt;
      &lt;span class="s"&gt;--change-set-name $CHANGESET_NAME \&lt;/span&gt;
      &lt;span class="s"&gt;--output json &amp;gt; changeset.json&lt;/span&gt;
    &lt;span class="s"&gt;aws cloudformation delete-change-set \&lt;/span&gt;
      &lt;span class="s"&gt;--stack-name MyStack \&lt;/span&gt;
      &lt;span class="s"&gt;--change-set-name $CHANGESET_NAME&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Blast Radius Analysis&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sburgholzer/BlastRadius@v0.1.0&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;blast-radius&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cloudformation&lt;/span&gt;
    &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;changeset.json&lt;/span&gt;
    &lt;span class="na"&gt;threshold&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;75&lt;/span&gt;
    &lt;span class="na"&gt;ai-gate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;api-url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.BLAST_RADIUS_API_URL }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each step has a specific job: synth produces the template, create-change-set asks CloudFormation what it would change, wait holds until it's ready, describe-change-set captures the answer as JSON, and delete-change-set cleans up so it doesn't linger between runs.&lt;/p&gt;

&lt;h3&gt;
  
  
  CloudFormation Workflow
&lt;/h3&gt;

&lt;p&gt;Same as CDK but without the synth step — you already have a template file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Generate CloudFormation changeset&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;CHANGESET_NAME="blast-radius-$(date +%s)"&lt;/span&gt;
    &lt;span class="s"&gt;aws cloudformation create-change-set \&lt;/span&gt;
      &lt;span class="s"&gt;--stack-name MyStack \&lt;/span&gt;
      &lt;span class="s"&gt;--change-set-name $CHANGESET_NAME \&lt;/span&gt;
      &lt;span class="s"&gt;--template-body file://template.json \&lt;/span&gt;
      &lt;span class="s"&gt;--capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM \&lt;/span&gt;
      &lt;span class="s"&gt;--change-set-type UPDATE \&lt;/span&gt;
      &lt;span class="s"&gt;--output json&lt;/span&gt;
    &lt;span class="s"&gt;aws cloudformation wait change-set-create-complete \&lt;/span&gt;
      &lt;span class="s"&gt;--stack-name MyStack \&lt;/span&gt;
      &lt;span class="s"&gt;--change-set-name $CHANGESET_NAME&lt;/span&gt;
    &lt;span class="s"&gt;aws cloudformation describe-change-set \&lt;/span&gt;
      &lt;span class="s"&gt;--stack-name MyStack \&lt;/span&gt;
      &lt;span class="s"&gt;--change-set-name $CHANGESET_NAME \&lt;/span&gt;
      &lt;span class="s"&gt;--output json &amp;gt; changeset.json&lt;/span&gt;
    &lt;span class="s"&gt;aws cloudformation delete-change-set \&lt;/span&gt;
      &lt;span class="s"&gt;--stack-name MyStack \&lt;/span&gt;
      &lt;span class="s"&gt;--change-set-name $CHANGESET_NAME&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Blast Radius Analysis&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sburgholzer/BlastRadius@v0.1.0&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cloudformation&lt;/span&gt;
    &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;changeset.json&lt;/span&gt;
    &lt;span class="na"&gt;threshold&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;75&lt;/span&gt;
    &lt;span class="na"&gt;ai-gate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;api-url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.BLAST_RADIUS_API_URL }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is essentially the same as the CDK Workflow, except we don't need to run a synth.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the GitHub Action Does Under the Hood
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Downloads the bundled CLI from the GitHub release&lt;/li&gt;
&lt;li&gt;Runs &lt;code&gt;analyze&lt;/code&gt; with &lt;code&gt;--ci&lt;/code&gt; flag for JSON output&lt;/li&gt;
&lt;li&gt;Parses the JSON into GitHub Action outputs&lt;/li&gt;
&lt;li&gt;Comments on the PR with a formatted table + AI summary (disable with &lt;code&gt;comment-pr: false&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Action inputs:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Input&lt;/th&gt;
&lt;th&gt;Required&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;format&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Input format: cloudformation / terraform-plan / canonical&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;input&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Path to the input file (changeset JSON / terraform plan JSON / canonical manifest)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;api-url&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Blast Radius API URL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;threshold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Risk score threshold (0-100). Fails if any resource exceeds this.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ai-gate&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;Fail if AI recommends against deployment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;no-summary&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;Skip AI summary generation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;comment-pr&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;td&gt;Automatically comment results on the PR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;version&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;latest&lt;/td&gt;
&lt;td&gt;CLI version to use&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Action outputs:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Output&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;analysis-id&lt;/td&gt;
&lt;td&gt;The analysis ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;verdict&lt;/td&gt;
&lt;td&gt;pass or fail&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;highest-score&lt;/td&gt;
&lt;td&gt;Highest impact score (0-100)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;total-affected&lt;/td&gt;
&lt;td&gt;Total affected resources&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;recommend-deploy&lt;/td&gt;
&lt;td&gt;AI deployment recommendation (true/false)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;confidence&lt;/td&gt;
&lt;td&gt;AI recommendation confidence (high/medium/low)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;summary&lt;/td&gt;
&lt;td&gt;AI-generated risk summary (markdown)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;result-json&lt;/td&gt;
&lt;td&gt;Full JSON result (for custom downstream steps)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  CLI in Any CI System
&lt;/h2&gt;

&lt;p&gt;The GitHub Action is a convenience wrapper. Underneath, it's the CLI. If you're on GitLab, Jenkins, CircleCI, Bitbucket Pipelines, any other tool, or want greater control than the GitHub Action, you can use the CLI directly.&lt;/p&gt;

&lt;p&gt;It's a single bundled JavaScript file. No npm install, no dependencies beyond a Node.js runtime. Download it, run it, read the exit code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to get the CLI:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sL&lt;/span&gt; https://github.com/sburgholzer/BlastRadius/releases/latest/download/blast-radius.js &lt;span class="nt"&gt;-o&lt;/span&gt; blast-radius.js
&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'1{/^#!/d}'&lt;/span&gt; blast-radius.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;sed&lt;/code&gt; command strips the shebang line. Node.js can't parse &lt;code&gt;#!/usr/bin/env node&lt;/code&gt; when you invoke it with &lt;code&gt;node blast-radius.js&lt;/code&gt; directly (needed in CI environments that don't support executing .js files as scripts).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Environment setup:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Set &lt;code&gt;BLAST_RADIUS_API_URL&lt;/code&gt; as an environment variable pointing to your deployed API Gateway URL. That's the only required config. Everything else goes in command flags.&lt;/p&gt;

&lt;h3&gt;
  
  
  GitLab CI example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;blast-radius&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node:20&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;validate&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;curl -sL https://github.com/sburgholzer/BlastRadius/releases/latest/download/blast-radius.js -o blast-radius.js&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;sed -i '1{/^#!/d}' blast-radius.js&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;node blast-radius.js analyze --format terraform-plan --input plan.json --threshold 75 --ai-gate --ci&lt;/span&gt;
  &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;BLAST_RADIUS_API_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$BLAST_RADIUS_URL&lt;/span&gt;
  &lt;span class="na"&gt;artifacts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;blast-radius-result.json&lt;/span&gt;
    &lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note: &lt;code&gt;--ci&lt;/code&gt; outputs JSON to stdout. Capture it with &lt;code&gt;&amp;gt; blast-radius-result.json&lt;/code&gt; and save as an artifact for downstream jobs. Exit code 1 naturally fails the GitLab job, no extra configuration needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Jenkins example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="n"&gt;stage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Blast Radius'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;environment&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;BLAST_RADIUS_API_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;credentials&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'blast-radius-url'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;steps&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;sh&lt;/span&gt; &lt;span class="s1"&gt;'''
      curl -sL https://github.com/sburgholzer/BlastRadius/releases/latest/download/blast-radius.js -o blast-radius.js
      sed -i '1{/^#!/d}' blast-radius.js
      node blast-radius.js analyze --format cloudformation --input changeset.json --threshold 75 --ai-gate --ci
    '''&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Store your API URL in Jenkins credentials and reference it as an environment variable. Non-zero exit fails the stage, standard Jenkins behavior.&lt;/p&gt;

&lt;h3&gt;
  
  
  CircleCI example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Blast Radius Analysis&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;curl -sL https://github.com/sburgholzer/BlastRadius/releases/latest/download/blast-radius.js -o blast-radius.js&lt;/span&gt;
      &lt;span class="s"&gt;sed -i '1{/^#!/d}' blast-radius.js&lt;/span&gt;
      &lt;span class="s"&gt;node blast-radius.js analyze --format terraform-plan --input plan.json --ai-gate --ci&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;BLAST_RADIUS_API_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${BLAST_RADIUS_URL}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Parsing the JSON output
&lt;/h3&gt;

&lt;p&gt;With &lt;code&gt;--ci&lt;/code&gt;, the CLI outputs a JSON object to stdout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"analysisId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"abc-123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"verdict"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fail"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exitCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ai-gate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"recommendDeploy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"confidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"high"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"riskSummary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"highestScore"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;82&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"totalAffected"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"critical"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"high"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"medium"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"low"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"naturalLanguageSummary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"## Executive Overview&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;This deployment presents..."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What you can do with this:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pipe to jq for specific fields: &lt;code&gt;node blast-radius.js analyze ... --ci | jq '.riskSummary.highestScore'&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Send to Slack: extract &lt;code&gt;verdict&lt;/code&gt;, &lt;code&gt;highestScore&lt;/code&gt;, and &lt;code&gt;naturalLanguageSummary&lt;/code&gt; into a webhook payload.&lt;/li&gt;
&lt;li&gt;Build a custom PR comment in GitLab/Bitbucket using the JSON fields.&lt;/li&gt;
&lt;li&gt;Feed into a dashboard or metrics system; track risk scores over time per team or per service.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Exit Codes
&lt;/h2&gt;

&lt;p&gt;Any CI system's native "fail on non-zero" behavior works out of the box. No special error handling needed.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Code&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Pass - threshold OK and AI approves (or gates not enabled)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Fail - threshold exceeded OR AI recommends against deployment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Error - invalid input / pipeline failure / timeout / misconfiguration&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Choosing Your Gate Strategy
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Threshold only (&lt;code&gt;--threshold N&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;This is deterministic. Same input, same result, every time. It's based on the scoring formula from the previous article: depth (how far the resource is from the changed resource), criticality (how critical the resource is to your infrastructure), and change severity (how much the change affects the resource). Each factor is weighted to produce a score between 0 and 100.&lt;/p&gt;

&lt;p&gt;Because it's a formula, there's no AI dependency. If you run Blast Radius without Bedrock, you still get a usable result for your CI/CD pipeline.&lt;/p&gt;

&lt;p&gt;This gate is good for compliance-driven teams, environments where you need reproducible gates, or if you want to understand how the scoring system works before enabling AI.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;N&lt;/code&gt; is your cutoff: if the highest score of any resource exceeds that value, Blast Radius fails your pipeline. Here's a general guideline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;90:&lt;/strong&gt; very permissive. Only blocks Critical-category changes. Good for dev/staging environments.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;75:&lt;/strong&gt; moderate. Blocks Critical and most High-category hits. Good default for production.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;50:&lt;/strong&gt; conservative. Blocks anything above Medium. Good for sensitive workloads.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Start at 75 to see how it works in your environment, then fine-tune from there.&lt;/p&gt;

&lt;h3&gt;
  
  
  AI gate only (&lt;code&gt;--ai-gate&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;This is where judgment-based thinking comes into play. The AI gate looks at the full graph, not just the individual scores. It catches patterns like shared dependencies, fan-out risk, and cascading failure paths. It will return &lt;code&gt;recommendDeploy: true/false&lt;/code&gt; with &lt;code&gt;confidence: high/medium/low&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is good for teams that trust AI tooling or want to experiment with it to catch systemic risks. If you use this gate, I recommend reading through the summary and doing manual investigation to verify the reasoning, and to understand what to fix when it blocks.&lt;/p&gt;

&lt;p&gt;One caveat: &lt;code&gt;--ai-gate&lt;/code&gt; requires Bedrock enabled on the backend. If Bedrock is disabled, the CLI exits with error code 2.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using Both Gates Together
&lt;/h3&gt;

&lt;p&gt;This is my recommended configuration. When you use both gates, Blast Radius fails your pipeline if EITHER triggers. The threshold catches obvious high-scoring resources. The AI catches non-obvious patterns below your threshold value.&lt;/p&gt;

&lt;h3&gt;
  
  
  Running Without AI Summary
&lt;/h3&gt;

&lt;p&gt;If you run Blast Radius with &lt;code&gt;--no-summary&lt;/code&gt;, it skips the AI-generated natural language summary but will still run the analysis. This saves a few seconds if you only care about the score.&lt;/p&gt;

&lt;p&gt;Note: &lt;code&gt;--ai-gate&lt;/code&gt; overrides &lt;code&gt;--no-summary&lt;/code&gt; because the gate needs the AI to run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading the Output
&lt;/h2&gt;

&lt;p&gt;You've set up the gate. It ran. Now you're looking at a PR comment (or JSON output) with scores, dependency chains, and an AI recommendation. Here's how to read it all.&lt;/p&gt;

&lt;h3&gt;
  
  
  The PR Comment Anatomy
&lt;/h3&gt;

&lt;p&gt;The GitHub Action comments on your PR with two parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Summary table:&lt;/strong&gt; four key metrics at a glance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Highest Score: the single scariest number in the analysis (0-100)&lt;/li&gt;
&lt;li&gt;Affected Resources: total number of downstream resources discovered&lt;/li&gt;
&lt;li&gt;AI Recommendation: Deploy ✅ or Do Not Deploy ❌&lt;/li&gt;
&lt;li&gt;Confidence: how sure the AI is (high/medium/low)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;AI-generated summary&lt;/strong&gt; (if Bedrock is enabled): structured markdown with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Executive overview: one-paragraph assessment&lt;/li&gt;
&lt;li&gt;Key findings: the specific resources and patterns that drove the recommendation&lt;/li&gt;
&lt;li&gt;Cascading risks: what could go wrong if you deploy anyway&lt;/li&gt;
&lt;li&gt;Recommendation: the final call with reasoning&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you're using the CLI with &lt;code&gt;--ci&lt;/code&gt;, you get the same information as JSON fields: &lt;code&gt;riskSummary&lt;/code&gt;, &lt;code&gt;recommendDeploy&lt;/code&gt;, &lt;code&gt;confidence&lt;/code&gt;, and &lt;code&gt;naturalLanguageSummary&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Risk Scores — What the Numbers Mean
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Score&lt;/th&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;75-100&lt;/td&gt;
&lt;td&gt;Critical (red)&lt;/td&gt;
&lt;td&gt;Direct or near-direct impact on high-criticality resources (databases, auth systems, core networking). Stop and think.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;50-74&lt;/td&gt;
&lt;td&gt;High (orange)&lt;/td&gt;
&lt;td&gt;Significant downstream impact. Review the dependency chain before deploying.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;25-49&lt;/td&gt;
&lt;td&gt;Medium (yellow)&lt;/td&gt;
&lt;td&gt;Worth noting. Probably fine, but look at what's at the end of the chain.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0-24&lt;/td&gt;
&lt;td&gt;Low (green)&lt;/td&gt;
&lt;td&gt;Far away, low criticality, or a non-destructive change type. Safe to proceed.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  How the Score is Calculated (review)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;impactScore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;depthScore&lt;/span&gt; &lt;span class="err"&gt;×&lt;/span&gt; &lt;span class="mf"&gt;0.30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;criticalityScore&lt;/span&gt; &lt;span class="err"&gt;×&lt;/span&gt; &lt;span class="mf"&gt;0.40&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;changeTypeSeverity&lt;/span&gt; &lt;span class="err"&gt;×&lt;/span&gt; &lt;span class="mf"&gt;0.30&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Depth (30%)&lt;/strong&gt; — how many hops away. Depth 1 = 100, Depth 5 = 60, Depth 10 = 10.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Criticality (40%)&lt;/strong&gt; — how important the resource type is. Database = 100, Lambda = 75, S3 = 50, Log group = 25.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Change severity (30%)&lt;/strong&gt; — how dangerous the action is. Remove = 100, Replace = 80, Modify = 50, Add = 30.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Criticality is weighted highest because what's affected matters more than how far away it is. Deleting a test log group one hop away is less dangerous than modifying something three hops from a production database.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dependency Chains — the Breadcrumb Trail
&lt;/h3&gt;

&lt;p&gt;Every scored resource includes a &lt;code&gt;dependencyChain&lt;/code&gt;: the ordered path showing exactly how risk flows from your change to that resource.&lt;/p&gt;

&lt;p&gt;Example: &lt;code&gt;sg-abc123 → ec2-instance-1 → rds-prod&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Read left to right: your changed resource → intermediate resources → the thing at risk.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Longer chains mean lower depth scores (the resource is further away).&lt;/li&gt;
&lt;li&gt;But a long chain ending at a Critical-class resource (like a database) can still score high because criticality carries 40% of the weight.&lt;/li&gt;
&lt;li&gt;Short chains ending at low-criticality resources score low even though they're close.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the frontend graph view, the dependency chain is the highlighted path when you click a node. In the PR comment, it appears in the detail expansion for each resource.&lt;/p&gt;

&lt;h3&gt;
  
  
  AI Confidence Levels
&lt;/h3&gt;

&lt;p&gt;When using the AI gate, the model returns a confidence alongside its recommendation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;High:&lt;/strong&gt; clear signal. The graph strongly supports the recommendation. Trust it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Medium:&lt;/strong&gt; mixed signals. Some risk factors present, some mitigating. Worth human review before overriding.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Low:&lt;/strong&gt; AI isn't sure. Limited data, ambiguous patterns, or a very small graph. Don't gate on this alone — treat it as advisory.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When the AI says "don't deploy" but scores are below threshold:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the AI gate earning its keep. It's seeing a pattern that individual scores don't capture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fan-out risk: many resources all depending on one thing you're changing.&lt;/li&gt;
&lt;li&gt;Single points of failure: a resource that's the only path between your change and critical infrastructure.&lt;/li&gt;
&lt;li&gt;Cascading depth: the graph is deep and converges on something important at the far end.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check the AI summary for the specific reasoning. It will explain what pattern it detected and why it recommends against deployment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When the AI says "deploy" but you're nervous:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The AI can be wrong. If your gut says something's off:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Look at the dependency chain for the highest-scored resource. Does the path make sense?&lt;/li&gt;
&lt;li&gt;Check whether the graph is missing resources (AWS Config relationships aren't always complete).&lt;/li&gt;
&lt;li&gt;Use the interactive frontend to explore the full graph visually.&lt;/li&gt;
&lt;li&gt;When in doubt, deploy to a non-production stage first.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Local Development and Testing
&lt;/h2&gt;

&lt;p&gt;You don't have to go straight to CI/CD pipeline. The CLI has commands for generating, inspecting, and exploring results locally. The interactive frontend also gives you a visual way to understand your infrastructure's dependency landscape.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;blast-radius generate&lt;/code&gt; — Create Input Files without Submitting
&lt;/h3&gt;

&lt;p&gt;The generate command produces the same input file that &lt;code&gt;analyze&lt;/code&gt; would create and submit, but stops there. No analysis is run, no API is called.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# CDK: synthesizes and creates a changeset, saves to disk&lt;/span&gt;
blast-radius generate &lt;span class="nt"&gt;--format&lt;/span&gt; cdk &lt;span class="nt"&gt;--stack&lt;/span&gt; MyStack &lt;span class="nt"&gt;--output&lt;/span&gt; changeset.json

&lt;span class="c"&gt;# Terraform: runs terraform plan + show, saves the JSON&lt;/span&gt;
blast-radius generate &lt;span class="nt"&gt;--format&lt;/span&gt; terraform-plan &lt;span class="nt"&gt;--output&lt;/span&gt; plan.json

&lt;span class="c"&gt;# CloudFormation: creates changeset from template, saves it&lt;/span&gt;
blast-radius generate &lt;span class="nt"&gt;--format&lt;/span&gt; cloudformation &lt;span class="nt"&gt;--stack&lt;/span&gt; MyStack &lt;span class="nt"&gt;--template&lt;/span&gt; cfn.json &lt;span class="nt"&gt;--output&lt;/span&gt; changeset.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why this is useful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Verify your CI workflow produces valid input before enabling the gate&lt;/li&gt;
&lt;li&gt;Inspect exactly what Blast Radius will see — review the changeset JSON to understand the resources and change types&lt;/li&gt;
&lt;li&gt;Build a library of test inputs for different scenarios&lt;/li&gt;
&lt;li&gt;Share with teammates: "here's what the analysis would run against"&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;--save&lt;/code&gt; flag — save AND submit
&lt;/h3&gt;

&lt;p&gt;If you want to both inspect the input and run the analysis in one shot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;blast-radius analyze &lt;span class="nt"&gt;--format&lt;/span&gt; cdk &lt;span class="nt"&gt;--stack&lt;/span&gt; MyStack &lt;span class="nt"&gt;--save&lt;/span&gt; changeset.json &lt;span class="nt"&gt;--ai-gate&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This saves the generated input to disk, then submits it. When a result comes back surprising ("why did my security group change score 82?"), you can open &lt;code&gt;changeset.json&lt;/code&gt; and see exactly what was sent: which resources, which change types, which properties.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Interactive Frontend
&lt;/h3&gt;

&lt;p&gt;After deploying the backend, the CloudFront URL serves a React SPA where you can explore results visually.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Analysis list&lt;/strong&gt; (&lt;code&gt;/analyses&lt;/code&gt;): shows all past analyses sorted by date. Click any row to drill in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependency graph:&lt;/strong&gt; Cytoscape.js interactive graph. Your changed resources appear as solid blue nodes. Downstream dependencies fan out, colored by risk category (red/orange/yellow/green). Click any node for a detail panel: resource ID, type, score, risk category, and full dependency chain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filters:&lt;/strong&gt; filter by risk category, resource type, or toggle direct changes on/off. Filters apply as intersection (resource must match all active filters).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Table view:&lt;/strong&gt; sortable alternative to the graph. Sorted by impact score (highest first) by default. Paginated at 50 rows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Export:&lt;/strong&gt; download the full analysis as JSON for archiving or feeding into other tools.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI Summary:&lt;/strong&gt; rendered as formatted markdown below the graph (if Bedrock was enabled for the analysis).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The frontend is useful for exploring results visually after CI runs, understanding dependency patterns in your account before they cause problems, demoing Blast Radius to stakeholders, and investigating why the AI gate blocked a deployment.&lt;/p&gt;

&lt;h3&gt;
  
  
  Running against the demo examples
&lt;/h3&gt;

&lt;p&gt;The repo includes &lt;code&gt;examples/cdk-demo/&lt;/code&gt; with a two-step demo that doesn't require your real production infrastructure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;01-baseline/&lt;/code&gt;: deploys a realistic infrastructure: VPC, ECS Fargate service, Aurora PostgreSQL cluster, ALB, Lambda function, S3 bucket.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;02-risky-change/&lt;/code&gt;: modifies the security group (restricts from public to internal-only) and resizes the database instance.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Deploy baseline&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;examples/cdk-demo/01-baseline
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npx cdk deploy

&lt;span class="c"&gt;# Run Blast Radius against the risky change&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; ../02-risky-change
npm &lt;span class="nb"&gt;install
&lt;/span&gt;&lt;span class="nv"&gt;BLAST_RADIUS_API_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;your-url&amp;gt; blast-radius analyze &lt;span class="nt"&gt;--format&lt;/span&gt; cdk &lt;span class="nt"&gt;--stack&lt;/span&gt; BlastRadiusDemoBaseline &lt;span class="nt"&gt;--ai-gate&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This produces a real analysis showing 7 affected resources cascading from the security group and database changes, ECS service, Lambda, ingress/egress rules, scored and visualized.&lt;/p&gt;

&lt;p&gt;Cost note: The demo creates real AWS resources (~$0.45/hr). Run &lt;code&gt;cdk destroy&lt;/code&gt; when you're done. Don't do what I did and forget to run the destroy!&lt;/p&gt;

&lt;h3&gt;
  
  
  Other useful commands
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check status of a running analysis&lt;/span&gt;
blast-radius status &lt;span class="nt"&gt;--analysis-id&lt;/span&gt; abc-123

&lt;span class="c"&gt;# Fetch completed results&lt;/span&gt;
blast-radius &lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nt"&gt;--analysis-id&lt;/span&gt; abc-123 &lt;span class="nt"&gt;--format&lt;/span&gt; json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;You now have a working deployment gate. Every infrastructure PR gets analyzed for downstream risk before it can merge. Whether you're using the GitHub Action, the CLI in GitLab, or exploring results in the frontend, the workflow is the same: generate, analyze, gate, decide.&lt;/p&gt;

&lt;p&gt;Next in this series, we go under the hood:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Article 3: Serverless IaC Risk Analysis — The Architecture Behind Blast Radius:&lt;/strong&gt; How the pipeline is designed. Why Step Functions. How the canonical format abstraction makes multi-IaC support possible without touching the analysis engine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Article 4: 349 Tests, Zero Mocks — Building Blast Radius in TypeScript:&lt;/strong&gt; Engineering decisions, property-based testing with fast-check, dependency injection over module mocking, and the Lambda runtime gotchas that wasted a day.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These articles are coming in the next few weeks. I'll update this post with links as each one publishes.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>devops</category>
      <category>cicd</category>
      <category>infrastructureascode</category>
    </item>
    <item>
      <title>Stop Taking Down Production Systems. Use "Blast Radius" To Help You.</title>
      <dc:creator>Scott Burgholzer</dc:creator>
      <pubDate>Sun, 28 Jun 2026 19:59:06 +0000</pubDate>
      <link>https://dev.to/aws-builders/terraform-plan-shows-what-youre-changing-blast-radius-shows-what-youre-breaking-3324</link>
      <guid>https://dev.to/aws-builders/terraform-plan-shows-what-youre-changing-blast-radius-shows-what-youre-breaking-3324</guid>
      <description>&lt;p&gt;&lt;em&gt;Series: Introducing Blast Radius — See What Breaks Before You Deploy&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  “It Was Just a Security Group Change”
&lt;/h2&gt;

&lt;p&gt;A developer modifies an ingress rule on a security group. The pull request looks clean, and &lt;code&gt;cdk diff&lt;/code&gt; shows that only one resource is changing. The PR is approved, merged, and deployed.&lt;/p&gt;

&lt;p&gt;Then the alerts start flowing in. Ten EC2 instances lose connectivity. Three RDS databases become unreachable. The production app goes down.&lt;/p&gt;

&lt;p&gt;The change was correct in isolation. The problem was everything downstream of that change, and nothing in the IaC diff revealed it.&lt;/p&gt;

&lt;p&gt;This isn’t a fictional horror story. It is a category of incident that happens because IaC tools are blind to runtime dependency relationships.&lt;/p&gt;

&lt;h2&gt;
  
  
  Terraform Plan Shows Your Change — Not Its Consequences.
&lt;/h2&gt;

&lt;p&gt;IaC tools tell us what we are changing, but they don’t tell us what else is affected and will break.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;terraform plan&lt;/code&gt; shows 1 resource modified. In reality, 28 downstream resources are affected.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cdk diff&lt;/code&gt; shows a property update. In reality, there is a cascading dependency chain through Config relationships.&lt;/p&gt;

&lt;p&gt;CloudFormation change sets list actions on declared resources. Undeclared runtime dependencies are invisible.&lt;/p&gt;

&lt;p&gt;Manual review doesn’t scale. You’d need to trace relationships across AWS Config, Resource Explorer, and tribal knowledge, and still probably miss something. The other option is to deploy to a dev environment and see what breaks.&lt;/p&gt;

&lt;p&gt;The information exists in AWS. Config tracks relationships. Resource Explorer indexes resources. It’s just not connected to your deployment workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  See the Blast Radius Before You Deploy.
&lt;/h2&gt;

&lt;p&gt;Blast Radius connects the data AWS already has to your deployment workflow. It discovers downstream dependencies from your infrastructure change, scores each one for risk, and optionally asks AI whether you should deploy.&lt;/p&gt;

&lt;p&gt;It works with CDK, CloudFormation, and Terraform out of the box. You don’t need to change your IaC tool.&lt;/p&gt;

&lt;p&gt;The dependency graph makes the cascade visible: your changed resource highlighted in blue, everything downstream fanning out and color-coded by risk score. Click any node for details. There’s a table view too.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv5tzucbh8umcdja0ziyh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv5tzucbh8umcdja0ziyh.png" alt="Frontend Dependency Graph Example" width="800" height="644"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Frontend Dependency Graph Example&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The score isn’t a mystery: &lt;code&gt;(depth × 0.30) + (criticality × 0.40) + (change severity × 0.30)&lt;/code&gt;. It is explainable, auditable, and tunable.&lt;/p&gt;

&lt;p&gt;The output lands in your PR as a comment (GitHub Action), in your terminal (CLI), or in an interactive graph (frontend).&lt;/p&gt;
&lt;h2&gt;
  
  
  From IaC Diff to Risk Score in 30 Seconds
&lt;/h2&gt;

&lt;p&gt;You push a PR with an infrastructure change.&lt;/p&gt;

&lt;p&gt;Blast Radius auto-generates the changeset (runs &lt;code&gt;cdk synth&lt;/code&gt;, &lt;code&gt;terraform plan&lt;/code&gt;, or reads your CloudFormation change set).&lt;/p&gt;

&lt;p&gt;An adapter normalizes the changeset into a single canonical format. CDK, CloudFormation, and Terraform all describe the same change in different ways. The canonical format unifies them.&lt;/p&gt;

&lt;p&gt;AWS Config queries discover real, live dependency relationships. Not what’s in the IaC, what’s actually wired together in your account.&lt;/p&gt;

&lt;p&gt;Each affected resource is scored for how far away it is (depth), how important it is (criticality), and how dangerous the change type is (severity).&lt;/p&gt;

&lt;p&gt;Results: a risk summary, scored resources, dependency graph, and optional AI-generated deployment recommendation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo2yu5bt1cux7tdf5u62k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo2yu5bt1cux7tdf5u62k.png" alt="Flow Diagram of how Blast Radius Works" width="800" height="211"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Flow Diagram of how Blast Radius Works&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Total wall time: 10–30 seconds, depending on graph size.&lt;/p&gt;
&lt;h2&gt;
  
  
  Two Gates: Numbers and Judgment
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Threshold gate (&lt;code&gt;--threshold 75&lt;/code&gt;):&lt;/em&gt; If any downstream resource scores above your number, the pipeline fails. Deterministic. No AI required. Good for teams that want a hard cutoff.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;AI gate (&lt;code&gt;--ai-gate&lt;/code&gt;):&lt;/em&gt; An AI model analyzes the full dependency graph and returns a deploy/no-deploy recommendation with confidence. Catches systemic risks that individually score below the threshold but are dangerous collectively.&lt;/p&gt;

&lt;p&gt;You modify a security group attached to a shared RDS Proxy. Eight Lambda functions score 62. Two ECS services score 68. All below your threshold of 75. But every one of them routes database traffic through that proxy. Take it down and you’ve killed every service that touches your database. The AI sees the shared dependency. The threshold just sees ten numbers under 75.&lt;/p&gt;

&lt;p&gt;You can use either of them or both together. If using both, it fails if EITHER triggers. This gives you defense in depth.&lt;/p&gt;

&lt;p&gt;Exit codes are CI-friendly: 0 = pass, 1 = fail, 2 = error&lt;/p&gt;
&lt;h2&gt;
  
  
  Add It to Your Pipeline in Five Minutes
&lt;/h2&gt;

&lt;p&gt;First, deploy the backend to your own AWS account. One stack, one command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/sburgholzer/BlastRadius.git &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;BlastRadius
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm run build &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;packages/infra &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; cdk deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One requirement before you deploy: AWS Config and Resource Explorer must be enabled in your account. That’s where Blast Radius reads live dependency relationships.&lt;/p&gt;

&lt;p&gt;Then add it to your pipeline. The fastest path is the GitHub Action — it auto-comments on your PR with the full analysis:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sburgholzer/BlastRadius@v0.1.0&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform-plan&lt;/span&gt;
    &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;plan.json&lt;/span&gt;
    &lt;span class="na"&gt;ai-gate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;api-url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.BLAST_RADIUS_URL }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For CDK, you’ll need to generate a CloudFormation changeset first, then pass it to the action. The full setup is in Article 2.&lt;/p&gt;

&lt;p&gt;Or skip the GitHub Action entirely and run it from the CLI in any CI system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sL&lt;/span&gt; https://github.com/sburgholzer/BlastRadius/releases/latest/download/blast-radius.js &lt;span class="nt"&gt;-o&lt;/span&gt; blast-radius.js
node blast-radius.js analyze &lt;span class="nt"&gt;--format&lt;/span&gt; cdk &lt;span class="nt"&gt;--stack&lt;/span&gt; MyStack &lt;span class="nt"&gt;--ai-gate&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Blast Radius is open source. &lt;a href="https://github.com/sburgholzer/BlastRadius" rel="noopener noreferrer"&gt;You can find the repo here.&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing Thoughts
&lt;/h2&gt;

&lt;p&gt;Infrastructure changes will always carry risk. The goal isn’t to eliminate that risk; it’s to see it clearly before you deploy. Blast Radius gives you that visibility in the time it takes to review a PR.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next In This Series
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Article 2: Stop Deploying Blind: Add a Blast Radius Gate to Your CI/CD Pipeline&lt;/strong&gt;: Step-by-step setup for GitHub Actions, GitLab, and Jenkins. Choosing between threshold and AI gates, tuning your cutoff, and reading the output like a pro.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Article 3: Serverless IaC Risk Analysis: The Architecture Behind Blast Radius&lt;/strong&gt;: How the pipeline is designed. Why Step Functions. How the canonical format abstraction makes multi-IaC support possible without touching the analysis engine.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Article 4: 349 Tests, Zero Mocks: Building Blast Radius in TypeScript&lt;/strong&gt;: Engineering decisions, property-based testing with fast-check, dependency injection over module mocking, and the Lambda runtime gotchas that wasted a day.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;These articles are coming in the next few weeks. I’ll update this post with links as each one publishes.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Title help from Aaron Hunter!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>infrastructureascode</category>
      <category>cicd</category>
      <category>devops</category>
    </item>
    <item>
      <title>The InvalidCiphertextException Mystery: Decrypting Cognito's Encrypted OTP Codes</title>
      <dc:creator>Scott Burgholzer</dc:creator>
      <pubDate>Thu, 01 Jan 2026 05:05:01 +0000</pubDate>
      <link>https://dev.to/aws-builders/the-invalidciphertextexception-mystery-decrypting-cognitos-encrypted-otp-codes-jg8</link>
      <guid>https://dev.to/aws-builders/the-invalidciphertextexception-mystery-decrypting-cognitos-encrypted-otp-codes-jg8</guid>
      <description>&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;You're building a custom email sender for Cognito because the default emails are pretty basic. In this case you want branded HTML emails for a One Time Password code that actually explain what the code is for and when it expires. Sounds straightforward, right?&lt;/p&gt;

&lt;p&gt;Then you hit the encryption wall. Cognito encrypts those OTP codes for security (which is great), but when you try to decrypt them in your Lambda function using standard KMS operations, you get this error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;InvalidCiphertextException: An error occurred (InvalidCiphertextException) when calling the Decrypt operation:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. No helpful details, no explanation of what went wrong. The error message makes it seem like there's something fundamentally wrong with the ciphertext, but even after triple-checking your KMS permissions, it still doesn't work. Here's why, and more importantly, how to fix it.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR - The Solution
&lt;/h2&gt;

&lt;p&gt;Cognito doesn't use regular KMS encryption. It uses the &lt;strong&gt;AWS Encryption SDK&lt;/strong&gt;, which creates a completely different data structure. You need to use the same AWS Encryption SDK (v4 with Material Providers Library) to decrypt the codes. Jump to the working code if you just want the fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Everyone Tries First, Including Myself (And Why It Fails)
&lt;/h2&gt;

&lt;p&gt;When you think AWS encryption, you think KMS. So naturally, you try the standard approaches:&lt;/p&gt;

&lt;h3&gt;
  
  
  Attempt 1: Basic KMS Decryption
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# This seems logical but doesn't work
&lt;/span&gt;&lt;span class="n"&gt;kms_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;kms&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;decrypt_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;kms_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CiphertextBlob&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;encrypted_blob&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; &lt;code&gt;InvalidCiphertextException&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Attempt 2: Adding Encryption Context
&lt;/h3&gt;

&lt;p&gt;Maybe it needs the user pool ID as context?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Still doesn't work
&lt;/span&gt;&lt;span class="n"&gt;decrypt_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;kms_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;CiphertextBlob&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;encrypted_blob&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;EncryptionContext&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;userpool-id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;userPoolId&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; Same error&lt;/p&gt;

&lt;h3&gt;
  
  
  Attempt 3: AWS-Prefixed Context
&lt;/h3&gt;

&lt;p&gt;Perhaps it needs the AWS service prefix?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Nope, still fails
&lt;/span&gt;&lt;span class="n"&gt;decrypt_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;kms_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;CiphertextBlob&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;encrypted_blob&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;EncryptionContext&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aws:cognito:userpool-id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;userPoolId&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; Still the same error&lt;/p&gt;

&lt;p&gt;At this point, you start questioning everything. Your KMS permissions look right, the key exists, but nothing works.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Issue: It's Not Actually KMS Ciphertext
&lt;/h2&gt;

&lt;p&gt;Here's the key insight that changes everything: &lt;strong&gt;Cognito doesn't use direct KMS encryption&lt;/strong&gt;. Instead, it uses something called the AWS Encryption SDK, which creates a completely different type of encrypted data.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's the Difference?
&lt;/h3&gt;

&lt;p&gt;Think of it this way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Regular KMS encryption&lt;/strong&gt; = A locked box&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS Encryption SDK&lt;/strong&gt; = A locked box inside a shipping container with labels, tracking info, and handling instructions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you try to use KMS to "unlock" the shipping container, KMS says "I don't know what this is - this isn't a box I locked!"&lt;/p&gt;

&lt;h3&gt;
  
  
  The Technical Details
&lt;/h3&gt;

&lt;p&gt;When Cognito encrypts an OTP code, here's what actually happens:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Cognito uses the AWS Encryption SDK&lt;/strong&gt; (not direct KMS)&lt;/li&gt;
&lt;li&gt;The SDK creates an "envelope" containing:

&lt;ul&gt;
&lt;li&gt;Algorithm information&lt;/li&gt;
&lt;li&gt;An encrypted data key (this part uses KMS)&lt;/li&gt;
&lt;li&gt;The actual encrypted OTP code&lt;/li&gt;
&lt;li&gt;Integrity checks and metadata&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;This entire envelope gets passed to your Lambda function&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The &lt;code&gt;encrypted_blob&lt;/code&gt; you receive isn't simple KMS ciphertext - it's this complex envelope structure. That's why KMS can't decrypt it directly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why the Error Message is Confusing
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;InvalidCiphertextException&lt;/code&gt; error just says:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;An error occurred (InvalidCiphertextException) when calling the Decrypt operation:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. No details, no explanation. This generic message makes you think the ciphertext is corrupted or you have permission issues, but the real problem is that KMS is saying "I can't even parse this data structure." &lt;/p&gt;

&lt;p&gt;It's like trying to open a ZIP file with a text editor - the format is just wrong, but the error message doesn't tell you that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution: AWS Encryption SDK Implementation
&lt;/h2&gt;

&lt;p&gt;Once you understand the problem, the solution becomes clear. You need to use the same AWS Encryption SDK that Cognito uses to create the encrypted data.&lt;/p&gt;

&lt;h3&gt;
  
  
  What You Need
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;AWS Encryption SDK v4&lt;/strong&gt; - The encryption library&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Material Providers Library (MPL)&lt;/strong&gt; - Required for v4&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Proper KMS permissions&lt;/strong&gt; - Your Lambda still needs to access the KMS key&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The right Python packages&lt;/strong&gt; - Install them as a Lambda layer&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The Working Code
&lt;/h3&gt;

&lt;p&gt;Here's the code that actually works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;aws_encryption_sdk&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;aws_encryption_sdk&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CommitmentPolicy&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;aws_cryptographic_material_providers.mpl&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AwsCryptographicMaterialProviders&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;aws_cryptographic_material_providers.mpl.config&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MaterialProvidersConfig&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;aws_cryptographic_material_providers.mpl.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CreateAwsKmsKeyringInput&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;decrypt_cognito_code&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encrypted_blob&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Set up the encryption client
&lt;/span&gt;    &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aws_encryption_sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;EncryptionSDKClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;commitment_policy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;CommitmentPolicy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;REQUIRE_ENCRYPT_ALLOW_DECRYPT&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Create the material providers
&lt;/span&gt;    &lt;span class="n"&gt;mat_prov&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AwsCryptographicMaterialProviders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;MaterialProvidersConfig&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Set up the KMS keyring
&lt;/span&gt;    &lt;span class="n"&gt;kms_key_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;KMS_KEY_ID&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;keyring_input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CreateAwsKmsKeyringInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;kms_key_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;kms_key_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;kms_client&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;kms&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;kms_keyring&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mat_prov&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_aws_kms_keyring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;keyring_input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Now this actually works!
&lt;/span&gt;    &lt;span class="n"&gt;plaintext_bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;decryption_header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;encrypted_blob&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;keyring&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;kms_keyring&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Convert to string and return
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;plaintext_bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Setting It Up
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Install the packages:&lt;/strong&gt;&lt;br&gt;
Create a Lambda layer with these dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="s2"&gt;"aws-encryption-sdk[MPL]"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;4.0.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Set your environment variable:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;KMS_KEY_ID=your-kms-key-id-here
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Update your IAM role:&lt;/strong&gt;&lt;br&gt;
Your Lambda execution role needs &lt;code&gt;kms:Decrypt&lt;/code&gt; permission for your KMS key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"kms:Decrypt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:kms:region:account:key/your-key-id"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why This Matters Beyond Cognito
&lt;/h2&gt;

&lt;p&gt;This isn't just a Cognito quirk. Other AWS services also use the AWS Encryption SDK instead of direct KMS encryption:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Systems Manager Parameter Store&lt;/strong&gt; (SecureString parameters)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Some S3 client-side encryption&lt;/strong&gt; scenarios&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom applications&lt;/strong&gt; that need to encrypt large amounts of data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The pattern is becoming more common because the AWS Encryption SDK offers benefits that direct KMS doesn't:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No size limits&lt;/strong&gt; (KMS is limited to 4KB)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Better performance&lt;/strong&gt; for large data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Additional security features&lt;/strong&gt; like key commitment&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Key Takeaway
&lt;/h3&gt;

&lt;p&gt;When you see an AWS service that says it "uses KMS encryption," don't assume it's using direct KMS calls. It might be using the AWS Encryption SDK under the hood, which changes everything about how you decrypt the data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting Tips
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;If you're still getting errors:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Check your Lambda layer&lt;/strong&gt; - Make sure you have the right packages installed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify your KMS key ID&lt;/strong&gt; - It should be the same one configured in your Cognito user pool&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test your IAM permissions&lt;/strong&gt; - Try a simple KMS decrypt operation to verify access&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check the commitment policy&lt;/strong&gt; - Use &lt;code&gt;REQUIRE_ENCRYPT_ALLOW_DECRYPT&lt;/code&gt; for compatibility&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Common mistakes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Using the wrong version of the AWS Encryption SDK (you need v4)&lt;/li&gt;
&lt;li&gt;Forgetting to install the Material Providers Library&lt;/li&gt;
&lt;li&gt;Using the wrong KMS key (it must match what Cognito is configured to use)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Error message reference:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;InvalidCiphertextException&lt;/code&gt; with no details = You're trying to decrypt AWS Encryption SDK data with direct KMS&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;IncorrectKeyException&lt;/code&gt; = You're using the wrong KMS key for real KMS ciphertext&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AccessDeniedException&lt;/code&gt; = Actual permissions problem&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;This problem can stump a lot of developers because the error message is so generic and unhelpful. When you see &lt;code&gt;InvalidCiphertextException&lt;/code&gt; with no details, it's usually a sign that you're trying to decrypt AWS Encryption SDK data with direct KMS calls.&lt;/p&gt;

&lt;p&gt;Once you understand that Cognito uses the AWS Encryption SDK instead of direct KMS, everything clicks into place. The extra complexity is worth it - you get better security, no size limits, and future-proof encryption. Plus, now you know how to handle this pattern when you encounter it in other AWS services.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-custom-sender-triggers.html" rel="noopener noreferrer"&gt;AWS Cognito Custom Sender Lambda Triggers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/" rel="noopener noreferrer"&gt;AWS Encryption SDK Developer Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/python-example-code.html" rel="noopener noreferrer"&gt;AWS Encryption SDK Python Examples&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>encryption</category>
      <category>lambda</category>
      <category>kms</category>
    </item>
    <item>
      <title>Understanding Amazon CloudFront's New Flat-Rate Pricing</title>
      <dc:creator>Scott Burgholzer</dc:creator>
      <pubDate>Thu, 27 Nov 2025 00:45:35 +0000</pubDate>
      <link>https://dev.to/aws-builders/understanding-amazon-cloudfronts-new-flat-rate-pricing-e9k</link>
      <guid>https://dev.to/aws-builders/understanding-amazon-cloudfronts-new-flat-rate-pricing-e9k</guid>
      <description>&lt;p&gt;On November 18th, AWS introduced new flat‑rate pricing plans for Amazon CloudFront designed to make content delivery and security costs more predictable for teams of all sizes. These plans sit alongside the existing pay‑as‑you‑go model and bundle multiple services into a single monthly price per distribution.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Flat-Rate Pricing&amp;nbsp;Matters
&lt;/h3&gt;

&lt;p&gt;Traditionally, CloudFront has used pay‑as‑you‑go pricing, which is great for starting at $0, scaling with actual usage, and only paying for what you consume. The tradeoff is that estimating costs can be difficult, especially when you also depend on AWS WAF, DDoS protection, Route 53, CloudWatch Logs, and S3 for a single application. You end up stitching together multiple pricing pages and trying to map them to your traffic patterns just to get a reasonable forecast of your monthly bill.&lt;/p&gt;

&lt;p&gt;The new flat‑rate plans aim to simplify this. Instead of tracking every request, rule, and log line, you choose a plan tier per distribution and pay a fixed monthly fee that includes a defined bundle of features and usage allowances.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's Included in the New&amp;nbsp;Plans
&lt;/h3&gt;

&lt;p&gt;Each flat‑rate plan bundles the following into one monthly price per CloudFront distribution:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Amazon CloudFront CDN&lt;/li&gt;
&lt;li&gt;AWS WAF and DDoS protection&lt;/li&gt;
&lt;li&gt;Bot management and analytics&lt;/li&gt;
&lt;li&gt;Amazon Route 53 DNS&lt;/li&gt;
&lt;li&gt;Amazon CloudWatch Logs ingestion&lt;/li&gt;
&lt;li&gt;Serverless edge compute (via CloudFront Functions)&lt;/li&gt;
&lt;li&gt;Monthly Amazon S3 storage credits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because everything is packaged together, you get a predictable bill for that distribution with no overage charges. There are no surprises at the end of the month.&lt;/p&gt;

&lt;p&gt;A few structural details to keep in mind:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Plans are per distribution. If you put two distributions on the Business plan at $200 each, you will pay $400 per month for those two.&lt;/li&gt;
&lt;li&gt;You can upgrade at any time, but downgrades/cancellations or a move back to pay‑as‑you‑go only take effect at the start of the next billing cycle.&lt;/li&gt;
&lt;li&gt;There is a limit of three free plans per AWS account and a limit of 100 total plans per account, which matters if you run many environments or tenants.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh2xx8sfav7ijcbwv7tzt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh2xx8sfav7ijcbwv7tzt.png" alt="CloudFront flat-rate plans" width="800" height="599"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Handling Attacks and Traffic&amp;nbsp;Spikes
&lt;/h3&gt;

&lt;p&gt;One of the more reassuring aspects of these plans is how they treat malicious traffic. DDoS attacks do not count against your usage allowance, and traffic blocked by AWS WAF or bot protections also does not consume your plan quota. That means an attack should not cause you to exceed your allowance.&lt;/p&gt;

&lt;p&gt;You will receive email notifications when you hit 50%, 80%, and 100% of your usage allowance for the month. If you exceed the allowance, AWS will not tack on overage fees, but you may experience reduced performance. That performance reduction is at AWS's discretion and depends on overall network conditions and other factors, so consistently bumping into your limits is a good signal that it is time to move to a higher‑tier plan.&lt;/p&gt;

&lt;h3&gt;
  
  
  Edge Compute: CloudFront Functions vs. Lambda@Edge
&lt;/h3&gt;

&lt;p&gt;All of the flat‑rate plans include CloudFront Functions, which are ideal for lightweight JavaScript logic at the edge such as header manipulation, redirects, and simple request or response normalization. For many use cases, this will cover the bulk of what you might previously have used Lambda@Edge for.&lt;/p&gt;

&lt;p&gt;However, Lambda@Edge is not supported on the flat‑rate plans. If your distribution relies on Lambda@Edge, such as more complex request processing, integrations, or heavy transformations, you must keep that distribution on the pay‑as‑you‑go pricing model. This is an important constraint to verify before flipping an existing production distribution over to a flat‑rate plan.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pay-As-You-Go Is Still Available
&lt;/h3&gt;

&lt;p&gt;All of this arrives in addition to, not instead of, the existing pay‑as‑you‑go model. The traditional CloudFront pricing structure remains available with its free tier, which is still a solid option for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Very low‑traffic or experimental workloads&lt;/li&gt;
&lt;li&gt;Architectures that depend on Lambda@Edge or other features not supported in flat‑rate plans&lt;/li&gt;
&lt;li&gt;Teams that prefer pure usage‑based billing and are comfortable modeling and monitoring their costs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In practice, many organizations will likely end up with a mix: flat‑rate plans for high‑volume, business‑critical sites where predictability matters, and pay‑as‑you‑go for specialized or experimental distributions.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloudfront</category>
      <category>cdn</category>
    </item>
    <item>
      <title>What is AWS Device Farm and Using it to Test a Simple Android App</title>
      <dc:creator>Scott Burgholzer</dc:creator>
      <pubDate>Sat, 23 Nov 2024 04:37:15 +0000</pubDate>
      <link>https://dev.to/aws-builders/what-is-aws-device-farm-and-using-it-to-test-a-simple-android-app-ah7</link>
      <guid>https://dev.to/aws-builders/what-is-aws-device-farm-and-using-it-to-test-a-simple-android-app-ah7</guid>
      <description>&lt;h4&gt;
  
  
  Introduction
&lt;/h4&gt;

&lt;p&gt;As I have been busy with my classes for my doctorate and work, I haven’t had much time to write articles like I had hoped. I was hoping to have a project done that I could discuss how I created a Web App using multiple AWS services for others to learn how they all can interact. As that project is still ongoing, I decided to focus on something else.&lt;/p&gt;

&lt;p&gt;I choose AWS Device Farm because eventually I would need to test that web app on mobile devices and on desktop browsers. Device Farm allows us to use physical devices to test our apps. Not only can we test web apps, we can also test Android and iOS apps on physical devices. There is a lot to learn for Device Farm, so I started very basic with using it, and hence this article was born. I hope, while this article just uses a simple app and the defaults, it can expose more people to this AWS service and see if it’ll work in their use cases.&lt;/p&gt;

&lt;p&gt;View this article as a “I’ve never heard or seen AWS Device Farm, so I want to start from the basics and work my way up” as that is exactly what I’m doing with you!&lt;/p&gt;




&lt;h4&gt;
  
  
  What is AWS Device&amp;nbsp;Farm?
&lt;/h4&gt;

&lt;p&gt;AWS Device farm is a cloud-based testing platform that allows developers to test their mobile and web apps on physical devices instead of simulators. There are two main ways of using Device Farm: automated testing of apps and remote access of devices [1].&lt;/p&gt;

&lt;h4&gt;
  
  
  Automated app&amp;nbsp;testing
&lt;/h4&gt;

&lt;p&gt;AWS Device Farm allows developers the ability to upload their own tests or to use built-in and script-free compatibility. AWS Device Farm performs testing in parallel, tests on multiple devices begin in minutes. As these tests are completed, a report is generated that contains high-level results, low-level logs, pixel-to-pixel screenshots and performance data. Device Farm supports the testing of native and hybrid Android and iOS Apps. Some frameworks that work with Device Farm are PhoneGap, Titanium, Xamarin, Unity, and others. You also get remote access of Android and iOS apps for interactive testing.&lt;/p&gt;

&lt;h4&gt;
  
  
  Remote access interaction
&lt;/h4&gt;

&lt;p&gt;This allows you to swipe, gesture and interact with a device through your web browser in real time. An example of where this is beneficial is a customer service rep can guide customers through the use or setup of their device. You have the ability to install apps on a device running in a remote access session and reproduce bugs or other issues. While the session is live, Device Farm will collect details about the actions that are completed on the device. Logs with those details and a video capture of the session are available at the end of the session [1].&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note about regions:&lt;/strong&gt; Per the documentation, Device Farm is only available in the us-west-2 (Oregon) region [1].&lt;/p&gt;




&lt;h4&gt;
  
  
  Walk-through
&lt;/h4&gt;

&lt;p&gt;I’m going to skip over the setting up section. If you wish to go through it in detail (signing up for an account, create or use an IAM user) you may read that &lt;a href="https://docs.aws.amazon.com/devicefarm/latest/developerguide/setting-up.html" rel="noopener noreferrer"&gt;here&lt;/a&gt;. Below is an example IAM policy [1] to allow full access to Device Farm. For this walk-through, this is fine, but if you are going to implement this service, you are going to want to restrict it as much as you can to the least privilege access.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"devicefarm:*"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbuzi74i1y13x4f5rb9ho.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbuzi74i1y13x4f5rb9ho.png" alt="Device Farm Console" width="799" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Device Farm&amp;nbsp;Console&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Create a project&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to Device Farm in the console
&lt;/li&gt;
&lt;li&gt;Expand the left hand navigation if necessary
&lt;/li&gt;
&lt;li&gt;Click on Projects underneath Mobile Device Testing
&lt;/li&gt;
&lt;li&gt;Click on Create mobile project
&lt;/li&gt;
&lt;li&gt;Enter a project name and assign any tags you wish the project to have
&lt;/li&gt;
&lt;li&gt;Click Create&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Create and start a run&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You will need an app in order to start the run. I have created a very simple Android App on my &lt;a href="https://github.com/sburgholzer/AWS-Device-Farm-Examples-for-Blog/blob/main/app-debug.apk" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. Go ahead and download that if you do not have your own Android or Apple App (note in order to get an&amp;nbsp;.ipa file for iOS you will need to have an Apple Developer Account.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Stay on the Automated tests page and click on Create Run  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Device Farm uses the app name as the default run name, feel free to choose your own name! Click Next  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You’ll be taken to a configure page to setup your test framework. You have the ability to choose one of the testing frameworks provided or built-in test suites. Since we are not worrying about our own tests for this example, we can use the Built-In: Fuzz for our case (can keep all the defaults). When you start writing your own test packages, you can choose the corresponding testing framework and upload the file with your tests. This is not applicable in our example. Click Next  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For our example, we will use the Top Devices Device pool. You do have the ability to create your own device pools, but for our simple app, Top Devices is just fine. Click Next  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We are going to keep the next page as is. You can read more about the options in the Developer Guide, which you can find at the end of this article tagged [1]. Click Next  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We will leave the execution timeout to the default value provided and will then click Confirm and start run. This can take a while, so wait patiently.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwdmygejisgotr8twl5ih.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwdmygejisgotr8twl5ih.png" alt="Run Results" width="799" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Run Results&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: View the run’s results&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
 — When the run is done, you will see the results of the runs. In my case 3 tests were ran and all passed.&lt;br&gt;&lt;br&gt;
 — The unique problems would list any problems that were discovered. A video can be downloaded to view the recording of the test that had the problem. There are additional data provided that I won’t get into detail in this article.&lt;br&gt;&lt;br&gt;
 — Screenshots will display any screenshots Device Farm took during the run.&lt;/p&gt;




&lt;h4&gt;
  
  
  Where to go from&amp;nbsp;here?
&lt;/h4&gt;

&lt;p&gt;The next thing I personally would suggest, and plan on doing, is to learn more about test packages. There are frameworks that work with both iOS and Android, and some that only work for only Android or only iOS. This is where you are able to specify your tests the way you want them instead of using just the built-in tests AWS provides. This gives you complete control over how you want the tests done and what you want tested. This is where the real power of AWS Device Farm comes into play.&lt;/p&gt;

&lt;p&gt;The other thing to look into is the Desktop Browser Testing for any web apps. Personally for me, my project is going to start as just a web app, so Desktop Browser Testing is of personal importance. As always, you need to figure out your use cases and determine what your needs are.&lt;/p&gt;

&lt;p&gt;There is so much more to learn about AWS Device Farm, and I will continue posting about it as I go through it and learn it myself. For now, happy developing and testing!&lt;/p&gt;

&lt;p&gt;[1] &lt;a href="https://docs.aws.amazon.com/devicefarm/latest/developerguide/welcome.html" rel="noopener noreferrer"&gt;AWS Device Farm Developer Guide&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>devicefarm</category>
      <category>testingtools</category>
    </item>
    <item>
      <title>Launching a simple EC2 Instance with Apache Web Server — Part 1</title>
      <dc:creator>Scott Burgholzer</dc:creator>
      <pubDate>Mon, 11 Mar 2024 02:06:15 +0000</pubDate>
      <link>https://dev.to/aws-builders/launching-a-simple-ec2-instance-with-apache-web-server-part-1-33nl</link>
      <guid>https://dev.to/aws-builders/launching-a-simple-ec2-instance-with-apache-web-server-part-1-33nl</guid>
      <description>&lt;p&gt;&lt;strong&gt;Little backstory:&lt;/strong&gt; I’ve had this in my drafts for a while, wanting to do it all as one article. I’ve decided that is not the best way of presenting this information. As a newer content creator, I’ve been playing with how I want my pages to look like, and have decided to go with how it looks right now.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Introduction&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In this article, I will be doing what some call “click-ops” in AWS console as that is the easiest way to get started. If you want more challenges, I will also be posting on how to use AWS CLI (an article post on this will come at a later date, when completed, I will be updating this with a link to it!) to do the same thing. I will also show how to use a Cloud Development Kit (CDK). I will be using Python in my CDK examples. I will also use AWS CloudFormation. Finally, for an even more advanced challenge, I will be creating this process again using Terraform. If you are new, the console way will be the easiest, but I also want to introduce you to the CLI, CDK, CloudFormation and Terraform as these are advanced tools used!&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Scope of this tutorial&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In this multi-part tutorial, we are wanting to set up an EC2 instance, configure security group(s), create a key-pair SSH Key to remote into the instance, and install Apache Web Server.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Method 1:&lt;/strong&gt; Using AWS Console (without using userdata)&lt;/p&gt;

&lt;p&gt;There are actually two ways we can use the AWS console to create the EC2 instance, and install Apache and run it and create the test HTML file. This method will require us to use SSH to remote into our EC2 instance to preform some steps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1:&lt;/strong&gt; When you are logged into the AWS Console, search for EC2 in the search bar.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe4zyev7c4psyzew0g3qc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe4zyev7c4psyzew0g3qc.png" alt="AWS Console and searching for EC2 via the search bar." width="800" height="488"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2:&lt;/strong&gt; Click on Launch Instance&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foc5cveduvfa026ric38d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foc5cveduvfa026ric38d.png" alt="EC2 console dashboard with Launch Instance button shown" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Configure your instance and install Apache and test web page&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Give the instance a name, I called mine “Web Server Test”    * Keep the application and OS Images as is&lt;/li&gt;
&lt;li&gt;Keep the instance type as t2.micro&lt;/li&gt;
&lt;li&gt;Click on new key par and give it a name, then click on Create key pair. It’ll download a file, move it to a safe location so you don’t loose it!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0hh4oktrmp3ybdz4tamw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0hh4oktrmp3ybdz4tamw.png" alt="Creating a Key Pair" width="799" height="487"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Under Network settings we will be keeping the selection Create Security Group. Allow SSH traffic from should be checked, and you’ll also want to check the Allow HTTP traffic from the internet. Next to SSH there is a dropdown menu that you can say any IP can connect to the instance, a custom IP or range, or your IP only. For this example, I’m keeping it as anywhere&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0qkjbaspm7g42ml0noft.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0qkjbaspm7g42ml0noft.png" alt="Creating the security group through the Launch Instance Wizard" width="799" height="487"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We will leave everything else as default&lt;/li&gt;
&lt;li&gt;Click on Launch Instance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcdmu9ixhn5z1q1aojh66.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcdmu9ixhn5z1q1aojh66.png" alt="showing the instance was created successfully from the wizard with a green success box with a link to view that instance." width="799" height="487"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click on the instance ID shown in the green success box&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2f8tylkz28vctdvo66h7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2f8tylkz28vctdvo66h7.png" alt="Instance shown as running" width="799" height="487"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click the checkbox next to the instance, then go to actions and click on Connect&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu6heinfunm0n0qa33im0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu6heinfunm0n0qa33im0.png" width="800" height="496"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You’ll see there are four options of connecting. The only two that will work with our instance, due to the way we set it up, is EC2 Instance Connect and SSH client. If on Mac or Linux you can use command line to SSH into the instance’s command line, on Windows it is easiest to use a tool such as Putty.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flz3upox6kyi44xmu35e7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flz3upox6kyi44xmu35e7.png" alt="Showing the connect options, and the EC2 Instance Connect we are using in our example" width="799" height="487"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We will be using EC2 Instance connect in our case, so click on that tab, then click on connect&lt;/li&gt;
&lt;li&gt;After waiting a few moments, you’ll see a command line terminal for our instance.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu32gmo2qevknel0gch3g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu32gmo2qevknel0gch3g.png" alt="The command line interface via EC2 Instance Connect" width="799" height="487"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We now want to install Apache and start the service. Run the following commands in the command prompt.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;yum update &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="c"&gt;# We are asking the packacge manager to install any updates&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;yum &lt;span class="nb"&gt;install &lt;/span&gt;httpd &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="c"&gt;# We are asking the package manager to install Apache&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;httpd &lt;span class="c"&gt;# We are telling the server to automatically start Apache upon start up&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start httpd &lt;span class="c"&gt;# we are telling the server to start the Apache Server&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status httpd &lt;span class="c"&gt;# we are telling the server to tell us the status of the Apache Server&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmvehd68fidaww8mxccfy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmvehd68fidaww8mxccfy.png" alt="Results of some of the commands and showing that Apache is running" width="800" height="496"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We are going to create a simple HTML file, run the below command to open a new file using a command line text editor&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;sudo nano /var/www/html/index.html&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Put the following code into the text editor
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Test Web Page&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Welcome to my website!&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;I love AWS&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;When you are done with the file, press ctl and x, it’ll prompt you if you want to save, type Y, press enter, then press enter again.&lt;/li&gt;
&lt;li&gt;Go back to the EC2 Instances page, click on your instance, then click on open address under the Public IPv4 address, change the HTTPS to HTTP&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8ngmx3ft4etn2f5upp5t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8ngmx3ft4etn2f5upp5t.png" alt="You should see your test page" width="800" height="496"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you see your test page, you were successful!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 4: Clean Up&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Close all windows except for the Instances window.&lt;/li&gt;
&lt;li&gt;Select it, then click on Instance state then click on Terminate instance so you don’t use up your free trial credits or get charged!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Congrats! You have just launched a simple EC2 Web server!&lt;/p&gt;




&lt;p&gt;&lt;a href="https://dev.to/aws-builders/launching-a-simple-ec2-instance-with-apache-web-server-part-1-33nl"&gt;Launching a simple EC2 Instance with Apache Web Server — Part 1 (AWS Console)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Launching a simple EC2 Instance with Apache Web Server — Part 2 (AWS Console with Userdata) (Coming Soon)&lt;/p&gt;

&lt;p&gt;Launching a simple EC2 Instance with Apache Web Server — Part 3 (AWS CLI with user data) (Coming Soon)&lt;/p&gt;

&lt;p&gt;Launching a simple EC2 Instance with Apache Web Server — Part 4 (Terraform with user data) (Coming Soon)&lt;/p&gt;

</description>
      <category>aws</category>
      <category>ec2</category>
    </item>
  </channel>
</rss>
