Skip to content

Instantly share code, notes, and snippets.

@breiter
Created May 26, 2026 18:38
Show Gist options
  • Select an option

  • Save breiter/d77b5ecbe93efb43ff9d2eafc1239c58 to your computer and use it in GitHub Desktop.

Select an option

Save breiter/d77b5ecbe93efb43ff9d2eafc1239c58 to your computer and use it in GitHub Desktop.
export aws inspector code repo findings to markdown
#!/usr/bin/env bash
set -euo pipefail
PROFILE="${1:-default}"
OUTDIR="$(PROFILE)_inspector_reports"
mkdir -p "$OUTDIR"
fetch_all_findings() {
local filter="$1"
local outfile="$2"
aws inspector2 list-findings \
--profile "$PROFILE" \
--filter "$filter" \
--output json > "$outfile"
while token=$(jq -r '.nextToken // empty' "$outfile" 2>/dev/null) && [ -n "$token" ]; do
aws inspector2 list-findings \
--profile "$PROFILE" \
--filter "$filter" \
--next-token "$token" \
--output json > "${outfile}.next"
jq -s '
.[0].findings += .[1].findings
| .[0].nextToken = .[1].nextToken
| .[0]
' "$outfile" "${outfile}.next" > "${outfile}.merged"
mv "${outfile}.merged" "$outfile"
rm -f "${outfile}.next"
echo " Fetched $(grep -v '"nextToken"' "$outfile" | jq '.findings | length') findings so far..."
done
local count
count=$(grep -v '"nextToken"' "$outfile" | jq '.findings | length')
echo " Total: $count findings"
}
split_by_repo() {
local rawfile="$1"
local suffix="$2"
local count
count=$(grep -v '"nextToken"' "$rawfile" | jq '.findings | length')
if [ "$count" -eq 0 ]; then
echo " No findings to split."
return 0
fi
for repo in $(grep -v '"nextToken"' "$rawfile" \
| jq -r '[.findings[].resources[0].details.codeRepository.projectName] | unique[]'); do
local filename
filename="$OUTDIR/$(echo "$repo" | tr '/' '_' | sed 's/:.*//').$suffix.json"
grep -v '"nextToken"' "$rawfile" \
| jq --arg repo "$repo" \
'[.findings[] | select(.resources[0].details.codeRepository.projectName == $repo)]' \
> "$filename"
echo " Wrote $filename"
done
}
generate_pkg_reports() {
local found=0
for f in "$OUTDIR"/*.pkg.json; do
[ -f "$f" ] || continue
found=1
local outfile="${f%.json}_report.md"
jq -r '
[.[] | {
severity: .severity,
cve: .packageVulnerabilityDetails.vulnerabilityId,
package: .packageVulnerabilityDetails.vulnerablePackages[0].name,
version: .packageVulnerabilityDetails.vulnerablePackages[0].version,
fixedIn: (.packageVulnerabilityDetails.vulnerablePackages[0].fixedInVersion // "N/A"),
filePath: .packageVulnerabilityDetails.vulnerablePackages[0].filePath,
sourceUrl: .packageVulnerabilityDetails.sourceUrl
}]
| sort_by(
(if .severity == "CRITICAL" then 0
elif .severity == "HIGH" then 1
elif .severity == "MEDIUM" then 2
elif .severity == "LOW" then 3
else 4 end),
.filePath
)
| "| SEVERITY | CVE | PACKAGE | VERSION | FIXED IN | FILE | URL |",
"| --- | --- | --- | --- | --- | --- | --- |",
(.[] | "| \(.severity) | \(.cve) | \(.package) | \(.version) | \(.fixedIn) | \(.filePath) | \(.sourceUrl) |")
' "$f" > "$outfile"
echo " Wrote $outfile"
done
if [ "$found" -eq 0 ]; then
echo " No package vulnerability files to report."
fi
}
generate_code_reports() {
local found=0
for f in "$OUTDIR"/*.code.json; do
[ -f "$f" ] || continue
found=1
local outfile="${f%.json}_report.md"
jq -r '
[.[] | {
severity: .severity,
title: .title,
filePath: .codeVulnerabilityDetails.filePath.fileName,
startLine: .codeVulnerabilityDetails.filePath.startLine,
endLine: .codeVulnerabilityDetails.filePath.endLine,
url: .remediation.recommendation.Url
}]
| sort_by(
(if .severity == "CRITICAL" then 0
elif .severity == "HIGH" then 1
elif .severity == "MEDIUM" then 2
elif .severity == "LOW" then 3
else 4 end),
.filePath
)
| "| SEVERITY | TITLE | FILE | START | END | URL |",
"| --- | --- | --- | --- | --- | --- |",
(.[] | "| \(.severity) | \(.title) | \(.filePath) | \(.startLine) | \(.endLine) | \(.url) |")
' "$f" > "$outfile"
echo " Wrote $outfile"
done
if [ "$found" -eq 0 ]; then
echo " No code vulnerability files to report."
fi
}
echo "Using profile: $PROFILE"
echo ""
echo "Fetching package vulnerabilities (CODE_REPOSITORY)..."
PKG_FILTER='{"findingStatus":[{"comparison":"EQUALS","value":"ACTIVE"}],"findingType":[{"comparison":"EQUALS","value":"PACKAGE_VULNERABILITY"}],"resourceType":[{"comparison":"EQUALS","value":"CODE_REPOSITORY"}]}'
fetch_all_findings "$PKG_FILTER" pkg_findings_raw.json
echo ""
echo "Fetching code vulnerabilities..."
CODE_FILTER='{"findingStatus":[{"comparison":"EQUALS","value":"ACTIVE"}],"findingType":[{"comparison":"EQUALS","value":"CODE_VULNERABILITY"}]}'
fetch_all_findings "$CODE_FILTER" code_findings_raw.json
echo ""
echo "Splitting package findings by repository..."
split_by_repo pkg_findings_raw.json pkg
echo ""
echo "Splitting code findings by repository..."
split_by_repo code_findings_raw.json code
echo ""
echo "Generating package vulnerability reports..."
generate_pkg_reports
echo ""
echo "Generating code vulnerability reports..."
generate_code_reports
echo ""
echo "Done. Reports are in $OUTDIR/"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment