Back to Checks
aws

Non-default EC2 security group is in use

ec2_securitygroup_not_used
Service: ec2Severity: low
OfficialBuilt-inVerified
Categories:trust-boundaries
Resource Type:AwsEc2SecurityGroup

EC2 security groups, except default, are assessed for unused status: zero attached network interfaces, no AWS Lambda associations, no AWS Batch compute environment associations, and no references from other security groups.

Risk

Orphaned security groups may later be attached with overly permissive rules without review, enabling unintended inbound or lateral access that compromises confidentiality and integrity. They also create configuration drift, increasing the chance of misapplied access controls.

Run this check with Prowler

Run in Prowler Cloud
Run
prowler aws --checks ec2_securitygroup_not_used

Recommendation

Apply least privilege and strong lifecycle management: delete or quarantine unused security groups, enforce ownership tags and retention policies, review regularly, and manage changes via IaC with approvals. Restrict who can attach groups and use guardrails to prevent reuse of stale or permissive groups.

Remediation

CLI

aws ec2 delete-security-group --group-id <SECURITY_GROUP_ID>

Terraform

# Destroy the unused non-default security group
resource "aws_security_group" "<example_resource_name>" {
  count = 0 # Critical: setting count=0 removes/destroys this unused SG
}

Other

1In the AWS Console, go to EC2 > Security Groups
2Select the non-default security group that shows Used by = 0 (no network interfaces or resources)
3Click Actions > Delete security group > Delete

Check Code

from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.awslambda.awslambda_client import awslambda_client
from prowler.providers.aws.services.batch.batch_client import batch_client
from prowler.providers.aws.services.ec2.ec2_client import ec2_client


class ec2_securitygroup_not_used(Check):
    def execute(self):
        findings = []
        for security_group_arn, security_group in ec2_client.security_groups.items():
            # Default security groups can not be deleted, so ignore them
            if security_group.name != "default":
                report = Check_Report_AWS(
                    metadata=self.metadata(), resource=security_group
                )
                report.resource_details = security_group.name
                report.status = "PASS"
                report.status_extended = f"Security group {security_group.name} ({security_group.id}) it is being used."
                sg_in_lambda = (
                    security_group.id in awslambda_client.security_groups_in_use
                )
                # A Batch compute environment scaled down to zero instances
                # keeps its security groups in configuration without any ENI
                sg_in_batch = security_group.id in batch_client.security_groups_in_use
                sg_associated = False
                for sg in ec2_client.security_groups.values():
                    if security_group.id in sg.associated_sgs:
                        sg_associated = True
                if (
                    len(security_group.network_interfaces) == 0
                    and not sg_in_lambda
                    and not sg_in_batch
                    and not sg_associated
                ):
                    # Compute environments failing to list leaves their security
                    # group associations unknown, not absent, so reporting the
                    # group as unused would be a guess. Not being able to read
                    # the compute environments is a lack of visibility, not a
                    # misconfiguration, so report MANUAL rather than asserting a
                    # status either way.
                    if (
                        security_group.region
                        in batch_client.compute_environment_lookup_failed_regions
                    ):
                        report.status = "MANUAL"
                        report.status_extended = f"Security group {security_group.name} ({security_group.id}) usage could not be verified because AWS Batch compute environments could not be listed in region {security_group.region}; grant batch:DescribeComputeEnvironments and run the check again."
                    else:
                        report.status = "FAIL"
                        report.status_extended = f"Security group {security_group.name} ({security_group.id}) it is not being used."

                findings.append(report)

        return findings