mirror of
https://github.com/prompt-security/clawsec.git
synced 2026-06-13 05:28:02 +03:00
Exploitability Context for CVE Advisories (#89)
* feat(advisories): add exploitability context for CVE advisories * fix(ci): align exploitability workflow with signing model * docs(skills): add patch release changelog entries * chore(clawsec-feed): bump version to 0.0.5 * chore(clawsec-suite): bump version to 0.1.4 * fix(clawsec-nanoclaw): align exploitability handling and nanoclaw integration * chore(clawsec-nanoclaw): bump version to 0.0.2 * refactor(scripts): share feed path and mirror sync helpers * refactor(utils): unify cvss vector parsing flow * refactor(clawsec-nanoclaw): centralize advisory risk evaluation * docs(exploitability): refresh release metadata dates * fix(review): align feed signing and advisory dedupe * chore(clawsec-feed): bump version to 0.0.6 * chore(clawsec-nanoclaw): bump version to 0.0.3 * fix(backfill): limit signing to target feed only * fix(review): keep skill runtime verify-only and dedupe matching * chore(clawsec-nanoclaw): bump version to 0.0.4 * chore(skills): align versions with published tags * feat(feed): enrich local population with exploitability analysis * docs(exploitability): mark backfill as historical flow
This commit is contained in:
@@ -193,6 +193,74 @@ jobs:
|
||||
echo "Created advisory JSON:"
|
||||
cat tmp_advisory.json
|
||||
|
||||
- name: Set up Python for exploitability analysis
|
||||
if: steps.parse.outputs.already_exists != 'true'
|
||||
uses: actions/setup-python@8d2f52b6169cf4c0f64d2e9f6f8f6d6a6e1c90f7 # v5.4.1
|
||||
with:
|
||||
python-version: '3.10'
|
||||
|
||||
- name: Analyze exploitability for community advisory
|
||||
if: steps.parse.outputs.already_exists != 'true'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
echo "=== Analyzing exploitability for community advisory ==="
|
||||
|
||||
# Extract fields from advisory for analysis
|
||||
CVE_ID=$(jq -r '.id' tmp_advisory.json)
|
||||
SEVERITY=$(jq -r '.severity // "medium"' tmp_advisory.json)
|
||||
VULN_TYPE=$(jq -r '.type // "unknown"' tmp_advisory.json)
|
||||
DESCRIPTION=$(jq -r '.description // ""' tmp_advisory.json)
|
||||
REFERENCES=$(jq -c '.references // []' tmp_advisory.json)
|
||||
|
||||
# Map severity to approximate CVSS score for analysis
|
||||
case "$SEVERITY" in
|
||||
critical) CVSS_SCORE=9.5 ;;
|
||||
high) CVSS_SCORE=7.5 ;;
|
||||
medium) CVSS_SCORE=5.5 ;;
|
||||
low) CVSS_SCORE=3.0 ;;
|
||||
*) CVSS_SCORE=5.0 ;;
|
||||
esac
|
||||
|
||||
# Build input JSON for analyzer
|
||||
INPUT_JSON=$(jq -n \
|
||||
--arg cve_id "$CVE_ID" \
|
||||
--argjson cvss_score "$CVSS_SCORE" \
|
||||
--arg type "$VULN_TYPE" \
|
||||
--arg description "$DESCRIPTION" \
|
||||
--argjson references "$REFERENCES" \
|
||||
'{
|
||||
cve_id: $cve_id,
|
||||
cvss_score: $cvss_score,
|
||||
type: $type,
|
||||
description: $description,
|
||||
references: $references
|
||||
}')
|
||||
|
||||
# Run exploitability analysis with exploit detection.
|
||||
# Continue without enrichment if analysis fails.
|
||||
if ANALYSIS=$(echo "$INPUT_JSON" | python utils/analyze_exploitability.py --json --check-exploits 2>/dev/null); then
|
||||
echo "$ANALYSIS" > tmp_exploitability.json
|
||||
echo "✓ Analyzed $CVE_ID"
|
||||
|
||||
# Merge exploitability analysis into advisory
|
||||
jq --slurpfile analysis tmp_exploitability.json '
|
||||
. + {
|
||||
exploitability_score: $analysis[0].exploitability_score,
|
||||
exploitability_rationale: $analysis[0].exploitability_rationale,
|
||||
attack_vector_analysis: $analysis[0].attack_vector_analysis,
|
||||
exploit_detection: $analysis[0].exploit_detection
|
||||
}
|
||||
' tmp_advisory.json > tmp_advisory_enriched.json
|
||||
|
||||
mv tmp_advisory_enriched.json tmp_advisory.json
|
||||
|
||||
echo "=== Exploitability analysis complete ==="
|
||||
echo "Exploitability score: $(jq -r '.exploitability_score // "unknown"' tmp_advisory.json)"
|
||||
else
|
||||
echo "::warning::Failed to analyze exploitability for $CVE_ID, continuing without enrichment"
|
||||
fi
|
||||
|
||||
- name: Update feed
|
||||
if: steps.parse.outputs.already_exists != 'true'
|
||||
run: |
|
||||
|
||||
@@ -353,7 +353,9 @@ jobs:
|
||||
title: (.cve.descriptions[] | select(.lang == "en") | .value | .[0:100] + (if length > 100 then "..." else "" end)),
|
||||
affected: normalized_affected,
|
||||
platforms: normalized_platforms,
|
||||
references: [.cve.references[]?.url // empty] | unique | .[0:3]
|
||||
references: [.cve.references[]?.url // empty] | unique | .[0:3],
|
||||
exploitability_score: null,
|
||||
exploitability_rationale: null
|
||||
}]
|
||||
' tmp/filtered_cves.json > tmp/nvd_current_state.json
|
||||
|
||||
@@ -553,7 +555,7 @@ jobs:
|
||||
end
|
||||
);
|
||||
|
||||
[.[] |
|
||||
[.[] |
|
||||
select(.cve.id as $id | $existing | index($id) | not) |
|
||||
{
|
||||
id: .cve.id,
|
||||
@@ -568,7 +570,9 @@ jobs:
|
||||
published: .cve.published,
|
||||
references: [.cve.references[]?.url // empty] | unique | .[0:3],
|
||||
cvss_score: get_cvss_score,
|
||||
nvd_url: ("https://nvd.nist.gov/vuln/detail/" + .cve.id)
|
||||
nvd_url: ("https://nvd.nist.gov/vuln/detail/" + .cve.id),
|
||||
exploitability_score: null,
|
||||
exploitability_rationale: null
|
||||
}
|
||||
]
|
||||
' tmp/filtered_cves.json > tmp/new_advisories.json
|
||||
@@ -582,6 +586,102 @@ jobs:
|
||||
jq '.[].id' tmp/new_advisories.json
|
||||
fi
|
||||
|
||||
- name: Set up Python for exploitability analysis
|
||||
if: steps.transform.outputs.new_count != '0'
|
||||
uses: actions/setup-python@8d2f52b6169cf4c0f64d2e9f6f8f6d6a6e1c90f7 # v5.4.1
|
||||
with:
|
||||
python-version: '3.10'
|
||||
|
||||
- name: Analyze exploitability for new advisories
|
||||
if: steps.transform.outputs.new_count != '0'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
echo "=== Analyzing exploitability for new advisories ==="
|
||||
|
||||
# Extract CVSS vectors from filtered CVEs to merge with advisories
|
||||
jq '
|
||||
[.[] | {
|
||||
id: .cve.id,
|
||||
cvss_vector: (
|
||||
.cve.metrics.cvssMetricV31[0]?.cvssData.vectorString //
|
||||
.cve.metrics.cvssMetricV30[0]?.cvssData.vectorString //
|
||||
.cve.metrics.cvssMetricV2[0]?.vectorString //
|
||||
""
|
||||
)
|
||||
}] | map({(.id): .cvss_vector}) | add
|
||||
' tmp/filtered_cves.json > tmp/cvss_vectors.json
|
||||
|
||||
# Process each advisory through exploitability analyzer
|
||||
jq -c '.[]' tmp/new_advisories.json | while IFS= read -r advisory; do
|
||||
CVE_ID=$(echo "$advisory" | jq -r '.id')
|
||||
CVSS_SCORE=$(echo "$advisory" | jq -r '.cvss_score // 0')
|
||||
CVSS_VECTOR=$(jq -r --arg id "$CVE_ID" '.[$id] // ""' tmp/cvss_vectors.json)
|
||||
VULN_TYPE=$(echo "$advisory" | jq -r '.type // ""')
|
||||
DESCRIPTION=$(echo "$advisory" | jq -r '.description // ""')
|
||||
REFERENCES=$(echo "$advisory" | jq -c '.references // []')
|
||||
|
||||
# Build input JSON for analyzer
|
||||
INPUT_JSON=$(jq -n \
|
||||
--arg cve_id "$CVE_ID" \
|
||||
--argjson cvss_score "$CVSS_SCORE" \
|
||||
--arg cvss_vector "$CVSS_VECTOR" \
|
||||
--arg type "$VULN_TYPE" \
|
||||
--arg description "$DESCRIPTION" \
|
||||
--argjson references "$REFERENCES" \
|
||||
'{
|
||||
cve_id: $cve_id,
|
||||
cvss_score: $cvss_score,
|
||||
cvss_vector: $cvss_vector,
|
||||
type: $type,
|
||||
description: $description,
|
||||
references: $references
|
||||
}')
|
||||
|
||||
# Run exploitability analysis with exploit detection.
|
||||
# Keep processing if any single advisory analysis fails.
|
||||
if ANALYSIS=$(echo "$INPUT_JSON" | python utils/analyze_exploitability.py --json --check-exploits 2>/dev/null); then
|
||||
echo "$ANALYSIS" > "tmp/exploitability_${CVE_ID}.json"
|
||||
echo "✓ Analyzed $CVE_ID"
|
||||
else
|
||||
echo "::warning::Failed to analyze exploitability for $CVE_ID, skipping enrichment"
|
||||
fi
|
||||
done
|
||||
|
||||
# Merge exploitability analysis back into advisories.
|
||||
if ls tmp/exploitability_*.json >/dev/null 2>&1; then
|
||||
jq -s '.' tmp/exploitability_*.json > tmp/exploitability_analyses.json
|
||||
else
|
||||
echo '[]' > tmp/exploitability_analyses.json
|
||||
fi
|
||||
|
||||
jq --slurpfile analyses tmp/exploitability_analyses.json '
|
||||
map(
|
||||
. as $advisory |
|
||||
($analyses[0] | map(select(.cve_id == $advisory.id)) | first) as $analysis |
|
||||
if $analysis then
|
||||
$advisory + {
|
||||
exploitability_score: $analysis.exploitability_score,
|
||||
exploitability_rationale: $analysis.exploitability_rationale,
|
||||
attack_vector_analysis: $analysis.attack_vector_analysis,
|
||||
exploit_detection: $analysis.exploit_detection
|
||||
}
|
||||
else
|
||||
$advisory
|
||||
end
|
||||
)
|
||||
' tmp/new_advisories.json > tmp/new_advisories_enriched.json
|
||||
|
||||
# Replace original with enriched version
|
||||
mv tmp/new_advisories_enriched.json tmp/new_advisories.json
|
||||
|
||||
echo "=== Exploitability analysis complete ==="
|
||||
|
||||
# Show summary of exploitability scores
|
||||
echo "Exploitability score distribution:"
|
||||
jq -r '.[] | "\(.id): \(.exploitability_score // "unknown")"' tmp/new_advisories.json | \
|
||||
awk -F': ' '{scores[$2]++} END {for (s in scores) print " " s ": " scores[s]}'
|
||||
|
||||
- name: Update feed.json
|
||||
if: steps.transform.outputs.new_count != '0' || steps.updates.outputs.update_count != '0'
|
||||
run: |
|
||||
|
||||
Reference in New Issue
Block a user