jq for Security Scanning
Security data comes as JSON. Master these patterns to audit vulnerabilities across your repositories at scale.
GitHub Dependabot Alerts
GitHub’s Dependabot scans package-lock.json, requirements.txt, go.sum, and other dependency files for known vulnerabilities.
Basic Alert Listing
# List all alerts for a repo
gh api repos/{owner}/{repo}/dependabot/alerts | jq '.[] | {
package: .dependency.package.name,
severity: .security_advisory.severity,
summary: .security_advisory.summary
}'
Example Output
{
"package": "minimatch",
"severity": "high",
"summary": "minimatch has ReDoS vulnerability"
}
Filter by Severity
# Critical and high only
gh api repos/{owner}/{repo}/dependabot/alerts | jq '
.[] | select(.security_advisory.severity == "critical" or .security_advisory.severity == "high") |
{package: .dependency.package.name, severity: .security_advisory.severity, file: .dependency.manifest_path}
'
# High severity with file location
gh api repos/{owner}/{repo}/dependabot/alerts | jq '
.[] | select(.security_advisory.severity == "high") |
{package: .dependency.package.name, file: .dependency.manifest_path, summary: .security_advisory.summary}
'
Count by Severity
# Severity distribution
gh api repos/{owner}/{repo}/dependabot/alerts | jq '
group_by(.security_advisory.severity) |
map({severity: .[0].security_advisory.severity, count: length}) |
sort_by(.count) | reverse
'
Example Output
[
{"severity": "high", "count": 8},
{"severity": "moderate", "count": 3},
{"severity": "low", "count": 1}
]
Alerts by Manifest File
Find which package.json or requirements.txt is causing the most alerts:
gh api repos/{owner}/{repo}/dependabot/alerts | jq '
group_by(.dependency.manifest_path) |
map({
file: .[0].dependency.manifest_path,
count: length,
packages: [.[] | .dependency.package.name] | unique,
severities: [.[] | .security_advisory.severity] | group_by(.) | map({(.[0]): length}) | add
}) |
sort_by(.count) | reverse
'
Example Output
[
{
"file": "02_Assets/old-project/package-lock.json",
"count": 5,
"packages": ["minimatch", "glob-parent", "ansi-regex"],
"severities": {"high": 3, "moderate": 2}
}
]
Open Alerts with CVE IDs
# CVE details for open alerts
gh api repos/{owner}/{repo}/dependabot/alerts | jq '
.[] | select(.state == "open") |
{
cve: .security_advisory.cve_id,
ghsa: .security_advisory.ghsa_id,
package: .dependency.package.name,
severity: .security_advisory.severity,
published: .security_advisory.published_at | split("T")[0]
}
'
Table Output for Reports
# Tab-separated for spreadsheet/column
gh api repos/{owner}/{repo}/dependabot/alerts | jq -r '
["PACKAGE", "SEVERITY", "CVE", "FILE"],
(.[] | [
.dependency.package.name,
.security_advisory.severity,
(.security_advisory.cve_id // "N/A"),
.dependency.manifest_path
]) | @tsv
' | column -t
Example Output
PACKAGE SEVERITY CVE FILE minimatch high CVE-2022-3517 package-lock.json glob-parent high CVE-2020-28469 package-lock.json ansi-regex moderate CVE-2021-3807 package-lock.json
Multi-Repo Scanning
Scan all your repositories for vulnerabilities:
# Get all repos, check each for alerts
gh repo list --limit 100 --json name -q '.[].name' | while read repo; do
COUNT=$(gh api "repos/{owner}/$repo/dependabot/alerts" 2>/dev/null | jq 'length')
if [[ "$COUNT" -gt 0 ]]; then
echo "$repo: $COUNT alerts"
fi
done
# Detailed multi-repo report
gh repo list --limit 100 --json name -q '.[].name' | while read repo; do
gh api "repos/{owner}/$repo/dependabot/alerts" 2>/dev/null | jq --arg repo "$repo" '
.[] | select(.security_advisory.severity == "critical" or .security_advisory.severity == "high") |
{repo: $repo, package: .dependency.package.name, severity: .security_advisory.severity}
'
done | jq -s '.'
GitLab Vulnerability Reports
GitLab’s Security Dashboard provides vulnerability findings from SAST, DAST, dependency scanning, and container scanning.
Project Vulnerability Findings
# Get project ID first
PROJECT_ID=$(glab api projects --jq '.[] | select(.path == "your-project") | .id')
# List all vulnerabilities
glab api "projects/$PROJECT_ID/vulnerability_findings" | jq '
.[] | {
severity: .severity,
name: .name,
scanner: .scanner.name,
state: .state
}
'
Filter Critical/High
glab api "projects/$PROJECT_ID/vulnerability_findings" | jq '
.[] | select(.severity == "critical" or .severity == "high") |
{
name: .name,
severity: .severity,
state: .state,
scanner: .scanner.name,
location: .location.file
}
'
Count by Scanner Type
glab api "projects/$PROJECT_ID/vulnerability_findings" | jq '
group_by(.scanner.name) |
map({scanner: .[0].scanner.name, count: length, critical: [.[] | select(.severity == "critical")] | length}) |
sort_by(.count) | reverse
'
Example Output
[
{"scanner": "dependency_scanning", "count": 15, "critical": 2},
{"scanner": "sast", "count": 8, "critical": 0},
{"scanner": "container_scanning", "count": 3, "critical": 1}
]
Common Security Audit Patterns
Compare Before/After Scans
# Save baseline
gh api repos/{owner}/{repo}/dependabot/alerts > baseline.json
# After remediation, compare
gh api repos/{owner}/{repo}/dependabot/alerts > current.json
# Find resolved (in baseline but not current)
jq -n --slurpfile old baseline.json --slurpfile new current.json '
($old[0] | map(.number)) - ($new[0] | map(.number)) |
. as $resolved |
$old[0] | map(select(.number | IN($resolved[]))) |
map({number, package: .dependency.package.name, severity: .security_advisory.severity})
'
# Find new (in current but not baseline)
jq -n --slurpfile old baseline.json --slurpfile new current.json '
($new[0] | map(.number)) - ($old[0] | map(.number)) |
. as $new_alerts |
$new[0] | map(select(.number | IN($new_alerts[]))) |
map({number, package: .dependency.package.name, severity: .security_advisory.severity})
'
Age Analysis
Find vulnerabilities that have been open for too long:
# Alerts open > 30 days
gh api repos/{owner}/{repo}/dependabot/alerts | jq '
(now | floor) as $now |
.[] | select(.state == "open") |
(.created_at | fromdateiso8601 | floor) as $created |
(($now - $created) / 86400 | floor) as $age_days |
select($age_days > 30) |
{
package: .dependency.package.name,
severity: .security_advisory.severity,
age_days: $age_days,
created: .created_at | split("T")[0]
}
' | jq -s 'sort_by(.age_days) | reverse'
CVSS Score Extraction
# Sort by CVSS score (highest risk first)
gh api repos/{owner}/{repo}/dependabot/alerts | jq '
[.[] | {
package: .dependency.package.name,
cvss: .security_advisory.cvss.score,
severity: .security_advisory.severity,
vector: .security_advisory.cvss.vector_string
}] | sort_by(.cvss) | reverse
'
Remediation Priority List
Combine severity, age, and CVSS for a prioritized fix list:
gh api repos/{owner}/{repo}/dependabot/alerts | jq '
(now | floor) as $now |
[.[] | select(.state == "open") |
(.created_at | fromdateiso8601 | floor) as $created |
(($now - $created) / 86400 | floor) as $age |
{
package: .dependency.package.name,
file: .dependency.manifest_path,
severity: .security_advisory.severity,
cvss: (.security_advisory.cvss.score // 0),
age_days: $age,
priority_score: (
(if .security_advisory.severity == "critical" then 100
elif .security_advisory.severity == "high" then 75
elif .security_advisory.severity == "medium" then 50
else 25 end) +
((.security_advisory.cvss.score // 0) * 5) +
(if $age > 90 then 20 elif $age > 30 then 10 else 0 end)
)
}
] | sort_by(.priority_score) | reverse | .[0:10]
'
Automation Scripts
Weekly Security Digest
#!/bin/bash
# security-digest.sh - Weekly vulnerability summary
OWNER="EvanusModestus"
REPOS=$(gh repo list --limit 100 --json name -q '.[].name')
echo "# Security Digest - $(date +%Y-%m-%d)"
echo ""
for repo in $REPOS; do
ALERTS=$(gh api "repos/$OWNER/$repo/dependabot/alerts" 2>/dev/null)
if [[ $(echo "$ALERTS" | jq 'length') -gt 0 ]]; then
echo "## $repo"
echo "$ALERTS" | jq -r '
group_by(.security_advisory.severity) |
map("\(.[0].security_advisory.severity): \(length)") |
join(", ")
'
echo ""
fi
done
Slack/Webhook Alert
#!/bin/bash
# alert-critical.sh - Alert on new critical vulnerabilities
CRITICAL=$(gh api repos/{owner}/{repo}/dependabot/alerts | jq '
[.[] | select(.state == "open" and .security_advisory.severity == "critical")]
')
COUNT=$(echo "$CRITICAL" | jq 'length')
if [[ "$COUNT" -gt 0 ]]; then
MESSAGE=$(echo "$CRITICAL" | jq -r '
"Critical vulnerabilities found:\n" +
([.[] | "• \(.dependency.package.name): \(.security_advisory.summary)"] | join("\n"))
')
curl -X POST "$SLACK_WEBHOOK" \
-H 'Content-type: application/json' \
-d "{\"text\": \"$MESSAGE\"}"
fi
Related
-
Git Forge CLIs - More gh/glab patterns