An AWS Lambda that fetches recent CVEs from the NVD API, prefilters them, and uses AWS Bedrock (Claude) to triage their relevance to your hosting platform. The results are delivered to Slack as:
- A full, styled HTML report uploaded as a file.
- A rich Block Kit summary message (severity counts + top applicable CVEs).
The Lambda is intended to be run on an EventBridge schedule (e.g. daily).
EventBridge (cron) ──▶ Lambda
│
├─ NVD API (fetch recent CVEs)
├─ prefilter (CVSS threshold + keywords)
├─ AWS Bedrock (AI triage → structured results)
├─ HTML report (html/template, embedded)
└─ Slack (upload file + Block Kit summary)
Pipeline source layout:
| Package | Responsibility |
|---|---|
cmd/lambda |
Lambda entry point (lambda.Start) |
internal/app |
Shared pipeline: load context → fetch → filter → triage |
internal/config |
Env-driven configuration (LoadFromEnv) |
internal/nvd |
NVD 2.0 API client |
internal/filter |
CVSS + keyword prefilter |
internal/triage |
Bedrock Converse triage |
internal/render |
HTML report + summary computation |
internal/slack |
File upload + Block Kit summary delivery |
The platform context (internal/app/platform.md) is embedded into the
binary, so the Lambda is self-contained. Override it at runtime with the
PLATFORM_PROMPT / PLATFORM_PROMPT_FILE env vars if needed.
All configuration is via environment variables.
| Variable | Required | Default | Description |
|---|---|---|---|
SLACK_BOT_TOKEN |
yes | – | Slack bot token (xoxb-...) |
SLACK_CHANNEL_ID |
yes | – | Target Slack channel ID |
PLATFORM_NAME |
no | platform |
Label used in titles / filenames |
AWS_REGION |
no | ap-southeast-2 |
Region for Bedrock (set automatically by Lambda) |
BEDROCK_MODEL_ID |
no | apac.anthropic.claude-sonnet-4-5-20250514-v1:0 |
Bedrock model / inference profile ID |
CVE_BATCH_SIZE |
no | 25 |
CVEs per Bedrock call |
CVE_MAX_CONCURRENCY |
no | 4 |
Max concurrent Bedrock batch calls (increase for faster runs; reduce if throttled) |
CVE_WINDOW |
no | 24h |
Lookback window (Go duration, e.g. 168h for 7 days) |
CVE_KEYWORDS |
no | (from platform.md) |
Comma-separated keyword include-list |
NVD_API_KEY |
no | – | NVD API key (raises rate limits) |
PLATFORM_PROMPT |
no | – | Inline platform context (overrides file/embed) |
PLATFORM_PROMPT_FILE |
no | platform.md |
Path to platform context file |
OUTPUT_FORMAT |
no | table |
Unused by the Lambda path |
If CVE_KEYWORDS is empty, keywords are extracted from the Keywords: line
in the platform context.
This project uses Mise to manage tooling and build tasks, matching the approach used across skpr's Lambda projects.
curl https://mise.run | sh
mise installmise run buildThis produces lambda-handler.zip containing the bootstrap binary
(GOOS=linux GOARCH=amd64 CGO_ENABLED=0, built with -tags lambda.norpc).
Pushing a v0.* tag triggers the
Publish Artifacts GitHub Actions workflow,
which builds the zip and attaches it to a GitHub Release automatically.
git tag v0.1.0
git push origin v0.1.0CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -tags lambda.norpc -o bootstrap ./cmd/lambda
zip lambda-handler.zip bootstrap
rm bootstrapCreate the function:
aws lambda create-function \
--function-name cve-triage \
--runtime provided.al2023 \
--architectures x86_64 \
--handler bootstrap \
--role arn:aws:iam::<account-id>:role/cve-triage-lambda \
--timeout 600 \
--memory-size 256 \
--zip-file fileb://lambda-handler.zip \
--environment "Variables={SLACK_BOT_TOKEN=xoxb-...,SLACK_CHANNEL_ID=C0123456789,PLATFORM_NAME=Skpr Hosting,BEDROCK_MODEL_ID=apac.anthropic.claude-sonnet-4-5-20250514-v1:0}"Update an existing function:
aws lambda update-function-code \
--function-name cve-triage \
--zip-file fileb://lambda-handler.zipaws events put-rule \
--name cve-triage-daily \
--schedule-expression "rate(1 day)"
aws lambda add-permission \
--function-name cve-triage \
--statement-id cve-triage-eventbridge \
--action lambda:InvokeFunction \
--principal events.amazonaws.com \
--source-arn arn:aws:events:<region>:<account-id>:rule/cve-triage-daily
aws events put-targets \
--rule cve-triage-daily \
--targets "Id"="cve-triage","Arn"="arn:aws:lambda:<region>:<account-id>:function:cve-triage"bedrock:InvokeModelon the model / inference profile ARN.logs:CreateLogGroup,logs:CreateLogStream,logs:PutLogEvents(or attachAWSLambdaBasicExecutionRole).
Example inline policy statement for Bedrock:
{
"Effect": "Allow",
"Action": "bedrock:InvokeModel",
"Resource": [
"arn:aws:bedrock:*::foundation-model/anthropic.*",
"arn:aws:bedrock:*:<account-id>:inference-profile/apac.anthropic.*"
]
}The bot token needs:
files:write— upload the HTML report.chat:write— post the summary message.
Invite the bot to the target channel, then use that channel's ID for
SLACK_CHANNEL_ID.
The handler reads everything from the environment, so you can exercise the pipeline locally with valid AWS credentials and a Slack token:
export SLACK_BOT_TOKEN=xoxb-...
export SLACK_CHANNEL_ID=C0123456789
export AWS_REGION=ap-southeast-2
export PLATFORM_NAME="Skpr Hosting"
go run ./cmd/lambda # note: lambda.Start expects the Lambda runtime API;
# use the AWS RIE or `sam local invoke` for true local runs.For a faithful local invocation, use the
AWS Lambda Runtime Interface Emulator
or sam local invoke.
go test ./...