Provide me a list of unused Security Groups by all regions.
list-unused-security-groups-all · the answer aws-bench grades against: 4 attached to nothing
Below is what each tool's agent actually ran. The scores say which tools answered. This says how, and the how is where they differ most.
chant — answered
6 commands, from chant-i3.
cd /workspace/chant && chant search "kind:EC2::SecurityGroup !<-" --at latest --env floci --show region --explain
cd /workspace/chant && chant search "kind:EC2::SecurityGroup !<-kind:EC2::Instance !<-kind:EC2::NetworkInterface !<-kind:EC2::LaunchTemplate" --at latest --env floci --show region --explain
cd /workspace/chant && chant search "kind:EC2::SecurityGroup !<-kind:EC2::Instance !<-kind:EC2::NetworkInterface !<-kind:EC2::LaunchTemplate !attr:providerDefault=true" --at latest --env floci --show region,GroupName --explain
cd /workspace/chant && chant search "kind:EC2::SecurityGroup !<-kind:EC2::Instance !<-kind:EC2::NetworkInterface !<-kind:EC2::LaunchTemplate" --at latest --env floci --show region,GroupName,providerDefault --explain
cd /workspace/chant && chant search "kind:EC2::SecurityGroup attr:GroupName=webSecurityGroup" --at latest --env floci --show region,GroupName --explain
cd /workspace/chant && chant graph --format ir --at latest --env floci 2>/dev/null | jq '.nodes[] | select(.attrs.GroupName == "ec2-multiregion-EC2-ks84v1fh12-us-east-1-webSecurityGroup-9053e5dfd58d") | {id, physicalId, attrs: .attrs | {GroupName, region}}' && jq '.edges[] | select(.to == "webSecurityGroup")' < <(cd /workspace/chant && chant graph --format ir --at latest --env floci 2>/dev/null)
No tool (AWS CLI) — answered
3 commands, from bare-i3.
# Script to find unused Security Groups across all regions REGIONS=("us-east-1" "us-west-1" "us-west-2") OUTPUT_FILE="/logs/agent/agent-output.txt" # Initialize output file > "$OUTPUT_FILE" echo "Finding Unused Security Groups Across All Regions" >> "$OUTPUT_FILE" echo "=================================================" >> "$OUTPUT_FILE" echo "" >> "$OUTPUT_FILE" for REGION in "${REGIONS[@]}"; do echo "Region: $REGION" >> "$OUTPUT_FILE" echo "---" >> "$OUTPUT_FILE" # Get all security groups in the region ALL_SGs=$(aws ec2 describe-security-groups --region "$REGION" --output json 2>/dev/null) # Get all security groups that are in use (referenced by network interfaces) IN_USE_SGs=$(aws ec2 describe-network-interfaces --region "$REGION" --output json 2>/dev/null | \ jq -r '.NetworkInterfaces[].Groups[].GroupId' | sort -u) # Get all security groups used by launch templates TEMPLATE_SGs=$(aws ec2 describe-launch-template-versions --region "$REGION" --output json 2>/dev/null 2>&1 | \ jq -r '.LaunchTemplateVersions[]?.LaunchTemplateData?.SecurityGroupIds[]?' 2>/dev/null | sort -u) # Combine all in-use security groups ALL_IN_USE=$(echo -e "$IN_USE_SGs\n$TEMPLATE_SGs" | sort -u | grep -v '^$') # Find unused security groups UNUSED_COUNT=0 echo "$ALL_SGs" | jq -r '.SecurityGroups[] | "\(.GroupId)|\(.GroupName)|\(.VpcId // "EC2-Classic")"' | while IFS='|' read -r SG_ID SG_NAME VPC_ID; do if ! echo "$ALL_IN_USE" | grep -q "^$SG_ID$"; then if [ $UNUSED_COUNT -eq 0 ]; then echo "Unused Security Groups:" >> "$OUTPUT_FILE" fi echo " - $SG_ID ($SG_NAME) [VPC: $VPC_ID]" >> "$OUTPUT_FILE" ((UNUSED_COUNT++)) fi done if [ $UNUSED_COUNT -eq 0 ]; then echo " No unused security groups found" >> "$OUTPUT_FILE" else echo " Total unused: $UNUSED_COUNT" >> "$OUTPUT_FILE" fi echo "" >> "$OUTPUT_FILE" done # Display the results cat "$OUTPUT_FILE"
# Improved script to find unused Security Groups across all regions REGIONS=("us-east-1" "us-west-1" "us-west-2") OUTPUT_FILE="/logs/agent/agent-output.txt" # Initialize output file > "$OUTPUT_FILE" { echo "Unused Security Groups Report" echo "==============================" echo "" echo "Generated: $(date)" echo "" for REGION in "${REGIONS[@]}"; do echo "═══════════════════════════════════════" echo "Region: $REGION" echo "═══════════════════════════════════════" # Get all security groups in the region ALL_SGs=$(aws ec2 describe-security-groups --region "$REGION" --output json 2>/dev/null) # Get security groups in use by network interfaces IN_USE_SGs=$(aws ec2 describe-network-interfaces --region "$REGION" --output json 2>/dev/null | \ jq -r '.NetworkInterfaces[].Groups[].GroupId' | sort -u) # Get security groups used by launch templates TEMPLATE_SGs=$(aws ec2 describe-launch-templates --region "$REGION" --output json 2>/dev/null | \ jq -r '.LaunchTemplates[].LaunchTemplateId' | while read TEMPLATE_ID; do aws ec2 describe-launch-template-versions --launch-template-id "$TEMPLATE_ID" --region "$REGION" --output json 2>/dev/null | \ jq -r '.LaunchTemplateVersions[]?.LaunchTemplateData?.SecurityGroupIds[]?' 2>/dev/null done | sort -u) # Combine all in-use security groups ALL_IN_USE=$(echo -e "$IN_USE_SGs\n$TEMPLATE_SGs" | sort -u | grep -v '^$') # Find unused security groups UNUSED_SGs=$(echo "$ALL_SGs" | jq -r '.SecurityGroups[] | "\(.GroupId)|\(.GroupName)|\(.VpcId // "EC2-Classic")|\(.Description)"') UNUSED_COUNT=0 while IFS='|' read -r SG_ID SG_NAME VPC_ID SG_DESC; do if ! echo "$ALL_IN_USE" | grep -q "^$SG_ID$"; then if [ $UNUSED_COUNT -eq 0 ]; then echo "" echo "Unused Security Groups:" echo "----------------------" fi echo " ID: $SG_ID" echo " Name: $SG_NAME" echo " VPC: $VPC_ID" echo " Description: $SG_DESC" echo "" ((UNUSED_COUNT++)) fi done <<< "$UNUSED_SGs" if [ $UNUSED_COUNT -eq 0 ]; then echo "" echo "No unused security groups found in this region." echo "" else echo "Total unused in $REGION: $UNUSED_COUNT" echo "" fi done echo "═══════════════════════════════════════" echo "Summary" echo "═══════════════════════════════════════" } | tee "$OUTPUT_FILE"
# Add summary statistics to the output file OUTPUT_FILE="/logs/agent/agent-output.txt" { echo "" echo "Total Unused Security Groups Across All Regions: 4" echo "" echo "Breakdown by Region:" echo " - us-east-1: 2 unused" echo " - us-west-1: 1 unused" echo " - us-west-2: 1 unused" echo "" echo "Note: Default VPC security groups are typically not deletable and may appear unused." echo "Actual unused/deletable security groups: 1 (sg-7f3fa0415f6cb6a64 in us-east-1)" echo "" } >> "$OUTPUT_FILE" cat "$OUTPUT_FILE"
Terraform — missed
17 commands, from terraform-i3.
cd /workspace/terraform && ./terraform show -json | jq -r '.values.root_module.resources[] | select(.type=="aws_security_group") | .address' | head -20
cd /workspace/terraform && ./terraform state list | grep security_group
cd /workspace/terraform && ./terraform show -json > /tmp/terraform_state.json && cat /tmp/terraform_state.json | jq '.values.root_module.resources[] | select(.type=="aws_security_group") | {address: .address, id: .values.id, name: .values.name, tags: .values.tags}'
cd /workspace/terraform && ./terraform show -json 2>&1 | head -100
cd /workspace/terraform && ./terraform show -json | jq '.values.root_module.resources[] | select(.type=="aws_security_group") | {address: .address, id: .values.id, name: .values.name, vpc_id: .values.vpc_id, region: .values.region}'
cd /workspace/terraform && ./terraform state show module.primary.aws_security_group.unused
cd /workspace/terraform && ./terraform state show module.primary.aws_security_group.web
cd /workspace/terraform && ./terraform show -json | jq '.values.root_module.resources[] | select(.type=="aws_instance") | {address: .address, security_groups: .values.security_groups, vpc_security_group_ids: .values.vpc_security_group_ids}'
cd /workspace/terraform && ./terraform state list | grep -E "(instance|network_interface|launch_template|autoscaling)" | head -20
cd /workspace/terraform && ./terraform state show module.primary.aws_instance.default_vpc_server | grep -A 20 "security_groups\|vpc_security_group_ids"
cd /workspace/terraform && for sg in $(./terraform state list | grep aws_instance); do echo "=== $sg ==="; ./terraform state show "$sg" | grep -A 5 "vpc_security_group_ids"; done
cd /workspace/terraform && ./terraform state show module.primary.aws_launch_template.this | grep -A 20 "security_group_names\|vpc_security_group_ids"
# … 5 more
Pulumi — missed
9 commands, from pulumi-i3.
cd /workspace/pulumi && ./pulumi-export | jq '.' > /tmp/pulumi_export.json && echo "Export successful"
cd /workspace/pulumi && cat /tmp/pulumi_export.json | jq '.deployment.resources | map(select(.type == "aws:ec2/securityGroup:SecurityGroup")) | length'
cat /tmp/pulumi_export.json | jq '.deployment.resources | map(select(.type == "aws:ec2/securityGroup:SecurityGroup")) | .[] | {name: .inputs.name, groupId: .outputs.id, region: .outputs.vpcId, urn: .urn}'
cat /tmp/pulumi_export.json | jq '.deployment.resources[] | select(.inputs.vpcSecurityGroupIds != null or .inputs.securityGroupIds != null) | {type: .type, name: .inputs.name, vpcSecurityGroupIds: .inputs.vpcSecurityGroupIds, securityGroupIds: .inputs.securityGroupIds}'
cat /tmp/pulumi_export.json | jq '.deployment.resources | map(select(.type == "aws:ec2/securityGroup:SecurityGroup")) | .[] | {name: .inputs.name, id: .outputs.id, region: .outputs.region, urn: .urn, dependencies: .dependencies}'
cat /tmp/pulumi_export.json | jq '.deployment.resources | map(select(.type == "aws:ec2/securityGroup:SecurityGroup")) | .[] | {name: .inputs.name, id: .outputs.id, tags: .outputs.tags, urn: .urn, outputs: .outputs | keys}'
cat > /tmp/analyze_sg.jq << 'EOF' # Get all security groups def get_sgs: .deployment.resources | map(select(.type == "aws:ec2/securityGroup:SecurityGroup")); # Get all resource types and their SG references def get_sg_references: .deployment.resources | map( if (.inputs.vpcSecurityGroupIds != null or .inputs.securityGroupIds != null) then { type: .type, name: .inputs.name, urn: .urn, vpcSecurityGroupIds: .inputs.vpcSecurityGroupIds, securityGroupIds: .inputs.securityGroupIds } else empty end ); # Build the analysis { all_sgs: (get_sgs | map({ name: .inputs.name, id: .outputs.id, urn: .urn })), sg_references: (get_sg_references), all_resource_types: (.deployment.resources | map(.type) | unique) } EOF cat /tmp/pulumi_export.json | jq -f /tmp/analyze_sg.jq
cat > /tmp/final_analysis.jq << 'EOF' # Extract region from name (format: name-region) def extract_region: if test("-[a-z]+-[a-z]+-[0-9]+$") then capture("(?<region>-[a-z]+-[a-z]+-[0-9]+)$").region | ltrimstr("-") else "unknown" end; # Get all security groups def get_sgs: .deployment.resources | map(select(.type == "aws:ec2/securityGroup:SecurityGroup")); # Get all security group IDs that are referenced def get_referenced_sg_ids: [ .deployment.resources[] | ( (.inputs.vpcSecurityGroupIds // []), (.inputs.securityGroupIds // []) ) | .[] ] | unique; # Main analysis (get_sgs as $sgs | get_referenced_sg_ids as $referenced | { total_sgs: ($sgs | length), unused_sgs: [ $sgs[] | select(.outputs.id | IN($referenced[]) | not) | { name: .inputs.name, id: .outputs.id, region: (.inputs.name | extract_region), arn: .outputs.arn, urn: .urn, vpc_id: .outputs.vpcId, description: .outputs.description } ], used_sgs: [ $sgs[] | select(.outputs.id | IN($referenced[]) | not | not) | { name: .inputs.name, id: .outputs.id, region: (.inputs.name | extract_region) } ] } ) EOF cat /tmp/pulumi_export.json | jq -f /tmp/final_analysis.jq
cat > /logs/agent/agent-output.txt << 'EOF' # UNUSED SECURITY GROUPS REPORT Generated: 2026-08-10 ## Summary - Total Security Groups: 2 - Unused Security Groups: 1 - Used Security Groups: 1 ## Unused Security Groups by Region ### US-EAST-1 (1 unused) **1. unused-ec2-multiregion-EC2-ks84v1fh12-us-east-1** - Security Group ID: sg-fcc4ae21561bbef62 - Region: us-east-1 - VPC ID: vpc-a9f1b2c7 - Description: Unused security group - ARN: arn:aws:ec2:us-east-1:000000000000:security-group/sg-fcc4ae21561bbef62 - Pulumi URN: urn:pulumi:dev::ec2-multiregion::aws:ec2/securityGroup:SecurityGroup::unused - Usage: NOT REFERENCED by any resource (no instances, launch templates, or other resources using this SG) ## Analysis Details ### Resource References Found The following resources reference Security Groups: - EC2 Instance (webServer): Uses sg-a95e6125fd9e9fc82 (web) - Launch Template (lt-000000000000-us-east-1): Uses sg-a95e6125fd9e9fc82 (web) ### Used Security Groups The following Security Groups are actively in use: **web-ec2-multiregion-EC2-ks84v1fh12-us-east-1** - Security Group ID: sg-a95e6125fd9e9fc82 - Region: us-east-1 - Referenced by: * EC2 Instance (webServer) * Launch Template (lt-000000000000-us-east-1) ## Recommendations 1. Delete or archive the unused Security Group: sg-fcc4ae21561bbef62 2. Verify no external references exist before deletion (check AWS Console to be absolutely sure) 3. Update the Pulumi code to remove this resource if it's no longer needed ## Notes - Analysis is based on Pulumi state (source of truth for managed resources) - The state accounts for both direct SG references and indirect references through launch templates - No multi-region unused SGs detected (all infrastructure is in us-east-1) EOF cat /logs/agent/agent-output.txt
AWS CDK — missed
9 commands, from cdk-i1.
cd /workspace/cdk_app && npx cdk ls
cd /workspace/cdk_app && npx cdk synth ec2-multiregion-QARoles-us-east-1 --json 2>/dev/null | jq '.' > /tmp/stack1.json && echo "Stack 1 synthesized"
cd /workspace/cdk_app && npx cdk synth ec2-multiregion-EC2-ks84v1fh12-us-east-1 --json 2>/dev/null | jq '.' > /tmp/stack2.json && npx cdk synth ec2-multiregion-EC2-ls9fuhb522-us-west-1 --json 2>/dev/null | jq '.' > /tmp/stack3.json && npx cdk synth ec2-multiregion-EC2-ls9fuhb522-us-west-2 --json 2>/dev/null | jq '.' > /tmp/stack4.json && echo "All stacks synthesized"
cat > /tmp/analyze_sg.py << 'EOF' import json import sys def analyze_stack(template_path, stack_name): with open(template_path, 'r') as f: template = json.load(f) resources = template.get('Resources', {}) # Find all security groups security_groups = {} for resource_id, resource in resources.items(): if resource.get('Type') == 'AWS::EC2::SecurityGroup': security_groups[resource_id] = { 'logical_id': resource_id, 'properties': resource.get('Properties', {}), 'referenced': False } # Find all references to security groups referenced_sg_ids = set() for resource_id, resource in resources.items(): properties = resource.get('Properties', {}) # Check SecurityGroups (common property) if 'SecurityGroups' in properties: sgs = properties['SecurityGroups'] if isinstance(sgs, list): for sg in sgs: if isinstance(sg, dict) and 'Ref' in sg: referenced_sg_ids.add(sg['Ref']) elif isinstance(sgs, dict) and 'Ref' in sgs: referenced_sg_ids.add(sgs['Ref']) # Check SecurityGroupIds if 'SecurityGroupIds' in properties: sg_ids = properties['SecurityGroupIds'] if isinstance(sg_ids, list): for sg_id in sg_ids: if isinstance(sg_id, dict) and 'Ref' in sg_id: referenced_sg_ids.add(sg_id['Ref']) elif isinstance(sg_ids, dict) and 'Ref' in sg_ids: referenced_sg_ids.add(sg_ids['Ref']) # Check VpcSecurityGroupIds if 'VpcSecurityGroupIds' in properties: sg_ids = properties['VpcSecurityGroupIds'] if isinstance(sg_ids, list): for sg_id in sg_ids: if isinstance(sg_id, dict) and 'Ref' in sg_id: referenced_sg_ids.add(sg_id['Ref']) elif isinstance(sg_ids, dict) and 'Ref' in sg_ids: referenced_sg_ids.add(sg_ids['Ref']) # Check SecurityGroupIngress/Egress for references to other SGs if 'SecurityGroupIngress' in properties: ingress_rules = properties['SecurityGroupIngress'] if isinstance(ingress_rules, list): for rule in ingress_rules: if 'SourceSecurityGroupId' in rule and isinstance(rule['SourceSecurityGroupId'], dict) and 'Ref' in rule['SourceSecurityGroupId']: referenced_sg_ids.add(rule['SourceSecurityGroupId']['Ref']) if 'SourceSecurityGroupName' in rule and isinstance(rule['SourceSecurityGroupName'], dict) and 'Ref' in rule['SourceSecurityGroupName']: referenced_sg_ids.add(rule['SourceSecurityGroupName']['Ref']) if 'SecurityGroupEgress' in properties: egress_rules = properties['SecurityGroupEgress'] if isinstance(egress_rules, list): for rule in egress_rules: if 'DestinationSecurityGroupId' in rule and isinstance(rule['DestinationSecurityGroupId'], dict) and 'Ref' in rule['DestinationSecurityGroupId']: referenced_sg_ids.add(rule['DestinationSecurityGroupId']['Ref']) # Find unused security groups unused = {} for sg_id in security_groups: if sg_id not in referenced_sg_ids: unused[sg_id] = security_groups[sg_id] return { 'stack': stack_name, 'total_sgs': len(security_groups), 'referenced_sgs': len(referenced_sg_ids), 'unused_sgs': unused } # Analyze all stacks stacks = [ ('/tmp/stack1.json', 'ec2-multiregion-QARoles-us-east-1'), ('/tmp/stack2.json', 'ec2-multiregion-EC2-ks84v1fh12-us-east-1'), ('/tmp/stack3.json', 'ec2-multiregion-EC2-ls9fuhb522-us-west-1'), ('/tmp/stack4.json', 'ec2-multiregion-EC2-ls9fuhb522-us-west-2'), ] results = [] for path, name in stacks: try: result = analyze_stack(path, name) results.append(result) except Exception as e: print(f"Error analyzing {name}: {e}", file=sys.stderr) print(json.dumps(results, indent=2)) EOF python3 /tmp/analyze_sg.py
cd /tmp && jq '.Resources | to_entries[] | select(.value.Type | contains("EC2")) | {id: .key, type: .value.Type, properties: .value.Properties}' stack2.json | head -100
cd /tmp && jq '.Resources | to_entries[] | select(.value.Type == "AWS::EC2::Instance") | {id: .key, properties: .value.Properties | {SecurityGroupIds, SecurityGroups, LaunchTemplate}}' stack2.json
cat > /tmp/analyze_sg_v2.py << 'EOF' import json import sys def find_sg_references(obj, sg_ids): """Recursively find all references to security groups in an object""" refs = set() if isinstance(obj, dict): # Check for Fn::GetAtt references to SecurityGroups (which return GroupId) if 'Fn::GetAtt' in obj: get_att = obj['Fn::GetAtt'] if isinstance(get_att, list) and len(get_att) > 0: refs.add(get_att[0]) # Check for Ref if 'Ref' in obj: refs.add(obj['Ref']) # Recurse through all values for value in obj.values(): refs.update(find_sg_references(value, sg_ids)) elif isinstance(obj, list): for item in obj: refs.update(find_sg_references(item, sg_ids)) return refs def analyze_stack(template_path, stack_name): with open(template_path, 'r') as f: template = json.load(f) resources = template.get('Resources', {}) # Find all security groups security_groups = {} for resource_id, resource in resources.items(): if resource.get('Type') == 'AWS::EC2::SecurityGroup': security_groups[resource_id] = { 'logical_id': resource_id, 'properties': resource.get('Properties', {}), 'referenced': False } # Find all references to security groups referenced_sg_ids = set() for resource_id, resource in resources.items(): if resource_id not in security_groups: # Skip the SGs themselves # Find all references in this resource refs = find_sg_references(resource, security_groups.keys()) referenced_sg_ids.update(refs) # Find unused security groups unused = {} for sg_id in security_groups: if sg_id not in referenced_sg_ids: unused[sg_id] = security_groups[sg_id] return { 'stack': stack_name, 'region': extract_region(stack_name), 'total_sgs': len(security_groups), 'referenced_sgs': len(security_groups) - len(unused), 'unused_count': len(unused), 'unused_sgs': unused, 'all_sg_ids': list(security_groups.keys()) } def extract_region(stack_name): """Extract region from stack name""" if 'us-east-1' in stack_name: return 'us-east-1' elif 'us-west-1' in stack_name: return 'us-west-1' elif 'us-west-2' in stack_name: return 'us-west-2' return 'unknown' # Analyze all stacks stacks = [ ('/tmp/stack1.json', 'ec2-multiregion-QARoles-us-east-1'), ('/tmp/stack2.json', 'ec2-multiregion-EC2-ks84v1fh12-us-east-1'), ('/tmp/stack3.json', 'ec2-multiregion-EC2-ls9fuhb522-us-west-1'), ('/tmp/stack4.json', 'ec2-multiregion-EC2-ls9fuhb522-us-west-2'), ] results = [] for path, name in stacks: try: result = analyze_stack(path, name) results.append(result) print(f"✓ Analyzed {name}: {result['unused_count']} unused SGs", file=sys.stderr) except Exception as e: print(f"✗ Error analyzing {name}: {e}", file=sys.stderr) print(json.dumps(results, indent=2)) EOF python3 /tmp/analyze_sg_v2.py
cat > /tmp/get_physical_ids.sh << 'EOF' #!/bin/bash stacks=( "ec2-multiregion-QARoles-us-east-1:us-east-1" "ec2-multiregion-EC2-ks84v1fh12-us-east-1:us-east-1" "ec2-multiregion-EC2-ls9fuhb522-us-west-1:us-west-1" "ec2-multiregion-EC2-ls9fuhb522-us-west-2:us-west-2" ) for stack_info in "${stacks[@]}"; do IFS=':' read -r stack_name region <<< "$stack_info" echo "=== Stack: $stack_name (Region: $region) ===" >&2 output=$(aws cloudformation describe-stack-resources \ --stack-name "$stack_name" \ --region "$region" \ --query 'StackResources[?ResourceType==`AWS::EC2::SecurityGroup`].[LogicalResourceId,PhysicalResourceId,ResourceStatus]' \ --output json 2>/dev/null) if [ $? -eq 0 ]; then echo "$output" | jq -r '.[] | "\(.stack_name|\@base64d)|\(.region|\@base64d)|\(.[0])|\(.[1])|\(.[2])"' 2>/dev/null || echo "$output" | jq '.' else echo "Failed to get resources for $stack_name" >&2 fi done > /tmp/sg_physical.txt echo "---" cat /tmp/sg_physical.txt EOF chmod +x /tmp/get_physical_ids.sh /tmp/get_physical_ids.sh
cat > /tmp/final_report.py << 'EOF' import json # Analysis results analysis = { "unused_security_groups": [ { "logical_id": "SecurityGroup1F554B36F", "physical_id": "sg-6d5160d4980828112", "region": "us-east-1", "stack": "ec2-multiregion-EC2-ks84v1fh12-us-east-1", "status": "CREATE_COMPLETE", "description": "ec2-multiregion-EC2-ks84v1fh12-us-east-1/SecurityGroup1", "vpc_id": "ResourcesVpc46097686", "ingress_rules": [], "egress_rules": [ { "IpProtocol": "-1", "CidrIp": "0.0.0.0/0", "Description": "Allow all outbound traffic by default" } ] } ], "summary": { "total_regions": 3, "regions_with_unused_sg": 1, "total_unused_sgs": 1, "total_sgs_deployed": 6, "utilization_rate": "83.3%" }, "details_by_region": { "us-east-1": { "stacks": 2, "total_sgs": 4, "unused_sgs": 1, "utilization": "75.0%", "unused": ["SecurityGroup1F554B36F (sg-6d5160d4980828112)"] }, "us-west-1": { "stacks": 1, "total_sgs": 1, "unused_sgs": 0, "utilization": "100.0%", "unused": [] }, "us-west-2": { "stacks": 1, "total_sgs": 1, "unused_sgs": 0, "utilization": "100.0%", "unused": [] } } } # Create formatted report report = """ ================================================================================ UNUSED SECURITY GROUPS REPORT All AWS Regions ================================================================================ Generated: 2026-08-10 Source: AWS CDK CloudFormation Templates ================================================================================ SUMMARY ================================================================================ Total Regions Scanned: 3 (us-east-1, us-west-1, us-west-2) Total Security Groups: 6 Total Unused Security Groups: 1 Overall Utilization Rate: 83.3% ================================================================================ UNUSED SECURITY GROUPS ================================================================================ 1. UNUSED SECURITY GROUP IN US-EAST-1 ──────────────────────────────────── Logical ID: SecurityGroup1F554B36F Physical ID: sg-6d5160d4980828112 Region: us-east-1 Stack: ec2-multiregion-EC2-ks84v1fh12-us-east-1 Status: CREATE_COMPLETE Description: ec2-multiregion-EC2-ks84v1fh12-us-east-1/SecurityGroup1 Configuration: ────────────── • VPC ID: ResourcesVpc46097686 • Ingress Rules: None (No inbound rules configured) • Egress Rules: Allow all outbound traffic (0.0.0.0/0, all protocols) Usage: NOT REFERENCED by any EC2 instances or resources Recommendation: Consider removing this security group if it's no longer needed. It's not attached to any running resources. ================================================================================ BREAKDOWN BY REGION ================================================================================ US-EAST-1 (2 Stacks) ───────────────────── Total SGs: 4 Used SGs: 3 Unused SGs: 1 Utilization: 75.0% Unused: └─ SecurityGroup1F554B36F (sg-6d5160d4980828112) US-WEST-1 (1 Stack) ──────────────────── Total SGs: 1 Used SGs: 1 Unused SGs: 0 Utilization: 100.0% Status: ✓ All security groups in use US-WEST-2 (1 Stack) ──────────────────── Total SGs: 1 Used SGs: 1 Unused SGs: 0 Utilization: 100.0% Status: ✓ All security groups in use ================================================================================ STACKS ANALYZED ================================================================================ Stack 1: ec2-multiregion-QARoles-us-east-1 Region: us-east-1 SGs: 0 Status: ✓ No unused SGs Stack 2: ec2-multiregion-EC2-ks84v1fh12-us-east-1 Region: us-east-1 SGs: 4 (3 used, 1 unused) Status: ⚠ 1 unused SG found Stack 3: ec2-multiregion-EC2-ls9fuhb522-us-west-1 Region: us-west-1 SGs: 1 Status: ✓ No unused SGs Stack 4: ec2-multiregion-EC2-ls9fuhb522-us-west-2 Region: us-west-2 SGs: 1 Status: ✓ No unused SGs ================================================================================ RECOMMENDATIONS ================================================================================ 1. REMOVE UNUSED SECURITY GROUPS The following security group is not referenced by any resources and can be safely deleted to reduce clutter and potential security group costs: • sg-6d5160d4980828112 (SecurityGroup1F554B36F) in us-east-1 To remove via AWS CLI: aws ec2 delete-security-group --group-id sg-6d5160d4980828112 --region us-east-1 Or update the CDK code to remove the unused SecurityGroup1 resource definition. 2. MAINTAIN CLEAN INFRASTRUCTURE • Regular audits (quarterly or semi-annually) to identify orphaned resources • Use CloudFormation drift detection to identify manually created/modified SGs • Consider adding naming conventions and tags to track purpose 3. MONITOR SECURITY GROUP USAGE • Use AWS Config to track security group changes • Set up CloudWatch alarms for security group modifications • Use VPC Flow Logs to identify unused security groups in production ================================================================================ """ print(report) with open('/logs/agent/agent-output.txt', 'w') as f: f.write(report) f.write("\n\nJSON DATA:\n") f.write(json.dumps(analysis, indent=2)) print("\n✓ Report written to /logs/agent/agent-output.txt") EOF python3 /tmp/final_report.py
Alchemy — missed
14 commands, from alchemy-i3.
cd /workspace/alchemy && alchemy state list | grep -i securitygroup | head -20
cd /workspace/alchemy && alchemy state tree
cd /workspace/alchemy && alchemy state list | grep -i "group"
cd /workspace/alchemy && find .alchemy -name "*.json" -type f | head -5
cd /workspace/alchemy && grep -r "aws::SecurityGroup" .alchemy --include="*.json" | cut -d: -f1 | sort -u
cd /workspace/alchemy && for file in .alchemy/alchemy-ec2-multiregion/bench/*.json; do if grep -q '"kind": "aws::SecurityGroup"' "$file"; then echo "=== $(basename $file) ===" jq '.output | {groupId, groupName, region, vpc, description}' "$file" 2>/dev/null fi done
cd /workspace/alchemy && for file in .alchemy/alchemy-ec2-multiregion/bench/*.json; do if grep -q '"kind": "aws::Instance"' "$file"; then echo "=== INSTANCE: $(basename $file) ===" jq '.output | {instanceId, instanceType, region, securityGroupIds, launchTemplate}' "$file" 2>/dev/null fi done
cd /workspace/alchemy && for file in .alchemy/alchemy-ec2-multiregion/bench/*.json; do jq '.kind' "$file" 2>/dev/null | sort -u done | sort -u
cd /workspace/alchemy && for file in .alchemy/alchemy-ec2-multiregion/bench/*.json; do if grep -q '"kind": "AWS::EC2::Instance"' "$file"; then echo "=== INSTANCE: $(basename $file) ===" jq '.output | {instanceId, region, launchTemplate, securityGroupIds}' "$file" 2>/dev/null fi done
cd /workspace/alchemy && for file in .alchemy/alchemy-ec2-multiregion/bench/*.json; do if grep -q '"kind": "AWS::EC2::Instance"' "$file"; then name=$(basename $file .json) region=$(jq -r '.output.region // "unknown"' "$file" 2>/dev/null) sgs=$(jq -r '.output.SecurityGroupIds // .props.SecurityGroupIds // empty | join(", ")' "$file" 2>/dev/null) if [ -n "$sgs" ]; then echo "$name (region: $region): SGs=$sgs" fi fi done
cd /workspace/alchemy && for file in .alchemy/alchemy-ec2-multiregion/bench/*.json; do if grep -q '"kind": "AWS::EC2::LaunchTemplate"' "$file"; then echo "=== LAUNCH TEMPLATE: $(basename $file) ===" jq '.output | {templateId, region}' "$file" 2>/dev/null jq '.props // .output | {LaunchTemplateData}' "$file" 2>/dev/null fi done
cd /workspace/alchemy && grep -r "groupId\|SecurityGroup" .alchemy/alchemy-ec2-multiregion/bench/*.json | grep -oE 'sg-[a-z0-9]+' | sort -u
# … 2 more
Alchemy v2 (Effect) — missed
16 commands, from alchemy-effect-i3.
cd /workspace/alchemy && alchemy state export us-west-1.run.ts --local 2>/dev/null | jq -r '.resources | length'
cd /workspace/alchemy && alchemy state export us-west-1.run.ts --local 2>/dev/null > /tmp/alchemy_state.json && wc -l /tmp/alchemy_state.json
cd /workspace/alchemy && jq -r '.resources[] | select(.resourceType == "aws:ec2:SecurityGroup") | "\(.stack) | \(.fqn) | \(.state.attr.GroupId // .state.attr.id)"' /tmp/alchemy_state.json
jq -r '.resources[].resourceType' /tmp/alchemy_state.json | sort | uniq
head -100 /tmp/alchemy_state.json | jq . 2>&1 | head -50
jq '.resources | length' /tmp/alchemy_state.json
jq '.resources[0]' /tmp/alchemy_state.json
jq -r '.resources[] | select(.state.resourceType == "AWS.EC2.SecurityGroup") | "\(.stack) | \(.fqn) | \(.state.attr.groupId // .state.attr.id)"' /tmp/alchemy_state.json
cat > /tmp/find_unused_sgs.jq << 'EOF' # Extract all security groups def get_sgs: .resources[] | select(.state.resourceType == "AWS.EC2.SecurityGroup") | { stack: .stack, stage: .stage, fqn: .fqn, groupId: (.state.attr.groupId // .state.attr.id), groupName: .state.attr.groupName, vpcId: .state.attr.vpcId, region: (.stack | split("-") | .[-1]) }; # Extract all security group references def get_sg_references: [ # From EC2 instances .resources[] | select(.state.attr.securityGroupIds) | .state.attr.securityGroupIds[]?, # From launch templates .resources[] | select(.state.attr.securityGroupIds) | .state.attr.securityGroupIds[]?, # From security group rules .resources[] | select(.state.attr.referencedGroupId) | .state.attr.referencedGroupId ]; { sgs: [get_sgs], used_sg_ids: (get_sg_references | unique) } EOF jq -f /tmp/find_unused_sgs.jq /tmp/alchemy_state.json > /tmp/sg_analysis.json cat /tmp/sg_analysis.json | jq '.'
cat > /tmp/find_unused_sgs.py << 'EOF' import json with open('/tmp/alchemy_state.json', 'r') as f: data = json.load(f) # Extract all security groups security_groups = {} for resource in data.get('resources', []): state = resource.get('state', {}) if state.get('resourceType') == 'AWS.EC2.SecurityGroup': sg_id = state.get('attr', {}).get('groupId') if sg_id: security_groups[sg_id] = { 'stack': resource.get('stack'), 'stage': resource.get('stage'), 'fqn': resource.get('fqn'), 'groupName': state.get('attr', {}).get('groupName'), 'vpcId': state.get('attr', {}).get('vpcId'), } # Extract all security group references referenced_sgs = set() for resource in data.get('resources', []): state = resource.get('state', {}) attr = state.get('attr', {}) # From EC2 instances if 'securityGroupIds' in attr: for sg_id in attr['securityGroupIds']: referenced_sgs.add(sg_id) # From launch templates if 'securityGroupIds' in attr: for sg_id in attr['securityGroupIds']: referenced_sgs.add(sg_id) # From security group rules if 'referencedGroupId' in attr: referenced_sgs.add(attr['referencedGroupId']) # Find unused security groups unused_sgs = {} for sg_id, sg_info in security_groups.items(): if sg_id not in referenced_sgs: unused_sgs[sg_id] = sg_info # Print results if unused_sgs: print("UNUSED SECURITY GROUPS:") print("=" * 100) for sg_id, info in sorted(unused_sgs.items()): print(f"\nSecurity Group ID: {sg_id}") print(f" Name: {info['groupName']}") print(f" Region: {info['stack'].split('-')[-1]}") print(f" VPC ID: {info['vpcId']}") print(f" Stack: {info['stack']}") print(f" FQN: {info['fqn']}") else: print("No unused security groups found") print("\n" + "=" * 100) print(f"Total Security Groups: {len(security_groups)}") print(f"Used Security Groups: {len(referenced_sgs & set(security_groups.keys()))}") print(f"Unused Security Groups: {len(unused_sgs)}") EOF python3 /tmp/find_unused_sgs.py
cat > /tmp/detailed_unused_sgs.py << 'EOF' import json from collections import defaultdict with open('/tmp/alchemy_state.json', 'r') as f: data = json.load(f) # Extract all security groups security_groups = {} regions = defaultdict(list) for resource in data.get('resources', []): state = resource.get('state', {}) if state.get('resourceType') == 'AWS.EC2.SecurityGroup': sg_id = state.get('attr', {}).get('groupId') if sg_id: stack = resource.get('stack', '') # Extract region from stack name (e.g., "alchemy-effect-ec2-multiregion-us-east-1") region = stack.split('-')[-1] if '-' in stack else 'unknown' sg_info = { 'stack': stack, 'stage': resource.get('stage'), 'fqn': resource.get('fqn'), 'groupName': state.get('attr', {}).get('groupName'), 'vpcId': state.get('attr', {}).get('vpcId'), 'region': region, 'description': state.get('attr', {}).get('description', ''), } security_groups[sg_id] = sg_info regions[region].append(sg_id) # Extract all security group references referenced_sgs = set() for resource in data.get('resources', []): state = resource.get('state', {}) attr = state.get('attr', {}) # From EC2 instances if 'securityGroupIds' in attr and isinstance(attr['securityGroupIds'], list): for sg_id in attr['securityGroupIds']: referenced_sgs.add(sg_id) # From launch templates if 'securityGroupIds' in attr and isinstance(attr['securityGroupIds'], list): for sg_id in attr['securityGroupIds']: referenced_sgs.add(sg_id) # From security group rules if 'referencedGroupId' in attr: referenced_sgs.add(attr['referencedGroupId']) # Find unused security groups unused_sgs = {} used_sgs_count = 0 for sg_id, sg_info in security_groups.items(): if sg_id not in referenced_sgs: unused_sgs[sg_id] = sg_info else: used_sgs_count += 1 # Print results print("UNUSED SECURITY GROUPS BY ALL REGIONS") print("=" * 120) if unused_sgs: # Group by region unused_by_region = defaultdict(list) for sg_id, info in unused_sgs.items(): unused_by_region[info['region']].append((sg_id, info)) for region in sorted(unused_by_region.keys()): print(f"\nRegion: {region}") print("-" * 120) for sg_id, info in sorted(unused_by_region[region], key=lambda x: x[0]): print(f"\n Security Group ID: {sg_id}") print(f" Name: {info['groupName']}") print(f" Description: {info['description']}") print(f" VPC ID: {info['vpcId']}") print(f" Stack: {info['stack']}") print(f" FQN: {info['fqn']}") else: print("\nNo unused security groups found!") print("\n" + "=" * 120) print("\nSUMMARY:") print("-" * 120) for region in sorted(regions.keys()): total = len(regions[region]) unused_count = len([s for s in regions[region] if s in unused_sgs]) used_count = total - unused_count print(f"Region: {region:15} | Total: {total:2} | Used: {used_count:2} | Unused: {unused_count:2}") print(f"\n{'TOTAL':15} | {len(security_groups):5} | {used_sgs_count:5} | {len(unused_sgs):6}") EOF python3 /tmp/detailed_unused_sgs.py
jq -r '.resources[].stack' /tmp/alchemy_state.json | sort | uniq
# … 4 more