aws-securityhubv2-bot
AWS Lambda bot that processes AWS Security Hub v2 findings with configurable auto-close rules and optional Slack notifications.
Important: Security Hub v2 only (OCSF format). Not directly compatible with original Security Hub CSPM (ASFF format).
Features
- auto-close rules - suppress/resolve findings via JSON filters (type, severity, tags, accounts, regions)
- optional slack - rich notifications with context and remediation links
- flexible config - environment variables or S3 for rule storage
- multi-service - GuardDuty, Inspector, Macie, IAM Access Analyzer, Security Hub CSPM
Quick Start
Build
mkdir -p dist
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -C cmd/lambda -o ../../dist/bootstrap
cd dist && zip deployment.zip bootstrap && cd ..
Deploy Lambda
- IAM role with
AWSLambdaBasicExecutionRole + securityhub:BatchUpdateFindingsV2
- Create function using
deployment.zip (runtime: provided.al2023, handler: bootstrap)
- EventBridge rule targeting the Lambda:
{
"source": ["aws.securityhub"],
"detail-type": ["Findings Imported V2"]
}
- Configure using environment variables below
Configuration
Auto-Close Rules
| Name |
Description |
APP_AUTO_CLOSE_RULES |
JSON array of auto-close rules (see examples) |
APP_AUTO_CLOSE_RULES_S3_BUCKET |
S3 bucket for rules (for large rule sets) |
APP_AUTO_CLOSE_RULES_S3_PREFIX |
S3 prefix for rules (default: rules/) |
Use environment variables, S3, or both. Environment rules evaluated first.
Slack (Optional)
| Name |
Description |
APP_SLACK_TOKEN |
Bot token with chat:write scope |
APP_SLACK_CHANNEL |
Channel ID (e.g., C000XXXXXXX) |
Additional
| Name |
Description |
APP_DEBUG_ENABLED |
Verbose logging (default: false) |
APP_AWS_CONSOLE_URL |
Base console URL |
APP_AWS_ACCESS_PORTAL_URL |
Federated access portal URL |
APP_AWS_ACCESS_ROLE_NAME |
IAM role for portal |
APP_AWS_SECURITYHUBV2_REGION |
Centralized SecurityHub region |
Examples
Basic Rule: Suppress GitHub Runner Findings
[
{
"name": "auto-close-github-runners",
"enabled": true,
"filters": {
"finding_types": ["Execution:Runtime/NewBinaryExecuted"],
"resource_tags": [{"name": "component-type", "value": "github-action-runners"}]
},
"action": {
"status_id": 5,
"comment": "Auto-archived: Expected CI/CD behavior"
},
"skip_notification": true
}
]
See examples/github-actions-runner-example.md for detailed walkthrough.
Multiple Rules
[
{
"name": "suppress-inspector-low-dev",
"filters": {
"product_name": ["Inspector"],
"severity": ["Low"],
"accounts": ["123456789012"]
},
"action": {"status_id": 3, "comment": "Auto-suppressed: Low severity in dev"},
"skip_notification": false
},
{
"name": "resolve-approved-scans",
"filters": {
"finding_types": ["Recon:EC2/PortProbeUnprotectedPort"],
"resource_tags": [{"name": "ScannerApproved", "value": "true"}]
},
"action": {"status_id": 4, "comment": "Auto-resolved: Approved scanner"},
"skip_notification": true
}
]
Filter Reference
All filters use AND logic. First matching rule wins.
| Field |
Type |
Example |
finding_types |
[]string |
["Execution:Runtime/NewBinaryExecuted"] |
severity |
[]string |
["Critical", "High"] |
product_name |
[]string |
["GuardDuty", "Inspector"] |
resource_types |
[]string |
["AWS::EC2::Instance"] |
resource_tags |
[]object |
[{"name": "Environment", "value": "dev"}] |
accounts |
[]string |
["123456789012"] |
regions |
[]string |
["us-east-1"] |
Status IDs
Based on OCSF 1.6.0 specification:
| ID |
Status |
Description |
| 0 |
Unknown |
The status is unknown |
| 1 |
New |
The finding is new and yet to be reviewed |
| 2 |
In Progress |
The finding is under review |
| 3 |
Suppressed |
The finding was reviewed, determined to be benign or false positive, suppressed |
| 4 |
Resolved |
The finding was reviewed, remediated and is now considered resolved |
| 5 |
Archived |
The finding was archived |
| 6 |
Deleted |
The finding was deleted (e.g., created in error) |
| 99 |
Other |
The status is not mapped (see status attribute for source-specific value) |
Common usage: status_id: 5 (Archived) for accepted behavior, status_id: 4 (Resolved) for remediated issues, status_id: 3 (Suppressed) for false positives.
S3 Rule Storage
For large rule sets (>4KB), store rules in S3. Supports single rule per file, arrays of rules, or mixed approach:
s3://my-rules-bucket/rules/
├── guardduty/
│ └── suppress-dev.json
├── inspector/
│ └── all-rules.json
└── auto-close-runners.json
Requirements: Lambda needs s3:GetObject and s3:ListBucket on the bucket. Only .json files processed.
EventBridge Filters (Optional)
Filter by severity for high-volume environments:
{
"source": ["aws.securityhub"],
"detail-type": ["Findings Imported V2"],
"detail": {
"findings": {
"severity": ["Critical", "High"]
}
}
}
Or by source service:
{
"detail": {
"findings": {
"metadata": {
"product": {
"name": ["GuardDuty", "Inspector"]
}
}
}
}
}
IAM Permissions
Lambda role needs AWSLambdaBasicExecutionRole plus:
{
"Effect": "Allow",
"Action": ["securityhub:BatchUpdateFindingsV2"],
"Resource": "*"
}
If using S3 rules, add:
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:ListBucket"],
"Resource": [
"arn:aws:s3:::my-rules-bucket",
"arn:aws:s3:::my-rules-bucket/*"
]
}
How It Works
- EventBridge triggers Lambda on "Findings Imported V2"
- Parse OCSF finding from event
- Evaluate auto-close rules in order (first match wins)
- If matched: call
BatchUpdateFindingsV2 with status + comment
- Send Slack notification (unless
skip_notification: true)
- If no match: send to Slack if finding is alertable
Local Development
cp .env.example .env # edit values
go run -C cmd/sample .
Uses OCSF findings from fixtures/samples.json. Requires AWS credentials for auto-close testing.
License
This project is licensed under the MIT License - see the LICENSE file for details.