AI Cloud Security Analyzer with Bedrock
Build a Python CLI tool that scans Terraform and IAM files for security risks.
Introduction
30 Second Summary
Cloud security misconfigurations are the leading cause of data breaches. A single overly permissive IAM policy or a public S3 bucket can expose an entire organization's data overnight.
In this project, you will build a Python command-line tool that scans Terraform and IAM policy files for security vulnerabilities. It uses AI to explain each risk in plain English and suggest specific fixes.
What You'll Build
A CLI security analyzer that combines static code analysis with Amazon Bedrock's Claude 3.5 Haiku model to produce AI-powered security reports.
By the end of this project, you'll have:
- A working Python CLI tool that scans Terraform .tf files and IAM policy JSON for common security anti-patterns.
- AI-generated security reports with severity ratings, plain-English risk explanations, and remediation code powered by Amazon Bedrock.
- Hands-on experience with the AWS Bedrock Converse API for sending structured prompts to Claude 3.5 Haiku and parsing responses.
- Secret Mission: Add a severity scoring system that outputs a pass/fail grade for CI/CD pipeline integration.
Are there any prerequisites?
You'll need an AWS account with Bedrock access, basic Python knowledge, and some familiarity with IAM policies and Terraform configuration files. You should also have the AWS CLI installed and configured with credentials.
Before We Start
Before We Start...
Set Up Your Project Environment
Security scanning tools need a clean environment with the right dependencies to parse infrastructure files and communicate with AI services.
In this step, you will create the project workspace, install the packages that power the scanner, and enable the AI model that will analyze your findings.
In this step, get ready to:
- Create the project directory structure and a Python virtual environment.
- Install boto3 and python-hcl2 packages.
- Enable Claude 3.5 Haiku model access in Amazon Bedrock.
Create the project directory and virtual environment
Your security analyzer needs a dedicated folder with subdirectories for scanner modules and sample files to test against.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Terminal and press Enter to open it.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder, move into it, and create the subfolders by running these commands:
mkdir security-analyzer
cd security-analyzer
mkdir scanners samples
- Confirm the folders exist by running:
ls
You should see scanners and samples in the output.
Don't see both folders?
Make sure you ran cd security-analyzer before running ls. If you see No such file or directory, double check that the mkdir security-analyzer command completed without typos.
help me figure out why my project folders are missing
Now you will create a Python virtual environment to keep this project's packages isolated from the rest of your system.
macOS
- Create and activate your virtual environment by running these commands:
python3 -m venv venv
source venv/bin/activate
Windows
- Create and activate your virtual environment by running these commands:
python -m venv venv
venv\Scripts\activate
You should see (venv) appear at the beginning of your terminal prompt. This confirms the virtual environment is active.
What is a virtual environment?
A virtual environment is an isolated Python workspace. Packages you install here stay separate from your system-wide Python installation. This prevents version conflicts between projects.
Not seeing (venv) in your prompt?
On Windows, if you get a permissions error when running the activate script, you may need to allow script execution first. Run Set-ExecutionPolicy -Scope CurrentUser RemoteSigned in PowerShell, then try the activate command again.
help me activate my Python virtual environment
Install required Python packages
The scanner needs two packages: boto3 to communicate with Amazon Bedrock, and python-hcl2 to parse Terraform configuration files.
- Install both packages by running this command:
pip install boto3 python-hcl2
What do these packages do?
boto3 is the official AWS SDK for Python. It lets your code call AWS services like Bedrock directly from a script.
python-hcl2 properly parses HCL2 syntax (Terraform's configuration language) into Python dictionaries. This is much more reliable than trying to parse Terraform files with regular expressions.
- Verify both packages installed correctly by running this quick import test:
python3 -c "import boto3; import hcl2; print('Both packages imported successfully')"
You should see Both packages imported successfully printed in your terminal.
Seeing a ModuleNotFoundError?
Make sure your virtual environment is still active. You should see (venv) in your prompt. If not, re-run the activate command from the previous substep before trying the install again.
help me fix my Python package installation
Enable Claude 3.5 Haiku model access in Bedrock
Your analyzer will use Claude 3.5 Haiku on Amazon Bedrock to explain security risks in plain English. Before you can call the model from code, you need to complete Anthropic's one-time use-case form in the Bedrock console.
Why Claude 3.5 Haiku?
For analyzing small code snippets (a few hundred lines of Terraform or IAM policy), Haiku is the most cost-effective choice. It responds quickly and is more than capable enough for structured security analysis.
- Open the AWS Management Console at https://console.aws.amazon.com and sign in.
- Make sure your region is set to US East (N. Virginia) (us-east-1) using the region selector in the top right corner.
- Search for Bedrock in the top search bar and select Amazon Bedrock.
- In the left navigation pane, under Bedrock configurations, choose Model access.
What is Model access?
Amazon Bedrock enables access to all foundation models by default if your IAM permissions include the required AWS Marketplace actions. However, Anthropic models require a one-time First Time Use form before you can invoke them.
- On the Model access page, choose Enable specific models.
- Find and select Claude 3.5 Haiku in the model list.
- Choose Next.
- If prompted with a use case details form for Anthropic, choose Submit use case details.
- Fill out the form with a brief description of your use case (e.g., security analysis of infrastructure code) and your website URL.
- Choose Submit form.
- Review the access changes, then choose Submit.
Access is granted immediately after form submission. Refresh the page and verify that Claude 3.5 Haiku shows an Access granted status.
Already completed the Anthropic form before?
The First Time Use form is a one-time requirement per AWS account (or per AWS Organization root account). If you have used any Anthropic model on Bedrock before, you may already have access and can skip the form step.
Still seeing access denied?
Make sure your IAM user or role has the AWS Marketplace permissions: aws-marketplace:Subscribe, aws-marketplace:Unsubscribe, and aws-marketplace:ViewSubscriptions. These are required in addition to bedrock:InvokeModel.
help me get access to Claude 3.5 Haiku on Amazon Bedrock
Your environment is ready. Next up, you will create intentionally insecure sample files to test your scanner against.
Create Intentionally Insecure Sample Files
Your environment is ready with all the tools installed. Now you need something to scan.
A security scanner is only as good as its test cases. In this step, you will create sample infrastructure files that contain real-world security anti-patterns. These files give your scanner known vulnerabilities to detect later.
In this step, get ready to:
- Create an insecure Terraform file with public buckets, open security groups, and unencrypted databases.
- Create an insecure IAM policy with wildcard permissions and missing conditions.
Create an insecure Terraform file
Terraform files (.tf) define cloud infrastructure as code. When written carelessly, they can expose entire environments to the internet.
- In VS Code, create a new file called insecure_s3.tf inside your samples/ folder.
- Paste the following code into the file:
resource "aws_s3_bucket" "data_dump" {
bucket = "company-data-dump-public"
acl = "public-read"
tags = {
Name = "Data Dump Bucket"
Environment = "production"
}
}
resource "aws_security_group" "wide_open" {
name = "allow_all_traffic"
description = "Extremely permissive security group"
vpc_id = "vpc-12345678"
ingress {
from_port = 0
to_port = 65535
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_db_instance" "production_db" {
allocated_storage = 20
engine = "mysql"
engine_version = "8.0"
instance_class = "db.t3.micro"
db_name = "productiondb"
username = "admin"
password = "supersecretpassword123"
storage_encrypted = false
publicly_accessible = true
skip_final_snapshot = true
}
What security issues are hiding in this file?
This file defines three AWS resources, each with a different category of vulnerability:
- aws_s3_bucket.data_dump uses acl = "public-read", meaning anyone on the internet can read its contents.
- aws_security_group.wide_open opens all TCP ports (0-65535) to 0.0.0.0/0. This allows any IP address to connect to any service.
- aws_db_instance.production_db has storage_encrypted = false and publicly_accessible = true. An unencrypted, public-facing database is a critical risk.
- Save samples/insecure_s3.tf.
Create an insecure IAM policy file
IAM policies control who can do what in your AWS account. Overly permissive policies are one of the most common causes of cloud breaches.
- Create a new file called insecure_iam.json inside your samples/ folder.
- Paste the following JSON into the file:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "FullAdminAccess",
"Effect": "Allow",
"Action": "*",
"Resource": "*"
},
{
"Sid": "S3FullAccess",
"Effect": "Allow",
"Action": "s3:*",
"Resource": "*"
},
{
"Sid": "EC2FullAccess",
"Effect": "Allow",
"Action": "ec2:*",
"Resource": "*"
}
]
}
What makes this policy dangerous?
Each statement in this policy violates the principle of least privilege:
- FullAdminAccess grants every possible action (Action: "*") on every resource (Resource: "*"). This is equivalent to full administrator access.
- S3FullAccess and EC2FullAccess use service-level wildcards (s3:*, ec2:*) with no resource restrictions.
- None of the statements include a Condition block. There is no MFA requirement, IP restriction, or time constraint.
- Save samples/insecure_iam.json.
- Confirm both files exist by running this in your terminal:
ls samples/
You should see insecure_iam.json and insecure_s3.tf listed in the output.
Don't see both files?
Make sure you created both files inside the samples/ folder, not in the root security-analyzer/ directory.
Check that your terminal is inside the security-analyzer/ folder when you run ls samples/.
help me find where my sample files were saved
✔️ Awesome, I've got everything!
Great. Double check you have saved both files.
ⓧ I'd like to double check the full code
Here is the complete samples/insecure_s3.tf file:
resource "aws_s3_bucket" "data_dump" {
bucket = "company-data-dump-public"
acl = "public-read"
tags = {
Name = "Data Dump Bucket"
Environment = "production"
}
}
resource "aws_security_group" "wide_open" {
name = "allow_all_traffic"
description = "Extremely permissive security group"
vpc_id = "vpc-12345678"
ingress {
from_port = 0
to_port = 65535
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_db_instance" "production_db" {
allocated_storage = 20
engine = "mysql"
engine_version = "8.0"
instance_class = "db.t3.micro"
db_name = "productiondb"
username = "admin"
password = "supersecretpassword123"
storage_encrypted = false
publicly_accessible = true
skip_final_snapshot = true
}
Here is the complete samples/insecure_iam.json file:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "FullAdminAccess",
"Effect": "Allow",
"Action": "*",
"Resource": "*"
},
{
"Sid": "S3FullAccess",
"Effect": "Allow",
"Action": "s3:*",
"Resource": "*"
},
{
"Sid": "EC2FullAccess",
"Effect": "Allow",
"Action": "ec2:*",
"Resource": "*"
}
]
}
Your test data is ready. Next up, you will build the Terraform scanner that parses these .tf files and detects the vulnerabilities you just planted.
Build the Terraform Security Scanner
You have insecure sample files ready to scan. Now you need a scanner that can actually read those files and flag the problems.
In this step, you'll build a Terraform scanner that uses python-hcl2 to parse .tf files into Python dictionaries. The scanner will iterate through each resource block and check it against a set of detection rules for common security anti-patterns.
In this step, get ready to:
- Create a Terraform scanner module that parses HCL2 files into Python data structures.
- Implement detection rules for public S3 buckets, overly open security groups, and unencrypted databases.
- Test the scanner against your sample file and verify the structured output.
Create the scanner file with HCL2 parsing
For Python to treat the scanners/ folder as an importable package, it needs an __init__.py file. This file can be empty.
- Create the package initializer file by running this command from your security-analyzer folder:
touch scanners/__init__.py
- Confirm the file exists by running:
ls scanners/
You should see __init__.py in the output.
Now you'll create the main scanner file. The scan_terraform_file function is the entry point. It opens a .tf file, uses hcl2.load() to parse the HCL2 syntax into a nested Python dictionary, then loops through each resource block to check for issues.
- Create a new file called terraform_scanner.py inside the scanners/ folder.
- Add the following code to scanners/terraform_scanner.py:
import hcl2
import json
def scan_terraform_file(file_path):
"""Parse a Terraform file and scan for security issues."""
findings = []
with open(file_path, "r") as f:
try:
tf_config = hcl2.load(f)
except Exception as e:
print(f"Error parsing {file_path}: {e}")
return findings
resources = tf_config.get("resource", [])
for resource_block in resources:
for resource_type, instances in resource_block.items():
for instance in instances:
for resource_name, config in instance.items():
findings.extend(
check_resource(resource_type, resource_name, config)
)
return findings
What does this code do?
hcl2.load(f) reads the Terraform file and converts it into a Python dictionary. This gives you a structured representation of every resource, variable, and block in the file.
The nested for loops walk through the specific way python-hcl2 structures its output. Each resource block is a list of dictionaries, where the key is the resource type (like aws_s3_bucket) and the value contains a list of named instances with their configurations.
The function calls check_resource() on each resource it finds. You will define that function next.
- Save scanners/terraform_scanner.py.
- Verify the parsing works by running this quick import test in your terminal:
python -c "from scanners.terraform_scanner import scan_terraform_file; print('Import successful')"
You should see Import successful printed. You will see a NameError about check_resource if you try to run the scan. That is expected because you have not defined that function yet.
Seeing a ModuleNotFoundError?
Make sure you are running the command from the security-analyzer folder (the one that contains scanners/). Also confirm your virtual environment is still activated. You should see the environment name in your terminal prompt.
help me fix this ModuleNotFoundError when importing my scanner
Now add the check_resource function. This is a dispatcher that routes each resource to the correct detection function based on its type.
- Add the following function below scan_terraform_file in scanners/terraform_scanner.py:
def check_resource(resource_type, resource_name, config):
"""Check a single resource for security issues."""
findings = []
if resource_type == "aws_s3_bucket":
findings.extend(check_s3_bucket(resource_name, config))
if resource_type == "aws_security_group":
findings.extend(check_security_group(resource_name, config))
if resource_type == "aws_db_instance":
findings.extend(check_rds_instance(resource_name, config))
return findings
What does this code do?
check_resource acts as a router. When the scanner finds a resource, this function checks its type and calls the appropriate detection function. Each if statement maps a Terraform resource type to a specialized checker.
This design makes it easy to add new resource checks later. You just add another if block for the new resource type.
- Save scanners/terraform_scanner.py. You will not see a change yet. The next chunk defines the detection functions that check_resource calls.
Implement detection rules for common security issues
Each detection function looks at a resource's configuration dictionary and checks for a specific anti-pattern. When it finds one, it appends a finding dictionary with three keys: resource (which resource has the issue), issue (what the problem is), and snippet (the offending configuration).
- Add the S3 bucket detection function below check_resource in scanners/terraform_scanner.py:
def check_s3_bucket(resource_name, config):
"""Check S3 bucket for public access and missing encryption."""
findings = []
acl = config.get("acl", "")
if acl in ["public-read", "public-read-write"]:
findings.append({
"resource": f"aws_s3_bucket.{resource_name}",
"issue": f"S3 bucket has public ACL: {acl}",
"snippet": json.dumps({"acl": acl}, indent=2),
})
return findings
What does this code do?
This function checks if an S3 bucket has its ACL set to public-read or public-read-write. These ACLs make bucket contents accessible to anyone on the internet.
When it finds the issue, it builds a finding dictionary with the resource identifier, a human-readable description, and the offending configuration serialized as JSON. This structure is the standard format all your detection functions will follow.
- Add the security group detection function below check_s3_bucket:
def check_security_group(resource_name, config):
"""Check security group for overly permissive ingress rules."""
findings = []
ingress_rules = config.get("ingress", [])
if isinstance(ingress_rules, dict):
ingress_rules = [ingress_rules]
for rule in ingress_rules:
cidr_blocks = rule.get("cidr_blocks", [])
from_port = rule.get("from_port", 0)
to_port = rule.get("to_port", 0)
if "0.0.0.0/0" in cidr_blocks:
port_range = to_port - from_port
if port_range > 100 or (from_port == 0 and to_port == 65535):
findings.append({
"resource": f"aws_security_group.{resource_name}",
"issue": (
f"Security group allows wide port range "
f"({from_port}-{to_port}) open to the internet (0.0.0.0/0)"
),
"snippet": json.dumps(rule, indent=2),
})
return findings
What does this code do?
This function iterates through the security group's ingress rules looking for a dangerous combination: the CIDR block 0.0.0.0/0 (meaning open to the entire internet) paired with a wide port range (more than 100 ports, or the full 0-65535 range).
Opening a single port like 443 (HTTPS) to the internet is normal. Opening all 65,535 ports to the internet is a serious misconfiguration that gives attackers many potential entry points.
- Add the RDS instance detection function below check_security_group:
def check_rds_instance(resource_name, config):
"""Check RDS instance for missing encryption."""
findings = []
storage_encrypted = config.get("storage_encrypted", False)
if not storage_encrypted:
findings.append({
"resource": f"aws_db_instance.{resource_name}",
"issue": "RDS instance does not have storage encryption enabled",
"snippet": json.dumps(
{"storage_encrypted": storage_encrypted}, indent=2
),
})
return findings
What does this code do?
This function checks whether an RDS database instance has storage_encrypted set to true. Without encryption, anyone with access to the underlying storage (physical or through a misconfigured backup) could read the raw database files.
- Save scanners/terraform_scanner.py.
Seeing a syntax error on save?
Check that each function definition starts at the top level (no accidental indentation). Also confirm the f-strings in check_security_group use matching parentheses for the multi-line string.
help me fix a syntax error in my terraform_scanner.py
Test the scanner and verify the structured findings
Your scanner is complete. Time to run it against the insecure Terraform file you created earlier and confirm it detects all three planted vulnerabilities.
- Test the scanner by running this command from your security-analyzer folder:
python -c "
from scanners.terraform_scanner import scan_terraform_file
import json
findings = scan_terraform_file('samples/insecure_s3.tf')
print(f'Found {len(findings)} issues:')
for f in findings:
print(json.dumps(f, indent=2))
"
What should I see?
You should see 3 findings printed as JSON dictionaries. Each finding has three keys:
- resource identifies which Terraform resource has the issue (like aws_s3_bucket.data_dump).
- issue describes the security problem in plain English.
- snippet shows the offending configuration value as JSON.
The three findings should flag the public S3 bucket ACL, the wide-open security group ports, and the unencrypted RDS instance.
Seeing 0 findings?
Make sure the file path samples/insecure_s3.tf is correct. The command must run from the security-analyzer folder where both scanners/ and samples/ live. Also check that insecure_s3.tf contains all three resource blocks from Step 2.
help me debug why my Terraform scanner finds 0 issues
✔️ Awesome, I've got everything!
Great. Double check you have saved scanners/terraform_scanner.py and scanners/__init__.py.
ⓧ I'd like to double check the full code
import hcl2
import json
def scan_terraform_file(file_path):
"""Parse a Terraform file and scan for security issues."""
findings = []
with open(file_path, "r") as f:
try:
tf_config = hcl2.load(f)
except Exception as e:
print(f"Error parsing {file_path}: {e}")
return findings
resources = tf_config.get("resource", [])
for resource_block in resources:
for resource_type, instances in resource_block.items():
for instance in instances:
for resource_name, config in instance.items():
findings.extend(
check_resource(resource_type, resource_name, config)
)
return findings
def check_resource(resource_type, resource_name, config):
"""Check a single resource for security issues."""
findings = []
if resource_type == "aws_s3_bucket":
findings.extend(check_s3_bucket(resource_name, config))
if resource_type == "aws_security_group":
findings.extend(check_security_group(resource_name, config))
if resource_type == "aws_db_instance":
findings.extend(check_rds_instance(resource_name, config))
return findings
def check_s3_bucket(resource_name, config):
"""Check S3 bucket for public access and missing encryption."""
findings = []
acl = config.get("acl", "")
if acl in ["public-read", "public-read-write"]:
findings.append({
"resource": f"aws_s3_bucket.{resource_name}",
"issue": f"S3 bucket has public ACL: {acl}",
"snippet": json.dumps({"acl": acl}, indent=2),
})
return findings
def check_security_group(resource_name, config):
"""Check security group for overly permissive ingress rules."""
findings = []
ingress_rules = config.get("ingress", [])
if isinstance(ingress_rules, dict):
ingress_rules = [ingress_rules]
for rule in ingress_rules:
cidr_blocks = rule.get("cidr_blocks", [])
from_port = rule.get("from_port", 0)
to_port = rule.get("to_port", 0)
if "0.0.0.0/0" in cidr_blocks:
port_range = to_port - from_port
if port_range > 100 or (from_port == 0 and to_port == 65535):
findings.append({
"resource": f"aws_security_group.{resource_name}",
"issue": (
f"Security group allows wide port range "
f"({from_port}-{to_port}) open to the internet (0.0.0.0/0)"
),
"snippet": json.dumps(rule, indent=2),
})
return findings
def check_rds_instance(resource_name, config):
"""Check RDS instance for missing encryption."""
findings = []
storage_encrypted = config.get("storage_encrypted", False)
if not storage_encrypted:
findings.append({
"resource": f"aws_db_instance.{resource_name}",
"issue": "RDS instance does not have storage encryption enabled",
"snippet": json.dumps(
{"storage_encrypted": storage_encrypted}, indent=2
),
})
return findings
Your Terraform scanner is working and catching real security issues. Next up, you'll build the IAM policy scanner to detect dangerous permission patterns in JSON policy documents.
Build the IAM Policy Scanner
Your Terraform scanner can detect insecure patterns in .tf files. Infrastructure-as-code is only half the security picture though.
The other half is IAM policies. These JSON documents control who can do what in your AWS account. A single wildcard permission can give an attacker full access to every service. In this step, you'll build a scanner that catches those dangerous patterns.
In this step, get ready to:
- Create an IAM policy scanner that parses JSON policy documents.
- Implement detection rules for wildcard permissions, overly broad resources, and missing conditions.
- Verify the scanner returns structured findings matching your Terraform scanner's format.
Create the IAM policy scanner file
IAM policies are JSON documents with a Statement array. Each statement specifies an Effect (Allow or Deny), Actions, and Resources. Your scanner needs to load this JSON and check each statement individually.
- Create a new file called iam_scanner.py inside the scanners/ folder.
- Add the following code to create the main scanning function:
import json
def scan_iam_policy(file_path):
"""Load and scan an IAM policy JSON file for security issues."""
findings = []
with open(file_path, "r") as f:
try:
policy = json.load(f)
except json.JSONDecodeError as e:
print(f"Error parsing {file_path}: {e}")
return findings
# Extract the list of policy statements
statements = policy.get("Statement", [])
# Check each statement for security issues
for i, statement in enumerate(statements):
findings.extend(check_statement(i, statement))
return findings
What does this code do?
json.load(f) reads the IAM policy file and converts it into a Python dictionary. If the file isn't valid JSON, the scanner prints an error and returns an empty list instead of crashing.
The function iterates through each statement using enumerate() so it can track the statement index. This makes findings easier to locate when reviewing the report later.
- Save scanners/iam_scanner.py.
You won't see a change yet. The next chunk adds the check_statement function that does the actual detection work.
Implement detection rules for policy statements
The most dangerous IAM patterns fall into three categories: wildcard actions that grant unrestricted access, wildcard resources that apply permissions to everything, and broad permissions without conditions like MFA requirements.
- In scanners/iam_scanner.py, add the check_statement function below the scan_iam_policy function:
def check_statement(index, statement):
"""Check a single IAM policy statement for security issues."""
findings = []
effect = statement.get("Effect", "")
# Only check Allow statements - Deny statements restrict access
if effect != "Allow":
return findings
# Normalize actions and resources to lists
actions = statement.get("Action", [])
if isinstance(actions, str):
actions = [actions]
resources = statement.get("Resource", [])
if isinstance(resources, str):
resources = [resources]
# Check for wildcard actions
for action in actions:
if action == "*":
findings.append({
"resource": f"Statement[{index}]",
"issue": "IAM policy grants ALL actions (Action: *)",
"snippet": json.dumps(statement, indent=2),
})
break
elif action.endswith(":*"):
findings.append({
"resource": f"Statement[{index}]",
"issue": (
f"IAM policy grants all actions for a service: {action}"
),
"snippet": json.dumps(statement, indent=2),
})
# Check for wildcard resources
if "*" in resources:
findings.append({
"resource": f"Statement[{index}]",
"issue": "IAM policy applies to ALL resources (Resource: *)",
"snippet": json.dumps(statement, indent=2),
})
# Check for missing conditions on sensitive permissions
condition = statement.get("Condition")
has_wildcard_action = "*" in actions or any(
a.endswith(":*") for a in actions
)
if has_wildcard_action and not condition:
findings.append({
"resource": f"Statement[{index}]",
"issue": (
"Broad permissions granted without any Condition block "
"(no MFA requirement, IP restriction, or time constraint)"
),
"snippet": json.dumps(statement, indent=2),
})
return findings
What does this code do?
The function only inspects Allow statements. Deny statements restrict access, so they are not a security risk.
IAM policies can express actions and resources as either a single string or an array. The isinstance(actions, str) check normalizes both formats into a list so the detection logic works consistently.
Three detection rules fire in sequence: wildcard actions ("*" or "service:*"), wildcard resources, and broad permissions missing a Condition block. A single statement can trigger multiple findings if it violates more than one rule.
- Save scanners/iam_scanner.py.
Seeing a syntax error?
Make sure the check_statement function is at the same indentation level as scan_iam_policy (not nested inside it). Both should be top-level functions in the file.
Check that the f-strings use matching quotes. Each f"Statement[{index}]" must have its opening and closing double quotes.
Help me debug a syntax error in my IAM scanner
Verify the scanner output against your sample file
Your IAM scanner returns findings in the same format as the Terraform scanner: a list of dictionaries with resource, issue, and snippet keys. This consistency means the Bedrock analyzer can process findings from both scanners identically.
- Test the scanner against your sample IAM policy by running this command from the security-analyzer folder:
python -c "from scanners.iam_scanner import scan_iam_policy; import json; findings = scan_iam_policy('samples/insecure_iam.json'); print(f'Found {len(findings)} issues'); print(json.dumps(findings[:2], indent=2))"
- You should see output showing multiple findings. The scanner should detect at least 7 issues across the three statements in your sample file (wildcard actions, wildcard resources, and missing conditions for each).
Why so many findings from three statements?
A single IAM statement can trigger multiple detection rules. For example, the FullAdminAccess statement has Action: "*" (wildcard action), Resource: "*" (wildcard resource), and no Condition block (missing safeguard). That is three findings from one statement.
Seeing "ModuleNotFoundError"?
Make sure you are running the command from the security-analyzer folder (the one containing the scanners/ directory). Python needs to find the scanners package relative to your current directory.
Also confirm that scanners/__init__.py exists. This empty file tells Python that scanners/ is a package it can import from.
Help me fix a ModuleNotFoundError when importing my IAM scanner
✔️ Awesome, I've got everything!
Great. Double check you have saved scanners/iam_scanner.py.
ⓧ I'd like to double check the full code
Here is the complete scanners/iam_scanner.py file:
import json
def scan_iam_policy(file_path):
"""Load and scan an IAM policy JSON file for security issues."""
findings = []
with open(file_path, "r") as f:
try:
policy = json.load(f)
except json.JSONDecodeError as e:
print(f"Error parsing {file_path}: {e}")
return findings
statements = policy.get("Statement", [])
for i, statement in enumerate(statements):
findings.extend(check_statement(i, statement))
return findings
def check_statement(index, statement):
"""Check a single IAM policy statement for security issues."""
findings = []
effect = statement.get("Effect", "")
if effect != "Allow":
return findings
actions = statement.get("Action", [])
if isinstance(actions, str):
actions = [actions]
resources = statement.get("Resource", [])
if isinstance(resources, str):
resources = [resources]
# Check for wildcard actions
for action in actions:
if action == "*":
findings.append({
"resource": f"Statement[{index}]",
"issue": "IAM policy grants ALL actions (Action: *)",
"snippet": json.dumps(statement, indent=2),
})
break
elif action.endswith(":*"):
findings.append({
"resource": f"Statement[{index}]",
"issue": (
f"IAM policy grants all actions for a service: {action}"
),
"snippet": json.dumps(statement, indent=2),
})
# Check for wildcard resources
if "*" in resources:
findings.append({
"resource": f"Statement[{index}]",
"issue": "IAM policy applies to ALL resources (Resource: *)",
"snippet": json.dumps(statement, indent=2),
})
# Check for missing conditions on sensitive permissions
condition = statement.get("Condition")
has_wildcard_action = "*" in actions or any(
a.endswith(":*") for a in actions
)
if has_wildcard_action and not condition:
findings.append({
"resource": f"Statement[{index}]",
"issue": (
"Broad permissions granted without any Condition block "
"(no MFA requirement, IP restriction, or time constraint)"
),
"snippet": json.dumps(statement, indent=2),
})
return findings
You now have two scanners that catch real-world security mistakes in infrastructure code. Next up, you'll connect them to Amazon Bedrock so Claude can explain each risk in plain English and suggest fixes.
Integrate AWS Bedrock for AI-Powered Analysis
Your scanners can now detect security issues in Terraform and IAM files. The findings are structured and ready for analysis.
The missing piece is interpretation. A list of flagged patterns is useful, but a plain-English explanation of why each finding is dangerous (plus how to fix it) is far more actionable. In this step, you will connect your scanners to Amazon Bedrock so that Claude 3.5 Haiku can analyze each finding and generate a severity rating, risk explanation, and remediation code.
In this step, get ready to:
- Define a system prompt that instructs Claude to act as a cloud security engineer.
- Build a function that sends scanner findings to Amazon Bedrock via the Converse API.
- Parse the AI response into a structured security report.
Create the Bedrock analyzer file and system prompt
The system prompt is what shapes how Claude responds. For security analysis, you need it to return consistent, structured output that your code can parse programmatically.
- Create a new file called bedrock_analyzer.py in the root of your security-analyzer folder (next to your scanners/ directory).
- Add the imports and system prompt constant by pasting this code:
import boto3
import json
from botocore.exceptions import ClientError
SYSTEM_PROMPT = """You are a senior cloud security engineer performing a security audit.
You will receive a list of security findings from automated scans of Terraform files and IAM policies.
For each finding, provide:
1. SEVERITY: Rate as CRITICAL, HIGH, MEDIUM, or LOW
2. EXPLANATION: A clear, plain-English explanation of why this is a security risk
3. ATTACK SCENARIO: A brief description of how an attacker could exploit this
4. REMEDIATION: Specific code or configuration changes to fix the issue
Format your response as a JSON array where each item has these fields:
- severity (string)
- explanation (string)
- attack_scenario (string)
- remediation (string)
Respond ONLY with the JSON array. No additional text before or after."""
What does this code do?
The imports bring in boto3 (the AWS SDK for Python), json for parsing responses, and ClientError for handling AWS API failures gracefully.
The SYSTEM_PROMPT tells Claude exactly how to format its response. By requesting a JSON array with specific fields, you can parse the AI output programmatically instead of scraping unstructured text.
Why request JSON output from the AI?
Your code needs to merge Claude's analysis back into the scanner findings. If the AI returns free-form paragraphs, parsing becomes fragile and error-prone. Requesting a strict JSON structure means you can reliably extract severity, explanation, and remediation for each finding.
- Save bedrock_analyzer.py.
Build the analyze_findings function with the Converse API
The Converse API is AWS Bedrock's unified interface for sending messages to any supported model. It works the same way regardless of whether you use Claude, Llama, or another model behind Bedrock.
- In bedrock_analyzer.py, add the analyze_findings function below the SYSTEM_PROMPT constant by pasting this code:
def analyze_findings(findings, region="us-east-1"):
"""Send findings to Bedrock Claude for AI-powered analysis."""
client = boto3.client("bedrock-runtime", region_name=region)
model_id = "anthropic.claude-3-5-haiku-20241022-v1:0"
findings_text = json.dumps(findings, indent=2)
user_message = (
f"Analyze the following security findings and provide your "
f"assessment:\n\n{findings_text}"
)
try:
response = client.converse(
modelId=model_id,
messages=[
{
"role": "user",
"content": [{"text": user_message}],
}
],
system=[{"text": SYSTEM_PROMPT}],
inferenceConfig={
"maxTokens": 4096,
"temperature": 0.2,
},
)
response_text = response["output"]["message"]["content"][0]["text"]
ai_analysis = json.loads(response_text)
except ClientError as e:
print(f"Error calling Bedrock: {e}")
ai_analysis = []
except json.JSONDecodeError:
print("Warning: Could not parse AI response as JSON.")
ai_analysis = []
report = {"findings": []}
for i, finding in enumerate(findings):
enriched = {
"resource": finding.get("resource", "Unknown"),
"issue": finding.get("issue", "Unknown"),
"snippet": finding.get("snippet", ""),
}
if i < len(ai_analysis):
ai = ai_analysis[i]
enriched["severity"] = ai.get("severity", "UNKNOWN")
enriched["ai_explanation"] = ai.get("explanation", "")
enriched["attack_scenario"] = ai.get("attack_scenario", "")
enriched["remediation"] = ai.get("remediation", "")
else:
enriched["severity"] = "UNKNOWN"
enriched["ai_explanation"] = "AI analysis unavailable"
enriched["attack_scenario"] = ""
enriched["remediation"] = ""
report["findings"].append(enriched)
return report
What does this function do?
- boto3.client("bedrock-runtime", region_name=region) creates a connection to the Bedrock Runtime service in your chosen AWS region.
- The model_id specifies Claude 3.5 Haiku (anthropic.claude-3-5-haiku-20241022-v1:0), which is fast and cost-effective for code analysis tasks.
- client.converse() sends the scanner findings as a user message along with the system prompt. The temperature: 0.2 keeps responses consistent and focused.
- The response path response["output"]["message"]["content"][0]["text"] extracts Claude's text reply from the Converse API response structure.
- Two except blocks handle AWS errors (ClientError) and malformed JSON responses separately, so the tool degrades gracefully instead of crashing.
Why use the Converse API instead of InvokeModel?
The Converse API provides a consistent interface across all Bedrock models. If you swap Claude for a different model later, the code barely changes. The older InvokeModel API requires model-specific request and response formats.
- Save bedrock_analyzer.py.
Seeing a ValidationException error?
If you see an error mentioning the operation not being allowed, your AWS account may not have model access enabled for Claude 3.5 Haiku. Go back to the Bedrock console, check Model access, and confirm the Anthropic Claude 3.5 Haiku model shows as granted.
Also verify your AWS CLI is configured for us-east-1 (or whichever region you enabled the model in).
help me debug a Bedrock ValidationException error
Understand the report structure
The last section of the function builds the report by merging your scanner findings with Claude's AI analysis. Each original finding gets enriched with four new fields from the AI response.
How does the report get built?
The function loops through each scanner finding and pairs it with the corresponding AI analysis entry by index. Each enriched finding in the final report contains:
- resource, issue, and snippet from the original scanner output.
- severity (Critical, High, Medium, or Low) rated by Claude.
- ai_explanation with a plain-English description of the risk.
- attack_scenario describing how an attacker could exploit the issue.
- remediation with specific code or configuration fixes.
If the AI response has fewer entries than findings (or fails entirely), the function fills in "UNKNOWN" severity and "AI analysis unavailable" instead of crashing.
✔️ Awesome, I've got everything!
Great. Double check you have saved bedrock_analyzer.py before moving on.
ⓧ I'd like to double check the full code
import boto3
import json
from botocore.exceptions import ClientError
SYSTEM_PROMPT = """You are a senior cloud security engineer performing a security audit.
You will receive a list of security findings from automated scans of Terraform files and IAM policies.
For each finding, provide:
1. SEVERITY: Rate as CRITICAL, HIGH, MEDIUM, or LOW
2. EXPLANATION: A clear, plain-English explanation of why this is a security risk
3. ATTACK SCENARIO: A brief description of how an attacker could exploit this
4. REMEDIATION: Specific code or configuration changes to fix the issue
Format your response as a JSON array where each item has these fields:
- severity (string)
- explanation (string)
- attack_scenario (string)
- remediation (string)
Respond ONLY with the JSON array. No additional text before or after."""
def analyze_findings(findings, region="us-east-1"):
"""Send findings to Bedrock Claude for AI-powered analysis."""
client = boto3.client("bedrock-runtime", region_name=region)
model_id = "anthropic.claude-3-5-haiku-20241022-v1:0"
findings_text = json.dumps(findings, indent=2)
user_message = (
f"Analyze the following security findings and provide your "
f"assessment:\n\n{findings_text}"
)
try:
response = client.converse(
modelId=model_id,
messages=[
{
"role": "user",
"content": [{"text": user_message}],
}
],
system=[{"text": SYSTEM_PROMPT}],
inferenceConfig={
"maxTokens": 4096,
"temperature": 0.2,
},
)
response_text = response["output"]["message"]["content"][0]["text"]
ai_analysis = json.loads(response_text)
except ClientError as e:
print(f"Error calling Bedrock: {e}")
ai_analysis = []
except json.JSONDecodeError:
print("Warning: Could not parse AI response as JSON.")
ai_analysis = []
report = {"findings": []}
for i, finding in enumerate(findings):
enriched = {
"resource": finding.get("resource", "Unknown"),
"issue": finding.get("issue", "Unknown"),
"snippet": finding.get("snippet", ""),
}
if i < len(ai_analysis):
ai = ai_analysis[i]
enriched["severity"] = ai.get("severity", "UNKNOWN")
enriched["ai_explanation"] = ai.get("explanation", "")
enriched["attack_scenario"] = ai.get("attack_scenario", "")
enriched["remediation"] = ai.get("remediation", "")
else:
enriched["severity"] = "UNKNOWN"
enriched["ai_explanation"] = "AI analysis unavailable"
enriched["attack_scenario"] = ""
enriched["remediation"] = ""
report["findings"].append(enriched)
return report
Your Bedrock integration is ready. In the next step, you will wire everything together into a command-line tool that scans files, calls Bedrock, and prints a formatted security report.
Build the CLI and Generate Your First Report
Your scanners can detect insecure patterns and your Bedrock integration can explain them. Now you need a way to tie all these pieces together into a tool you can actually run from the terminal.
In this step, you will build the main CLI script that accepts file paths, routes each file to the correct scanner, sends the collected findings to Amazon Bedrock, and prints a color-coded security report in your terminal.
In this step, get ready to:
- Create the CLI entry point with argparse and file-type detection.
- Build a formatted report printer with colored severity indicators.
- Run the analyzer against your sample files and review AI-generated findings.
Create the CLI entry point with file-type detection
The CLI needs to know which scanner to use for each file. A simple helper function that checks the file extension handles this routing decision.
- Create a new file called security_analyzer.py in the root of your security-analyzer folder (next to bedrock_analyzer.py).
- Add the imports and file-type detection function by pasting this code:
import argparse
import json
import sys
from scanners.terraform_scanner import scan_terraform_file
from scanners.iam_scanner import scan_iam_policy
from bedrock_analyzer import analyze_findings
def detect_file_type(file_path):
"""Detect whether a file is Terraform or IAM policy JSON."""
if file_path.endswith(".tf"):
return "terraform"
elif file_path.endswith(".json"):
return "iam"
else:
return None
What does this code do?
The imports pull in all the modules you built in previous steps. scan_terraform_file and scan_iam_policy handle detection, while analyze_findings sends results to Bedrock.
The detect_file_type() function checks the file extension to decide which scanner to use. Files ending in .tf go to the Terraform scanner, .json files go to the IAM scanner, and anything else is skipped.
- Save security_analyzer.py.
Seeing an import error?
Make sure security_analyzer.py is in the security-analyzer root folder, at the same level as the scanners/ folder and bedrock_analyzer.py. If it is inside scanners/, the relative imports will break.
Help me fix import errors in my security analyzer
Add the report formatter and main function
The report formatter takes the enriched findings from Bedrock and prints them with color-coded severity levels. This makes it easy to spot critical issues at a glance.
- In security_analyzer.py, add the print_report() function below detect_file_type():
def print_report(report):
"""Print a formatted security report to the terminal."""
print("\n" + "=" * 60)
print(" AI-POWERED CLOUD SECURITY ANALYSIS REPORT")
print("=" * 60)
if not report["findings"]:
print("\n No security issues found. Your configuration looks good!")
print("=" * 60)
return
print(f"\n Total findings: {len(report['findings'])}")
print("-" * 60)
for i, finding in enumerate(report["findings"], 1):
severity = finding.get("severity", "UNKNOWN")
severity_colors = {
"CRITICAL": "\033[91m",
"HIGH": "\033[93m",
"MEDIUM": "\033[33m",
"LOW": "\033[36m",
}
color = severity_colors.get(severity.upper(), "\033[0m")
reset = "\033[0m"
print(f"\n [{color}{severity.upper()}{reset}] Finding #{i}")
print(f" Resource: {finding.get('resource', 'N/A')}")
print(f" Issue: {finding.get('issue', 'N/A')}")
print(f"\n AI Analysis:")
print(f" {finding.get('ai_explanation', 'No analysis available')}")
if finding.get("remediation"):
print(f"\n Remediation:")
print(f" {finding.get('remediation')}")
print("-" * 60)
print("\n" + "=" * 60)
What does this code do?
The print_report() function formats each finding with ANSI color codes. Critical findings appear in red, high in yellow, medium in orange, and low in cyan.
For each finding, it displays the severity tag, the affected resource, the issue description, the AI-generated explanation, and remediation suggestions.
ANSI colors on Windows
The color codes may not display correctly in older Windows command prompts. Use Windows Terminal or VS Code's integrated terminal, which both support ANSI color codes.
- Now add the main() function below print_report(). This is the orchestrator that ties all your modules together:
def main():
parser = argparse.ArgumentParser(
description="AI-Powered Cloud Security Analyzer"
)
parser.add_argument(
"files",
nargs="+",
help="Terraform (.tf) or IAM policy (.json) files to scan",
)
parser.add_argument(
"--region",
default="us-east-1",
help="AWS region for Bedrock (default: us-east-1)",
)
args = parser.parse_args()
all_findings = []
for file_path in args.files:
file_type = detect_file_type(file_path)
if file_type == "terraform":
print(f"Scanning Terraform file: {file_path}")
findings = scan_terraform_file(file_path)
all_findings.extend(findings)
elif file_type == "iam":
print(f"Scanning IAM policy file: {file_path}")
findings = scan_iam_policy(file_path)
all_findings.extend(findings)
else:
print(f"Skipping unsupported file type: {file_path}")
if not all_findings:
print("\nNo security issues found!")
return
print(f"\nFound {len(all_findings)} potential security issue(s).")
print("Sending findings to Amazon Bedrock for AI analysis...\n")
report = analyze_findings(all_findings, region=args.region)
print_report(report)
if __name__ == "__main__":
main()
What does this code do?
The argparse setup defines a files positional argument that accepts one or more file paths, plus an optional --region flag if you want to use Bedrock in a different AWS region.
The loop iterates over each file path, calls detect_file_type() to choose the right scanner, then collects all findings into a single list. Once scanning is complete, the findings are sent to analyze_findings() for AI analysis and the result is passed to print_report().
- Save security_analyzer.py.
Seeing a syntax error when you save?
Check that the f-strings use matching quotes. Each print(f"...") line must have its opening and closing double quotes intact. Also confirm that the if __name__ == "__main__": block is at the very bottom of the file with no indentation.
Help me fix a syntax error in my security_analyzer.py
✔️ Awesome, I've got everything!
Great. Double check you have saved your file before moving on.
ⓧ I'd like to double check the full code
import argparse
import json
import sys
from scanners.terraform_scanner import scan_terraform_file
from scanners.iam_scanner import scan_iam_policy
from bedrock_analyzer import analyze_findings
def detect_file_type(file_path):
"""Detect whether a file is Terraform or IAM policy JSON."""
if file_path.endswith(".tf"):
return "terraform"
elif file_path.endswith(".json"):
return "iam"
else:
return None
def print_report(report):
"""Print a formatted security report to the terminal."""
print("\n" + "=" * 60)
print(" AI-POWERED CLOUD SECURITY ANALYSIS REPORT")
print("=" * 60)
if not report["findings"]:
print("\n No security issues found. Your configuration looks good!")
print("=" * 60)
return
print(f"\n Total findings: {len(report['findings'])}")
print("-" * 60)
for i, finding in enumerate(report["findings"], 1):
severity = finding.get("severity", "UNKNOWN")
severity_colors = {
"CRITICAL": "\033[91m",
"HIGH": "\033[93m",
"MEDIUM": "\033[33m",
"LOW": "\033[36m",
}
color = severity_colors.get(severity.upper(), "\033[0m")
reset = "\033[0m"
print(f"\n [{color}{severity.upper()}{reset}] Finding #{i}")
print(f" Resource: {finding.get('resource', 'N/A')}")
print(f" Issue: {finding.get('issue', 'N/A')}")
print(f"\n AI Analysis:")
print(f" {finding.get('ai_explanation', 'No analysis available')}")
if finding.get("remediation"):
print(f"\n Remediation:")
print(f" {finding.get('remediation')}")
print("-" * 60)
print("\n" + "=" * 60)
def main():
parser = argparse.ArgumentParser(
description="AI-Powered Cloud Security Analyzer"
)
parser.add_argument(
"files",
nargs="+",
help="Terraform (.tf) or IAM policy (.json) files to scan",
)
parser.add_argument(
"--region",
default="us-east-1",
help="AWS region for Bedrock (default: us-east-1)",
)
args = parser.parse_args()
all_findings = []
for file_path in args.files:
file_type = detect_file_type(file_path)
if file_type == "terraform":
print(f"Scanning Terraform file: {file_path}")
findings = scan_terraform_file(file_path)
all_findings.extend(findings)
elif file_type == "iam":
print(f"Scanning IAM policy file: {file_path}")
findings = scan_iam_policy(file_path)
all_findings.extend(findings)
else:
print(f"Skipping unsupported file type: {file_path}")
if not all_findings:
print("\nNo security issues found!")
return
print(f"\nFound {len(all_findings)} potential security issue(s).")
print("Sending findings to Amazon Bedrock for AI analysis...\n")
report = analyze_findings(all_findings, region=args.region)
print_report(report)
if __name__ == "__main__":
main()
Run the analyzer against your sample files
Everything is wired up. Time to scan both of your intentionally insecure sample files and see what the AI has to say about them.
- Make sure your terminal is in the security-analyzer folder and your virtual environment is active.
- Run the security analyzer against both sample files by running this command:
python security_analyzer.py samples/insecure_s3.tf samples/insecure_iam.json
- Wait a few seconds for Bedrock to respond. You should see a formatted report appear in your terminal with color-coded severity levels for each finding.
What should I see?
The output starts with scanning messages for each file, then shows the total number of findings. After a short delay while Bedrock processes the request, the full report prints.
Each finding displays a colored severity tag (CRITICAL, HIGH, MEDIUM, or LOW), the affected resource name, the issue description, an AI-generated explanation of the risk, and suggested remediation code.
Getting an AccessDeniedException?
This means your AWS credentials do not have permission to call Bedrock, or the Claude 3.5 Haiku model access has not been granted yet. Go back to the Bedrock console in us-east-1 and confirm model access is enabled for Anthropic Claude 3.5 Haiku.
Also check that your IAM user or role has the bedrock:InvokeModel permission.
Help me fix Bedrock access errors in my security analyzer
Your security analyzer is fully operational. You have a working CLI tool that combines static analysis with AI-powered explanation to produce actionable security reports. Next up, take on the Secret Mission to make this tool production-ready with CI/CD pipeline integration.
Secret mission
Add CI/CD Pipeline Integration with Pass/Fail Scoring
Your security analyzer gives great reports, but real DevSecOps teams need automated gates. Can you add a scoring system that makes your tool pass or fail in a CI/CD pipeline?
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely locally with on-demand API calls to Amazon Bedrock. There are no always-on cloud resources, so there are no ongoing costs when you stop running the analyzer.
Decide whether to keep your project files for future use, or delete them entirely.
Resources you used:
- Local security-analyzer project folder (Python files, virtual environment, sample files).
- Amazon Bedrock model access for Claude 3.5 Haiku (pay-per-request, no idle cost).
Why isn't Bedrock listed for deletion?
Bedrock foundation models are enabled by default in commercial AWS regions. You only pay when you make API calls. Leaving model access enabled costs nothing, and AWS re-enables access automatically on next invocation even if you revoke it.
Keep everything running
No action needed. Choose this if you plan to keep experimenting with the security analyzer or want to scan your own Terraform files.
- Your security-analyzer folder stays on your Desktop.
- Your Bedrock model access remains active at no cost until you make API calls.
- Your Python virtual environment stays available for future runs.
Pause - I'll come back to this later
There are no running processes or ongoing costs to pause. Your project folder and Bedrock access remain ready for when you return.
- If your virtual environment is activated, deactivate it by running:
deactivate
- Your project files remain on disk. Pick up where you left off by reactivating the virtual environment next time.
Delete - I don't want to use this again
Remove all local project files and start fresh. This cannot be undone.
- If your virtual environment is activated, deactivate it first by running:
deactivate
- Delete the project folder on macOS/Linux by running:
rm -rf ~/Desktop/security-analyzer
- On Windows, delete the project folder by running:
rmdir /s /q %USERPROFILE%\Desktop\security-analyzer
- Confirm the folder is gone by running:
ls ~/Desktop/security-analyzer
- You should see a "No such file or directory" message confirming the deletion.
- Your Bedrock model access costs nothing to leave enabled. No action is required on the AWS side.
That's a Wrap!
That's a Wrap!
Nice work! You just built an AI-powered cloud security analyzer that combines static code analysis with generative AI to detect and explain infrastructure vulnerabilities.
You've learned how to:
- Built a Python CLI tool that scans Terraform and IAM policy files for real-world security anti-patterns like wildcard permissions, public access, and missing encryption.
- Integrated Amazon Bedrock's Converse API with Claude 3.5 Haiku to generate intelligent risk explanations, attack scenarios, and remediation code.
- Generated professional security reports that combine automated scanning with AI-powered analysis, complete with severity ratings and actionable fixes.
- Secret Mission: Added CI/CD pipeline integration with a pass/fail scoring system for automated security gates.
Ready to quiz yourself?