Build an AI AWS Security Auditor with Claude API and Boto3
Use Python, boto3, and the Claude API to automatically audit your AWS environment for security misconfigurations and get AI-powered remediation recommendations.
Security teams spend hours reviewing AWS configs manually. In this project you will build a Python script that uses boto3 to collect your AWS security posture, sends it to the Claude API for analysis, and produces a prioritised remediation report. Run it weekly on a schedule and email the results.
What the Auditor Checks
- S3 buckets with public access enabled
- Security groups with
0.0.0.0/0inbound rules - IAM users without MFA enabled
- RDS instances that are publicly accessible
- CloudTrail status per region
Prerequisites
pip install anthropic boto3Set your credentials:
export AWS_DEFAULT_REGION=us-east-1
export ANTHROPIC_API_KEY=sk-ant-...The AWS credentials need read-only permissions: SecurityAudit managed policy covers everything used here.
The Audit Script
import boto3
import json
import anthropic
from datetime import datetime
def audit_s3_public_buckets():
s3 = boto3.client('s3')
findings = []
buckets = s3.list_buckets().get('Buckets', [])
for bucket in buckets:
name = bucket['Name']
try:
acl = s3.get_public_access_block(Bucket=name)
config = acl['PublicAccessBlockConfiguration']
if not all([
config.get('BlockPublicAcls', False),
config.get('BlockPublicPolicy', False),
config.get('IgnorePublicAcls', False),
config.get('RestrictPublicBuckets', False)
]):
findings.append({
"resource": f"s3://{name}",
"issue": "Public access block not fully enabled",
"severity": "HIGH"
})
except s3.exceptions.NoSuchPublicAccessBlockConfiguration:
findings.append({
"resource": f"s3://{name}",
"issue": "No public access block configuration set",
"severity": "HIGH"
})
except Exception:
pass
return findings
def audit_security_groups():
ec2 = boto3.client('ec2')
findings = []
paginator = ec2.get_paginator('describe_security_groups')
for page in paginator.paginate():
for sg in page['SecurityGroups']:
for rule in sg.get('IpPermissions', []):
for ip_range in rule.get('IpRanges', []):
if ip_range.get('CidrIp') == '0.0.0.0/0':
port = rule.get('FromPort', 'ALL')
findings.append({
"resource": f"sg/{sg['GroupId']} ({sg['GroupName']})",
"issue": f"Inbound 0.0.0.0/0 on port {port}",
"severity": "HIGH" if port in [22, 3389, 3306, 5432] else "MEDIUM"
})
return findings
def audit_iam_mfa():
iam = boto3.client('iam')
findings = []
paginator = iam.get_paginator('list_users')
for page in paginator.paginate():
for user in page['Users']:
username = user['UserName']
mfa_devices = iam.list_mfa_devices(UserName=username)['MFADevices']
if not mfa_devices:
# Only flag users that have console access
try:
iam.get_login_profile(UserName=username)
findings.append({
"resource": f"iam/user/{username}",
"issue": "Console access without MFA",
"severity": "CRITICAL"
})
except iam.exceptions.NoSuchEntityException:
pass # No console access, skip
return findings
def audit_rds_public():
rds = boto3.client('rds')
findings = []
paginator = rds.get_paginator('describe_db_instances')
for page in paginator.paginate():
for db in page['DBInstances']:
if db.get('PubliclyAccessible', False):
findings.append({
"resource": f"rds/{db['DBInstanceIdentifier']}",
"issue": f"RDS instance publicly accessible ({db['Engine']})",
"severity": "CRITICAL"
})
return findings
def audit_cloudtrail():
ct = boto3.client('cloudtrail')
findings = []
try:
trails = ct.describe_trails(includeShadowTrails=False)['trailList']
if not trails:
findings.append({
"resource": "cloudtrail",
"issue": "No CloudTrail trails configured",
"severity": "HIGH"
})
else:
for trail in trails:
status = ct.get_trail_status(Name=trail['TrailARN'])
if not status.get('IsLogging', False):
findings.append({
"resource": f"cloudtrail/{trail['Name']}",
"issue": "CloudTrail trail exists but logging is disabled",
"severity": "HIGH"
})
except Exception as e:
findings.append({
"resource": "cloudtrail",
"issue": f"Could not check CloudTrail: {str(e)}",
"severity": "UNKNOWN"
})
return findings
def analyse_with_claude(findings: list) -> str:
client = anthropic.Anthropic()
findings_text = json.dumps(findings, indent=2)
message = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=2048,
messages=[
{
"role": "user",
"content": f"""You are an AWS security expert. Analyse these security findings from an automated AWS audit and provide:
1. Executive summary (2-3 sentences for a non-technical manager)
2. Top 3 critical risks to fix immediately, with specific AWS CLI or console steps to remediate each
3. Quick wins that can be fixed in under 10 minutes
4. Overall security score out of 10
Findings:
{findings_text}
Be specific and actionable. Include the exact AWS CLI commands where possible."""
}
]
)
return message.content[0].text
def run_audit():
print(f"Starting AWS Security Audit at {datetime.now().isoformat()}")
print("=" * 60)
all_findings = []
print("Checking S3 public access...")
all_findings.extend(audit_s3_public_buckets())
print("Checking security groups...")
all_findings.extend(audit_security_groups())
print("Checking IAM MFA...")
all_findings.extend(audit_iam_mfa())
print("Checking RDS public access...")
all_findings.extend(audit_rds_public())
print("Checking CloudTrail...")
all_findings.extend(audit_cloudtrail())
print(f"\nFound {len(all_findings)} findings. Sending to Claude for analysis...\n")
analysis = analyse_with_claude(all_findings)
report = {
"audit_date": datetime.now().isoformat(),
"account_id": boto3.client('sts').get_caller_identity()['Account'],
"total_findings": len(all_findings),
"findings_by_severity": {
"CRITICAL": len([f for f in all_findings if f['severity'] == 'CRITICAL']),
"HIGH": len([f for f in all_findings if f['severity'] == 'HIGH']),
"MEDIUM": len([f for f in all_findings if f['severity'] == 'MEDIUM']),
},
"raw_findings": all_findings,
"ai_analysis": analysis
}
with open("security-report.json", "w") as f:
json.dump(report, f, indent=2)
print("=" * 60)
print("AI ANALYSIS")
print("=" * 60)
print(analysis)
print(f"\nFull report saved to security-report.json")
return report
if __name__ == "__main__":
run_audit()Sample Output
Starting AWS Security Audit at 2026-06-26T09:15:32
============================================================
Checking S3 public access...
Checking security groups...
Checking IAM MFA...
Checking RDS public access...
Checking CloudTrail...
Found 7 findings. Sending to Claude for analysis...
============================================================
AI ANALYSIS
============================================================
Executive Summary: Your AWS account has 2 critical security
gaps that expose sensitive data and systems to unauthorized
access. IAM users with console access lack MFA, and one RDS
instance is publicly reachable from the internet.
Top 3 Critical Risks:
1. IAM User Without MFA (CRITICAL)
User 'deploy-user' has console access with no MFA.
Fix: aws iam create-virtual-mfa-device --virtual-mfa-device-name deploy-user-mfa
Then enforce MFA via IAM policy or enable org-level SCP.
2. RDS Instance Publicly Accessible (CRITICAL)
prod-postgres-db is reachable from the internet.
Fix: aws rds modify-db-instance --db-instance-identifier prod-postgres-db
--no-publicly-accessible --apply-immediately
3. Security Group with SSH Open to World (HIGH)
sg-0abc123 allows 0.0.0.0/0 on port 22.
Fix: Replace with your office CIDR or use AWS SSM Session Manager instead of SSH.
Quick Wins (under 10 minutes):
- Enable S3 Block Public Access at account level (1 click in console)
- Delete unused IAM users without recent activity
Overall Security Score: 4/10
Running Weekly with GitHub Actions
Add this to .github/workflows/security-audit.yml:
name: Weekly AWS Security Audit
on:
schedule:
- cron: '0 8 * * 1'
workflow_dispatch:
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install dependencies
run: pip install anthropic boto3
- name: Run audit
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: us-east-1
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: python audit.py
- name: Upload report
uses: actions/upload-artifact@v4
with:
name: security-report-${{ github.run_id }}
path: security-report.jsonThe report artifact is available in the Actions UI. Add an SES or SMTP step to email it to your team.
This audit costs roughly $0.002 per run using claude-haiku-4-5 — about $0.10 per year. The IAM SecurityAudit managed policy means you can run it with read-only access and never worry about the script making unintended changes.
For more AWS security hardening, see our DevSecOps pipeline guide.
Today I Fixed
Short real fixes from production — posted daily
Stay ahead of the curve
Get the latest DevOps, Kubernetes, AWS, and AI/ML guides delivered straight to your inbox. No spam — just practical engineering content.
Related Articles
Build an AI-Powered Dockerfile Security Scanner with Claude
Build a tool that scans Dockerfiles for security issues using Claude API — finds hardcoded secrets, root users, unscanned base images, and missing security best practices.
Build an AI Kubernetes Resource Rightsizer with Claude API
Build a Python script that reads kubectl top output and current resource requests/limits, sends it to Claude API (claude-haiku-4-5), and gets back specific CPU/memory rightsizing recommendations to cut cloud costs by 30-40%.
Build an AI Terraform Plan Reviewer with Claude API
Automatically review terraform plan output with Claude API to catch risky changes, unintended destroys, and security issues before they hit production.