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).
- 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
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 ..- 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
| 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.
| Name | Description |
|---|---|
APP_SLACK_TOKEN |
Bot token with chat:write scope |
APP_SLACK_CHANNEL |
Channel ID (e.g., C000XXXXXXX) |
| 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 |
[
{
"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.
[
{
"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
}
]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"] |
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.
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.
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"]
}
}
}
}
}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/*"
]
}- EventBridge triggers Lambda on "Findings Imported V2"
- Parse OCSF finding from event
- Evaluate auto-close rules in order (first match wins)
- If matched: call
BatchUpdateFindingsV2with status + comment - Send Slack notification (unless
skip_notification: true) - If no match: send to Slack if finding is alertable
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.
This project is licensed under the MIT License - see the LICENSE file for details.