Documentation/Shell and jq recipes

Shell and jq recipes

Short recipes for the terminal and for CI scripts. They need curl and jq 1.6 or later. The first three work without an API key; the rest read your key from DEVSECURE_API_KEY.

export DEVSECURE_API=https://api.intelligence.devsecure.io
export DEVSECURE_API_KEY=...   # from your dashboard; never commit it

Look up one CVE (no key)

curl -s "$DEVSECURE_API/api/v1/public/cve/CVE-2024-3400" \
  | jq '{cve: .data.cve_id, rps: .data.rps.score, final_priority: .data.rps.band, rule: .data.rps.band_basis, in_kev: .data.kev.in_catalog, data_as_of}'

final_priority is the band after the KEV floor and the EPSS gate; rule says which one set it (rps, kev_floor or epss_gate). See Priority bands.

The CISA KEV catalogue, ranked (no key)

The ten highest-ranked KEV entries as a table:

curl -s "$DEVSECURE_API/api/v1/kev?limit=10" \
  | jq -r '.results[] | [.rps_score, .cve_id, .kev.vendor_project, .kev.product] | @tsv'

KEV entries CISA marks as used in ransomware, highest RPS first:

curl -s "$DEVSECURE_API/api/v1/kev" \
  | jq -r '.results[] | select(.kev.ransomware_associated == true) | [.rps_score, .cve_id, .kev.vulnerability_name] | @tsv' \
  | head -20

The full catalogue is about 1.5 MB and cached for an hour, so fetch it at most hourly.

Totals for a dashboard (no key)

curl -s "$DEVSECURE_API/api/v1/stats" | jq '.data'

Rank a CVE list

Put one CVE ID per line in cves.txt, then:

jq -R -s '{cve_ids: (split("\n") | map(select(length > 0)))}' cves.txt \
  | curl -s -X POST "$DEVSECURE_API/api/v1/prioritize" \
      -H "Authorization: Bearer $DEVSECURE_API_KEY" \
      -H "Content-Type: application/json" \
      -d @- \
  | jq -r '.ranked[] | [.rank_in_request, .cve_id, .priority_band, .rps_score, .reason] | @tsv'

Rank a scanner's findings

Any scanner report that mentions CVE IDs works: Trivy, Grype, SARIF, CycloneDX or plain text.

grep -ohE 'CVE-[0-9]{4}-[0-9]{4,}' scan-report.json | sort -u > cves.txt

Then rank cves.txt as above. For the full job (thresholds, allowlists, VEX and a job summary), use the CI gate.

Stay within the per-call limit

Prioritize takes up to 25 IDs per call on Free and 1,000 on Pro and above. This sends cves.txt in chunks of 25 and joins the results into one ranked list:

jq -R -s -c 'split("\n") | map(select(length > 0)) | _nwise(25) | {cve_ids: .}' cves.txt \
  | while read -r body; do
      curl -s -X POST "$DEVSECURE_API/api/v1/prioritize" \
        -H "Authorization: Bearer $DEVSECURE_API_KEY" \
        -H "Content-Type: application/json" \
        -d "$body"
    done \
  | jq -s -r 'def rank: {critical_priority: 0, high_priority: 1, medium_priority: 2, low_priority: 3, informational: 4}[.priority_band] // 5;
      [.[].ranked[]] | sort_by([rank, -.rps_score]) | .[] | [.cve_id, .priority_band, .rps_score] | @tsv'

The merge sorts the way the API does: by Final Priority first, then by RPS within a band (a CISA KEV CVE floored at High stays above a Medium with a higher score). CVEs not found come last. Each CVE found counts once against your monthly allowance. See Batch and pagination.

Fail a CI step on High, Critical or KEV

jq -e exits non-zero when the result is false, so this step fails if any CVE is High or Critical, or listed in CISA KEV:

jq -R -s '{cve_ids: (split("\n") | map(select(length > 0)))}' cves.txt \
  | curl -s -X POST "$DEVSECURE_API/api/v1/prioritize" \
      -H "Authorization: Bearer $DEVSECURE_API_KEY" \
      -H "Content-Type: application/json" \
      -d @- > ranked.json
jq -r '.ranked[] | select(.priority_band == "critical_priority" or .priority_band == "high_priority" or .evidence.kev.listed == true) | "\(.cve_id)\t\(.priority_band)\t\(.reason)"' ranked.json
jq -e '[.ranked[] | select(.priority_band == "critical_priority" or .priority_band == "high_priority" or .evidence.kev.listed == true)] | length == 0' ranked.json > /dev/null

Your organisation decides what blocks a build; change the bands to match your policy.

What changed in the last 24 hours

KEV additions and priority rises from the movers feed:

curl -s "$DEVSECURE_API/api/v1/movers?window=24h&type=kev_added,priority_change" \
  -H "Authorization: Bearer $DEVSECURE_API_KEY" \
  | jq -r '.data.items[] | select(.type == "kev_added" or .direction == "up") | [.occurred_on, .cve_id, .type, (.priority_now // ""), .reason] | @tsv'

Check your remaining allowance

curl -s -D - -o /dev/null "$DEVSECURE_API/api/v1/cve/CVE-2021-44228" \
  -H "Authorization: Bearer $DEVSECURE_API_KEY" \
  | grep -i '^x-\(quota\|ratelimit\)-'

X-Quota-Remaining is what's left of the monthly allowance; X-RateLimit-Remaining is what's left this minute. A 429 with code rate_limited comes with Retry-After (seconds to wait); quota_exceeded means the month's allowance is used. See Error handling.

This product uses the NVD API but is not endorsed or certified by the NVD. CVE is a trademark of The MITRE Corporation. All third-party sources and licences: Data Sources and Licences.