This check flags security group rules that have no description, leaving you guessing about why a port is open and who owns it. Add a meaningful description to every rule so audits, incident response, and cleanup stay fast and accurate.
Security groups are the workhorse firewall in AWS. They control inbound and outbound traffic at the instance and ENI level, and most accounts accumulate dozens or hundreds of them over time. The problem is rarely that a rule exists. The problem is that nobody remembers why it exists.
The sg_rule_no_description check catches security group rules that were created without a description. On its own a missing description is not a direct vulnerability, but it is the kind of small hygiene gap that turns a five-minute security review into a half-day archaeology project.
What this check detects
The check inspects every inbound and outbound rule across your security groups and reports any rule where the Description field is empty. In the AWS API, each entry in an IpPermissions or IpPermissionsEgress block can carry a description on its IpRanges, Ipv6Ranges, UserIdGroupPairs, or PrefixListIds entries. When that field is blank, the rule passes traffic with no recorded justification.
Here is what a rule with no description looks like when you describe a security group:
{
"IpProtocol": "tcp",
"FromPort": 22,
"ToPort": 22,
"IpRanges": [
{
"CidrIp": "0.0.0.0/0"
}
]
}
Notice there is no Description key on the CIDR range. Compare that to a documented rule:
{
"IpProtocol": "tcp",
"FromPort": 22,
"ToPort": 22,
"IpRanges": [
{
"CidrIp": "203.0.113.10/32",
"Description": "SSH from ops bastion - JIRA OPS-4412"
}
]
}
Note: In AWS, descriptions live on the individual IP range or group pair inside a rule, not on the rule as a whole. A single rule that references three CIDR blocks can have three separate descriptions, one per source.
Why it matters
An undescribed rule is a liability that compounds quietly. The risk is not theoretical, and it shows up in a few predictable ways.
Stale rules never get cleaned up
Say you find a rule allowing inbound TCP 8080 from a specific CIDR. Is that an active integration, a temporary debug session from 2022, or a vendor that churned last year? Without a description, the safe assumption is "leave it alone," so it stays forever. Over time these orphaned rules widen your attack surface for no operational benefit.
Incident response slows down
During an active incident, every minute spent reverse-engineering firewall rules is a minute attackers keep their foothold. If a responder can read Allow 443 from partner-payments VPN - contract CX-2231, they can make a fast, confident decision to keep or kill it. A blank rule forces them to chase down owners over Slack while the clock runs.
Audits and compliance reviews stall
Frameworks like SOC 2, PCI DSS, and ISO 27001 expect you to justify network access. An auditor reviewing your ingress rules will ask "what is this for?" Multiply that by every undescribed rule and you get a painful evidence-gathering exercise that could have been avoided with one field.
Warning: Missing descriptions often correlate with missing ownership. The rules you cannot explain are frequently the same rules that are too broadly scoped, like an SSH rule open to 0.0.0.0/0 that someone added "just to test."
How to fix it
AWS does not let you edit a description in place through the standard rule APIs. You update descriptions using update-security-group-rule-descriptions-ingress and the egress equivalent. These commands match on the existing rule definition and overwrite the description without dropping or recreating the rule, so there is no traffic interruption.
Step 1: Find rules with no description
List a security group's rules so you can see which ones are blank:
aws ec2 describe-security-groups \
--group-ids sg-0123456789abcdef0 \
--query 'SecurityGroups[0].IpPermissions'
To scan an entire account for rules missing descriptions, use the newer flat rule API:
aws ec2 describe-security-group-rules \
--query "SecurityGroupRules[?Description==null || Description==''].[GroupId,SecurityGroupRuleId,IpProtocol,FromPort,CidrIpv4]" \
--output table
Step 2: Add the description (CLI)
Update an ingress rule's description. The CIDR and port must match the existing rule exactly:
aws ec2 update-security-group-rule-descriptions-ingress \
--group-id sg-0123456789abcdef0 \
--ip-permissions 'IpProtocol=tcp,FromPort=22,ToPort=22,IpRanges=[{CidrIp=203.0.113.10/32,Description="SSH from ops bastion - OPS-4412"}]'
For egress rules:
aws ec2 update-security-group-rule-descriptions-egress \
--group-id sg-0123456789abcdef0 \
--ip-permissions 'IpProtocol=tcp,FromPort=443,ToPort=443,IpRanges=[{CidrIp=10.0.0.0/8,Description="Outbound HTTPS to internal services"}]'
Danger: Do not remediate by deleting and recreating rules to add a description. revoke-security-group-ingress followed by authorize-security-group-ingress creates a window where traffic is blocked, and any concurrent change can leave the group in a half-configured state. Always use the update-security-group-rule-descriptions-* commands, which edit in place.
Step 3: Add the description (Console)
- Open the VPC or EC2 console and go to Security Groups.
- Select the security group, then choose the Inbound rules or Outbound rules tab.
- Click Edit inbound rules.
- Fill in the Description column for any blank rule. Keep it short and useful, for example the source, the purpose, and a ticket reference.
- Click Save rules.
Fixing it in infrastructure as code
If you manage security groups with Terraform, descriptions are first-class arguments. Use a dedicated rule resource for clarity:
resource "aws_vpc_security_group_ingress_rule" "ssh_from_bastion" {
security_group_id = aws_security_group.app.id
description = "SSH from ops bastion - OPS-4412"
cidr_ipv4 = "203.0.113.10/32"
from_port = 22
to_port = 22
ip_protocol = "tcp"
}
CloudFormation supports the same field on each rule entry:
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: 203.0.113.10/32
Description: "SSH from ops bastion - OPS-4412"
Tip: If a security group was created in the console but is now managed by Terraform, run terraform plan after importing it. Terraform will show a diff for any rule whose description does not match your code, which is a quick way to surface every undescribed rule at once.
How to prevent it from happening again
Backfilling descriptions is a one-time cleanup. Keeping them populated is a process problem, and the fix is to make "no description" impossible to merge or deploy.
Enforce descriptions in CI with policy-as-code
If you use Terraform, add a check to your pipeline that fails the plan when a security group rule lacks a description. With Conftest and OPA you can write a policy like this:
package main
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_vpc_security_group_ingress_rule"
not resource.change.after.description
msg := sprintf("Ingress rule %s is missing a description", [resource.address])
}
Run it against your plan output in CI:
terraform plan -out=tfplan
terraform show -json tfplan > plan.json
conftest test plan.json --policy ./policy
Gate it at the org level with SCPs and Config
AWS Config has a managed rule pattern you can extend, and you can write a custom Config rule that marks any security group rule with an empty description as non-compliant. Wire that rule into an AWS Config conformance pack so it runs continuously across every account in the organization, not just at deploy time.
Note: Config rules catch drift that bypasses your IaC pipeline, like a rule someone adds manually during an incident and forgets to clean up. Pair IaC checks (prevent at merge) with Config (detect after the fact) for full coverage.
Make Lensix the continuous safety net
Lensix runs sg_rule_no_description across your accounts on every scan, so undescribed rules surface in your findings feed whether they came from a console click, a forgotten script, or a Terraform module that left the field blank. That gives you a running list to work through instead of discovering the gap during your next audit.
Best practices
- Adopt a description convention. Pick a consistent format and stick to it, for example
<purpose> from <source> - <ticket>. Consistency makes descriptions searchable and greppable. - Reference a ticket or owner. A description that points to a Jira issue, a runbook, or a team name lets the next person find the full context, not just a one-line summary.
- Avoid temporary rules without an expiry note. If a rule really is temporary, say so in the description and include a date.
TEMP debug access - remove after 2024-06-01is its own cleanup reminder. - Describe the source group, not just CIDRs. When a rule references another security group, explain the relationship, for example
Allow app tier to reach RDS. - Review descriptions during rule audits. A quarterly pass over your security groups is a good time to verify descriptions still match reality and to delete rules whose purpose no longer applies.
- Never use descriptions to store secrets. They are visible to anyone with read access to the security group, so keep them informational.
A description is the cheapest documentation you will ever write, and it pays off most at the worst possible moment, when you are mid-incident and need to know what a rule does. Treat the missing description not as a cosmetic nit but as a signal that a piece of your network policy lacks an owner and a reason. Fix the field, then fix the process that let it stay blank.

