From 073e771b73517b3b4c8f783ac46f8e2ffef19cd0 Mon Sep 17 00:00:00 2001 From: davida-ps Date: Sun, 1 Mar 2026 18:43:24 +0200 Subject: [PATCH 1/6] 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 --- .github/workflows/community-advisory.yml | 68 +++ .github/workflows/poll-nvd-cves.yml | 106 +++- README.md | 13 + scripts/backfill-exploitability.sh | 281 +++++++++ scripts/feed-utils.sh | 37 ++ scripts/populate-local-feed.sh | 129 ++++- scripts/prepare-to-push.sh | 6 +- skills/clawsec-feed/CHANGELOG.md | 19 + skills/clawsec-feed/SKILL.md | 100 +++- skills/clawsec-feed/skill.json | 7 +- skills/clawsec-nanoclaw/CHANGELOG.md | 20 + skills/clawsec-nanoclaw/INSTALL.md | 76 ++- skills/clawsec-nanoclaw/README.md | 16 +- skills/clawsec-nanoclaw/SKILL.md | 59 +- .../host-services/advisory-cache.ts | 42 +- skills/clawsec-nanoclaw/lib/advisories.ts | 42 +- skills/clawsec-nanoclaw/lib/risk.ts | 88 +++ skills/clawsec-nanoclaw/lib/types.ts | 2 + .../mcp-tools/advisory-tools.ts | 86 ++- skills/clawsec-nanoclaw/skill.json | 17 +- skills/clawsec-suite/CHANGELOG.md | 15 + skills/clawsec-suite/HEARTBEAT.md | 15 +- skills/clawsec-suite/SKILL.md | 15 +- skills/clawsec-suite/skill.json | 2 +- utils/analyze_exploitability.py | 531 ++++++++++++++++++ wiki/exploitability-scoring.md | 420 ++++++++++++++ 26 files changed, 2015 insertions(+), 197 deletions(-) create mode 100755 scripts/backfill-exploitability.sh create mode 100644 scripts/feed-utils.sh create mode 100644 skills/clawsec-feed/CHANGELOG.md create mode 100644 skills/clawsec-nanoclaw/CHANGELOG.md create mode 100644 skills/clawsec-nanoclaw/lib/risk.ts create mode 100755 utils/analyze_exploitability.py create mode 100644 wiki/exploitability-scoring.md diff --git a/.github/workflows/community-advisory.yml b/.github/workflows/community-advisory.yml index d199a17..d6bf077 100644 --- a/.github/workflows/community-advisory.yml +++ b/.github/workflows/community-advisory.yml @@ -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: | diff --git a/.github/workflows/poll-nvd-cves.yml b/.github/workflows/poll-nvd-cves.yml index d94f08b..3ef6326 100644 --- a/.github/workflows/poll-nvd-cves.yml +++ b/.github/workflows/poll-nvd-cves.yml @@ -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: | diff --git a/README.md b/README.md index d332c7a..676a0c7 100644 --- a/README.md +++ b/README.md @@ -203,6 +203,17 @@ The feed polls CVEs related to: - Prompt injection patterns - Agent security vulnerabilities +### Exploitability Context + +ClawSec enriches CVE advisories with **exploitability context** to help agents assess real-world risk beyond raw CVSS scores. Newly analyzed advisories can include: + +- **Exploit Evidence**: Whether public exploits exist in the wild +- **Weaponization Status**: If exploits are integrated into common attack frameworks +- **Attack Requirements**: Prerequisites needed for successful exploitation (network access, authentication, user interaction) +- **Risk Assessment**: Contextualized risk level combining technical severity with exploitability + +This feature helps agents prioritize vulnerabilities that pose immediate threats versus theoretical risks, enabling smarter security decisions. + ### Advisory Schema **NVD CVE Advisory:** @@ -217,6 +228,8 @@ The feed polls CVEs related to: "published": "2026-02-01T00:00:00Z", "cvss_score": 8.8, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-XXXXX", + "exploitability_score": "high|medium|low|unknown", + "exploitability_rationale": "Why this CVE is or is not likely exploitable in agent deployments", "references": ["..."], "action": "Recommended remediation" } diff --git a/scripts/backfill-exploitability.sh b/scripts/backfill-exploitability.sh new file mode 100755 index 0000000..6e95af0 --- /dev/null +++ b/scripts/backfill-exploitability.sh @@ -0,0 +1,281 @@ +#!/bin/bash +# backfill-exploitability.sh +# Adds exploitability scoring to existing advisories in feed.json that don't have it yet. +# Historical maintenance utility: normal advisory generation should use +# poll-nvd workflow (init/reset when rebuilding) or populate-local-feed.sh. +# +# Usage: ./scripts/backfill-exploitability.sh [--dry-run] [--feed PATH] +# --dry-run Show what would be updated without making changes +# --feed PATH Use specified feed file (default: advisories/feed.json) + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +# shellcheck source=./feed-utils.sh +source "$SCRIPT_DIR/feed-utils.sh" + +# Configuration +init_feed_paths "$PROJECT_ROOT" +ANALYZER="$PROJECT_ROOT/utils/analyze_exploitability.py" +SIGNING_PRIVATE_KEY="${CLAWSEC_FEED_SIGNING_PRIVATE_KEY_PATH:-${CLAWSEC_SIGNING_PRIVATE_KEY_PATH:-$PROJECT_ROOT/clawsec-signing-private.pem}}" +SIGNING_PUBLIC_KEY="${CLAWSEC_FEED_SIGNING_PUBLIC_KEY_PATH:-${CLAWSEC_SIGNING_PUBLIC_KEY_PATH:-$PROJECT_ROOT/clawsec-signing-public.pem}}" +SIGNING_PASSPHRASE="${CLAWSEC_FEED_SIGNING_PRIVATE_KEY_PASSPHRASE:-${CLAWSEC_SIGNING_PRIVATE_KEY_PASSPHRASE:-}}" + +sign_and_verify_feed_signature() { + local feed_file="$1" + local signature_file="$2" + local tmp_dir + local tmp_signature + local signature_bin + local passin_file + + tmp_dir=$(mktemp -d) + tmp_signature="${signature_file}.tmp.$$" + signature_bin="$tmp_dir/signature.bin" + passin_file="$tmp_dir/passin.txt" + + if [ -n "$SIGNING_PASSPHRASE" ]; then + printf '%s' "$SIGNING_PASSPHRASE" > "$passin_file" + chmod 600 "$passin_file" + + if ! openssl pkeyutl -sign -rawin -inkey "$SIGNING_PRIVATE_KEY" -passin "file:$passin_file" -in "$feed_file" \ + | openssl base64 -A > "$tmp_signature"; then + rm -rf "$tmp_dir" + rm -f "$tmp_signature" + echo "Error: Failed to sign $feed_file" >&2 + return 1 + fi + elif ! openssl pkeyutl -sign -rawin -inkey "$SIGNING_PRIVATE_KEY" -in "$feed_file" \ + | openssl base64 -A > "$tmp_signature"; then + rm -rf "$tmp_dir" + rm -f "$tmp_signature" + echo "Error: Failed to sign $feed_file" >&2 + return 1 + fi + + if ! openssl base64 -d -A -in "$tmp_signature" -out "$signature_bin"; then + rm -rf "$tmp_dir" + rm -f "$tmp_signature" + echo "Error: Failed to decode generated signature for $feed_file" >&2 + return 1 + fi + + if ! openssl pkeyutl -verify -rawin -pubin -inkey "$SIGNING_PUBLIC_KEY" -sigfile "$signature_bin" -in "$feed_file" >/dev/null; then + rm -rf "$tmp_dir" + rm -f "$tmp_signature" + echo "Error: Signature verification failed after signing $feed_file" >&2 + return 1 + fi + + mv "$tmp_signature" "$signature_file" + rm -rf "$tmp_dir" + echo "✓ Re-signed and verified: $signature_file" +} + +# Parse args +DRY_RUN=false +REQUIRE_SIGNING=false + +while [[ $# -gt 0 ]]; do + case $1 in + --dry-run) + DRY_RUN=true + shift + ;; + --feed) + FEED_PATH="$2" + shift 2 + ;; + *) + echo "Unknown option: $1" + echo "Usage: $0 [--dry-run] [--feed PATH]" + exit 1 + ;; + esac +done + +echo "=== ClawSec Exploitability Backfill ===" +echo "Feed path: $FEED_PATH" +echo "Dry run: $DRY_RUN" +echo "" + +# Verify prerequisites +if [ ! -f "$FEED_PATH" ]; then + echo "Error: Feed file not found: $FEED_PATH" + exit 1 +fi + +if [ ! -f "$ANALYZER" ]; then + echo "Error: Analyzer script not found: $ANALYZER" + exit 1 +fi + +# Check Python availability +if ! command -v python3 &> /dev/null; then + echo "Error: python3 is required but not found in PATH" + exit 1 +fi + +# Verify analyzer works +if ! python3 "$ANALYZER" --help &> /dev/null; then + echo "Error: Analyzer script failed to run. Check Python environment." + exit 1 +fi + +# Determine whether detached signatures must be regenerated. +# Runtime agents that only have public keys should run in dry-run mode. +if [ "$DRY_RUN" = "false" ]; then + if [ -f "${FEED_PATH}.sig" ]; then + REQUIRE_SIGNING=true + fi +fi + +if [ "$REQUIRE_SIGNING" = "true" ]; then + if ! command -v openssl &> /dev/null; then + echo "Error: openssl is required for detached signature signing/verification" + exit 1 + fi + + if [ ! -f "$SIGNING_PRIVATE_KEY" ]; then + echo "Error: Signing private key not found: $SIGNING_PRIVATE_KEY" + echo "This backfill updates signed feed artifacts. Use --dry-run in public-key-only environments." + exit 1 + fi + + if [ ! -f "$SIGNING_PUBLIC_KEY" ]; then + echo "Error: Signing public key not found: $SIGNING_PUBLIC_KEY" + exit 1 + fi +fi + +# Create temp directory +TEMP_DIR=$(mktemp -d) +trap 'rm -rf "$TEMP_DIR"' EXIT + +echo "=== Analyzing Feed ===" + +# Extract advisories without exploitability_score +jq '.advisories | map(select(.exploitability_score == null or .exploitability_score == ""))' \ + "$FEED_PATH" > "$TEMP_DIR/missing_exploitability.json" + +MISSING_COUNT=$(jq 'length' "$TEMP_DIR/missing_exploitability.json") +TOTAL_COUNT=$(jq '.advisories | length' "$FEED_PATH") +ALREADY_DONE=$((TOTAL_COUNT - MISSING_COUNT)) + +echo "Total advisories: $TOTAL_COUNT" +echo "Already have exploitability: $ALREADY_DONE" +echo "Missing exploitability: $MISSING_COUNT" +echo "" + +if [ "$MISSING_COUNT" -eq 0 ]; then + echo "✓ All advisories already have exploitability scores!" + exit 0 +fi + +if [ "$DRY_RUN" = "true" ]; then + echo "=== Dry Run - Would Update These Advisories ===" + jq -r '.[] | .id' "$TEMP_DIR/missing_exploitability.json" + echo "" + echo "Total advisories to update: $MISSING_COUNT" + exit 0 +fi + +echo "=== Processing Advisories ===" + +# Process each advisory +PROCESSED=0 +FAILED=0 + +# Read original feed to preserve all metadata +cp "$FEED_PATH" "$TEMP_DIR/feed_working.json" + +while IFS= read -r advisory; do + CVE_ID=$(echo "$advisory" | jq -r '.id') + + echo -n "Processing $CVE_ID... " + + # Prepare input for analyzer + ANALYZER_INPUT=$(echo "$advisory" | jq '{ + cve_id: .id, + cvss_score: (.cvss_score // 0.0), + type: .type, + description: .description, + references: (.references // []) + }') + + # Run analyzer + if ANALYSIS=$(echo "$ANALYZER_INPUT" | python3 "$ANALYZER" --json --check-exploits 2>/dev/null); then + # Extract exploitability fields + EXPL_SCORE=$(echo "$ANALYSIS" | jq -r '.exploitability_score // "unknown"') + EXPL_RATIONALE=$(echo "$ANALYSIS" | jq -r '.exploitability_rationale // "No rationale available"') + + # Update advisory in working feed + jq --arg id "$CVE_ID" \ + --arg score "$EXPL_SCORE" \ + --arg rationale "$EXPL_RATIONALE" \ + '(.advisories[] | select(.id == $id)) |= (. + { + exploitability_score: $score, + exploitability_rationale: $rationale + })' "$TEMP_DIR/feed_working.json" > "$TEMP_DIR/feed_updated.json" + + mv "$TEMP_DIR/feed_updated.json" "$TEMP_DIR/feed_working.json" + + echo "✓ $EXPL_SCORE" + PROCESSED=$((PROCESSED + 1)) + else + echo "✗ Failed" + FAILED=$((FAILED + 1)) + fi +done < <(jq -c '.[]' "$TEMP_DIR/missing_exploitability.json") + +# Check if loop executed successfully +if [ ! -f "$TEMP_DIR/feed_working.json" ]; then + echo "Error: Feed processing failed" + exit 1 +fi + +echo "" +echo "=== Processing Complete ===" +echo "Processed: $PROCESSED" +echo "Failed: $FAILED" +echo "" + +# Write updated feed +echo "Writing updated feed to: $FEED_PATH" +cp "$TEMP_DIR/feed_working.json" "$FEED_PATH" + +# Update feed version and timestamp +CURRENT_VERSION=$(jq -r '.version' "$FEED_PATH") +UPDATED_TS=$(date -u +%Y-%m-%dT%H:%M:%SZ) + +jq --arg ts "$UPDATED_TS" '.updated = $ts' "$FEED_PATH" > "$TEMP_DIR/feed_final.json" +mv "$TEMP_DIR/feed_final.json" "$FEED_PATH" + +echo "✓ Updated feed version: $CURRENT_VERSION" +echo "✓ Updated timestamp: $UPDATED_TS" +echo "" + +if [ "$REQUIRE_SIGNING" = "true" ]; then + echo "" + echo "=== Re-signing Advisory Feed ===" + + if [ -f "${FEED_PATH}.sig" ]; then + if ! sign_and_verify_feed_signature "$FEED_PATH" "${FEED_PATH}.sig"; then + exit 1 + fi + fi +fi + +echo "" +echo "=== Summary ===" +echo "✓ Backfill complete!" +echo "✓ $PROCESSED advisories updated with exploitability scores" + +if [ "$FAILED" -gt 0 ]; then + echo "⚠ $FAILED advisories failed analysis (kept original data)" +fi + +# Verify final state +FINAL_MISSING=$(jq '.advisories | map(select(.exploitability_score == null or .exploitability_score == "")) | length' "$FEED_PATH") +echo "✓ Advisories still missing exploitability: $FINAL_MISSING" diff --git a/scripts/feed-utils.sh b/scripts/feed-utils.sh new file mode 100644 index 0000000..5f7b29f --- /dev/null +++ b/scripts/feed-utils.sh @@ -0,0 +1,37 @@ +#!/bin/bash +# feed-utils.sh +# Shared advisory feed path and sync helpers for local/maintenance scripts. + +init_feed_paths() { + local project_root="$1" + + : "${FEED_PATH:=$project_root/advisories/feed.json}" + : "${SKILL_FEED_PATH:=$project_root/skills/clawsec-feed/advisories/feed.json}" + : "${PUBLIC_FEED_PATH:=$project_root/public/advisories/feed.json}" +} + +sync_feed_to_mirrors() { + local source_feed="$1" + local mode="${2:-create}" + + local target + for target in "$SKILL_FEED_PATH" "$PUBLIC_FEED_PATH"; do + case "$mode" in + create) + mkdir -p "$(dirname "$target")" + cp "$source_feed" "$target" + echo "✓ Updated: $target" + ;; + existing-only) + if [ -f "$target" ]; then + cp "$source_feed" "$target" + echo "✓ Updated: $target" + fi + ;; + *) + echo "Error: unsupported mirror sync mode: $mode" >&2 + return 1 + ;; + esac + done +} diff --git a/scripts/populate-local-feed.sh b/scripts/populate-local-feed.sh index 41997e5..ec67d61 100755 --- a/scripts/populate-local-feed.sh +++ b/scripts/populate-local-feed.sh @@ -11,13 +11,14 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +# shellcheck source=./feed-utils.sh +source "$SCRIPT_DIR/feed-utils.sh" # Configuration - same as pipeline -FEED_PATH="$PROJECT_ROOT/advisories/feed.json" -SKILL_FEED_PATH="$PROJECT_ROOT/skills/clawsec-feed/advisories/feed.json" -PUBLIC_FEED_PATH="$PROJECT_ROOT/public/advisories/feed.json" +init_feed_paths "$PROJECT_ROOT" KEYWORDS="OpenClaw clawdbot Moltbot NanoClaw WhatsApp-bot baileys" GITHUB_REF_PATTERN="github.com/openclaw/openclaw github.com/qwibitai/NanoClaw" +ANALYZER="$PROJECT_ROOT/utils/analyze_exploitability.py" # Parse args DAYS_BACK=120 @@ -46,6 +47,22 @@ echo "Days back: $DAYS_BACK" echo "Force mode: $FORCE" echo "" +# Verify exploitability analyzer prerequisites +if ! command -v python3 &> /dev/null; then + echo "Error: python3 is required but not found in PATH" + exit 1 +fi + +if [ ! -f "$ANALYZER" ]; then + echo "Error: Exploitability analyzer not found: $ANALYZER" + exit 1 +fi + +if ! python3 "$ANALYZER" --help &> /dev/null; then + echo "Error: Exploitability analyzer failed to run. Check Python environment." + exit 1 +fi + # Create temp directory TEMP_DIR=$(mktemp -d) trap 'rm -rf "$TEMP_DIR"' EXIT @@ -62,7 +79,7 @@ fi if [ -z "${START_DATE:-}" ]; then # macOS vs Linux date compatibility if date -v-1d > /dev/null 2>&1; then - START_DATE=$(date -u -v-${DAYS_BACK}d +%Y-%m-%dT%H:%M:%S.000Z) + START_DATE=$(date -u -v-"${DAYS_BACK}"d +%Y-%m-%dT%H:%M:%S.000Z) else START_DATE=$(date -u -d "${DAYS_BACK} days ago" +%Y-%m-%dT%H:%M:%S.000Z) fi @@ -74,8 +91,8 @@ echo "End date: $END_DATE" echo "" # URL encode dates -START_ENC=$(echo "$START_DATE" | sed 's/:/%3A/g') -END_ENC=$(echo "$END_DATE" | sed 's/:/%3A/g') +START_ENC=${START_DATE//:/%3A} +END_ENC=${END_DATE//:/%3A} echo "=== Fetching CVEs from NVD ===" @@ -267,7 +284,7 @@ jq --argjson existing "$EXISTING_JSON" ' | if length == 0 then ["openclaw@*", "nanoclaw@*"] else . end ); - [.[] | + [.[] | select(.cve.id as $id | $existing | index($id) | not) | { id: .cve.id, @@ -281,7 +298,9 @@ jq --argjson existing "$EXISTING_JSON" ' 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 } ] ' "$TEMP_DIR/filtered_cves.json" > "$TEMP_DIR/new_advisories.json" @@ -296,6 +315,87 @@ if [ "$NEW_COUNT" -eq 0 ]; then exit 0 fi +echo "" +echo "=== Analyzing Exploitability ===" + +# Build CVSS vector lookup for enriched analysis inputs. +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 +' "$TEMP_DIR/filtered_cves.json" > "$TEMP_DIR/cvss_vectors.json" + +ANALYZED_COUNT=0 +FAILED_ANALYSIS=0 + +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] // ""' "$TEMP_DIR/cvss_vectors.json") + VULN_TYPE=$(echo "$advisory" | jq -r '.type // ""') + DESCRIPTION=$(echo "$advisory" | jq -r '.description // ""') + REFERENCES=$(echo "$advisory" | jq -c '.references // []') + + 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 + }') + + if ANALYSIS=$(echo "$INPUT_JSON" | python3 "$ANALYZER" --json --check-exploits 2>/dev/null); then + echo "$ANALYSIS" > "$TEMP_DIR/exploitability_${CVE_ID}.json" + SCORE=$(echo "$ANALYSIS" | jq -r '.exploitability_score // "unknown"') + echo " ✓ $CVE_ID -> $SCORE" + ANALYZED_COUNT=$((ANALYZED_COUNT + 1)) + else + echo " ⚠ $CVE_ID analysis failed; keeping null exploitability fields" + FAILED_ANALYSIS=$((FAILED_ANALYSIS + 1)) + fi +done < <(jq -c '.[]' "$TEMP_DIR/new_advisories.json") + +if ls "$TEMP_DIR"/exploitability_*.json >/dev/null 2>&1; then + jq -s '.' "$TEMP_DIR"/exploitability_*.json > "$TEMP_DIR/exploitability_analyses.json" +else + echo '[]' > "$TEMP_DIR/exploitability_analyses.json" +fi + +jq --slurpfile analyses "$TEMP_DIR/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 + ) +' "$TEMP_DIR/new_advisories.json" > "$TEMP_DIR/new_advisories_enriched.json" + +mv "$TEMP_DIR/new_advisories_enriched.json" "$TEMP_DIR/new_advisories.json" + +echo "Exploitability analysis complete: $ANALYZED_COUNT analyzed, $FAILED_ANALYSIS failed" + echo "" echo "=== New Advisories ===" jq -r '.[] | " \(.id) [\(.severity)] - \(.title)"' "$TEMP_DIR/new_advisories.json" @@ -338,16 +438,9 @@ if jq empty "$TEMP_DIR/updated_feed.json" 2>/dev/null; then # Update main feed cp "$TEMP_DIR/updated_feed.json" "$FEED_PATH" echo "✓ Updated: $FEED_PATH" - - # Update skill feed - mkdir -p "$(dirname "$SKILL_FEED_PATH")" - cp "$FEED_PATH" "$SKILL_FEED_PATH" - echo "✓ Updated: $SKILL_FEED_PATH" - - # Update public feed for local dev - mkdir -p "$(dirname "$PUBLIC_FEED_PATH")" - cp "$FEED_PATH" "$PUBLIC_FEED_PATH" - echo "✓ Updated: $PUBLIC_FEED_PATH" + + # Sync feed mirrors for local skill/public consumers. + sync_feed_to_mirrors "$FEED_PATH" "create" echo "" TOTAL_ADVISORIES=$(jq '.advisories | length' "$FEED_PATH") diff --git a/scripts/prepare-to-push.sh b/scripts/prepare-to-push.sh index f0dafc7..8033327 100755 --- a/scripts/prepare-to-push.sh +++ b/scripts/prepare-to-push.sh @@ -76,13 +76,13 @@ fi # ESLint echo -e "\n${YELLOW}Running ESLint...${NC}" if $FIX_MODE; then - if npx eslint . --ext .ts,.tsx,.js,.jsx,.mjs --fix; then + if npx eslint . --ext .ts,.tsx,.js,.jsx,.mjs --ignore-pattern '.auto-claude/**' --fix; then check_pass "ESLint (with auto-fix)" else check_fail "ESLint found unfixable issues" fi else - if npx eslint . --ext .ts,.tsx,.js,.jsx,.mjs --max-warnings 0; then + if npx eslint . --ext .ts,.tsx,.js,.jsx,.mjs --ignore-pattern '.auto-claude/**' --max-warnings 0; then check_pass "ESLint" else check_fail "ESLint found issues (run with --fix to auto-fix)" @@ -190,7 +190,7 @@ print_header "Security" # Trivy FS Scan if command -v trivy &> /dev/null; then echo -e "\n${YELLOW}Running Trivy filesystem scan...${NC}" - if trivy fs . --severity CRITICAL,HIGH --exit-code 1 --ignore-unfixed; then + if trivy fs . --severity CRITICAL,HIGH --exit-code 1 --ignore-unfixed --skip-dirs .auto-claude --skip-files clawsec-signing-private.pem; then check_pass "Trivy filesystem scan" else check_fail "Trivy found CRITICAL/HIGH vulnerabilities" diff --git a/skills/clawsec-feed/CHANGELOG.md b/skills/clawsec-feed/CHANGELOG.md new file mode 100644 index 0000000..a3dfce1 --- /dev/null +++ b/skills/clawsec-feed/CHANGELOG.md @@ -0,0 +1,19 @@ +# Changelog + +All notable changes to the ClawSec Feed skill will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.0.5] - 2026-02-28 + +### Added + +- Exploitability-focused advisory guidance, including filtering and prioritization examples. +- Notification examples that include exploitability context and rationale. + +### Changed + +- Clarified exploitability scoring guidance to match runtime values (`high|medium|low|unknown`). +- Updated response-priority guidance to align with exploitability-first triage. +- De-duplicated exploitability filtering guidance in `SKILL.md` by pointing to canonical docs in `wiki/exploitability-scoring.md` and `clawsec-suite`. diff --git a/skills/clawsec-feed/SKILL.md b/skills/clawsec-feed/SKILL.md index ea57eba..421f773 100644 --- a/skills/clawsec-feed/SKILL.md +++ b/skills/clawsec-feed/SKILL.md @@ -1,6 +1,6 @@ --- name: clawsec-feed -version: 0.0.4 +version: 0.0.5 description: Security advisory feed with automated NVD CVE polling for OpenClaw-related vulnerabilities. Updated daily. homepage: https://clawsec.prompt.security metadata: {"openclaw":{"emoji":"📡","category":"security"}} @@ -318,7 +318,9 @@ curl -sSL --fail --show-error --retry 3 --retry-delay 1 "$FEED_URL" "description": "Skill sends user data to external server", "affected": ["helper-plus@1.0.0", "helper-plus@1.0.1"], "action": "Remove immediately", - "published": "2026-02-01T10:00:00Z" + "published": "2026-02-01T10:00:00Z", + "exploitability_score": "critical", + "exploitability_rationale": "Trivially exploitable through normal skill usage; no special conditions required. Active exploitation observed in the wild." } ] } @@ -385,6 +387,42 @@ fi echo "$RECENT" ``` +### Filter by exploitability score + +Shared exploitability prioritization guidance is maintained in: + +- [`wiki/exploitability-scoring.md`](../../wiki/exploitability-scoring.md) +- [`skills/clawsec-suite/SKILL.md`](../clawsec-suite/SKILL.md) ("Quick feed check") + +### Get exploitability context for an advisory + +```bash +# Show exploitability details for a specific CVE +CVE_ID="CVE-2026-27488" +echo "$FEED" | jq --arg cve "$CVE_ID" '.advisories[] | select(.id == $cve) | { + id: .id, + severity: .severity, + exploitability_score: .exploitability_score, + exploitability_rationale: .exploitability_rationale, + title: .title +}' +``` + +### Prioritize advisories by exploitability + +```bash +# Sort advisories by exploitability (critical → high → medium → low) +# This helps agents focus on the most immediately actionable threats +echo "$FEED" | jq '[.advisories[] | select(.exploitability_score != null)] | + sort_by( + if .exploitability_score == "critical" then 0 + elif .exploitability_score == "high" then 1 + elif .exploitability_score == "medium" then 2 + elif .exploitability_score == "low" then 3 + else 4 end + )' +``` + --- ## Cross-Reference Installed Skills @@ -476,23 +514,75 @@ done --- +## Prioritizing High-Exploitability Threats + +**IMPORTANT:** When reviewing advisories, always prioritize by **exploitability score** in addition to severity. The exploitability score indicates how easily a vulnerability can be exploited in practice, helping you focus on the most actionable threats. + +### Exploitability Priority Levels + +| Exploitability | Meaning | Action Priority | +|----------------|---------|-----------------| +| `high` | Trivially or easily exploitable with public tooling | **Immediate notification** | +| `medium` | Exploitable but requires specific conditions | **Standard notification** | +| `low` | Difficult to exploit or theoretical | **Low priority notification** | + +### How to Use Exploitability in Notifications + +1. **Filter for high-exploitability first:** + ```bash + # Get high exploitability advisories + echo "$FEED" | jq '.advisories[] | select(.exploitability_score == "high")' + ``` + +2. **Include exploitability in notifications:** + ``` + 📡 ClawSec Feed: High-exploitability alert + + CRITICAL - CVE-2026-27488 (Exploitability: HIGH) + → Trivially exploitable RCE in skill-loader v2.1.0 + → Public exploit code available + → Recommended action: Immediate removal or upgrade to v2.1.1 + ``` + +3. **Prioritize by both severity AND exploitability:** + - A HIGH severity + HIGH exploitability CVE is more urgent than a CRITICAL severity + LOW exploitability CVE + - Focus user attention on threats that are both severe and easily exploitable + - Include the exploitability rationale to help users understand the risk context + +### Example Notification Priority Order + +When multiple advisories exist, present them in this order: +1. **Critical severity + High exploitability** - most urgent +2. **High severity + High exploitability** +3. **Critical severity + Medium/Low exploitability** +4. **High severity + Medium/Low exploitability** +5. **Medium/Low severity** (any exploitability) + +This ensures you alert users to the most actionable, immediately dangerous threats first. + +--- + ## When to Notify Your User **Notify Immediately (Critical):** - New critical advisory affecting an installed skill - Active exploitation detected +- **High exploitability score** (regardless of severity) **Notify Soon (High):** - New high-severity advisory affecting installed skills - Failed to fetch advisory feed (network issue?) +- Medium exploitability with high severity **Notify at Next Interaction (Medium):** - New medium-severity advisories - General security updates +- Low exploitability advisories **Log Only (Low/Info):** - Low-severity advisories (mention if user asks) - Feed checked, no new advisories +- Theoretical vulnerabilities (low exploitability, low severity) --- @@ -503,11 +593,13 @@ done ``` 📡 ClawSec Feed: 2 new advisories since last check -CRITICAL - GA-2026-015: Malicious prompt pattern "ignore-all" +CRITICAL - GA-2026-015: Malicious prompt pattern "ignore-all" (Exploitability: HIGH) → Detected prompt injection technique. Update your system prompt defenses. + → Exploitability: Easily exploitable with publicly documented techniques. -HIGH - GA-2026-016: Vulnerable skill "data-helper" v1.2.0 +HIGH - GA-2026-016: Vulnerable skill "data-helper" v1.2.0 (Exploitability: MEDIUM) → You have this installed! Recommended action: Update to v1.2.1 or remove. + → Exploitability: Requires specific configuration; not trivially exploitable. ``` ### If nothing new: diff --git a/skills/clawsec-feed/skill.json b/skills/clawsec-feed/skill.json index 84fbd56..c6853a6 100644 --- a/skills/clawsec-feed/skill.json +++ b/skills/clawsec-feed/skill.json @@ -1,6 +1,6 @@ { "name": "clawsec-feed", - "version": "0.0.4", + "version": "0.0.5", "description": "Security advisory feed monitoring for AI agents. Subscribe to community-driven threat intelligence.", "author": "prompt-security", "license": "AGPL-3.0-or-later", @@ -21,6 +21,11 @@ "required": true, "description": "Advisory feed skill documentation" }, + { + "path": "CHANGELOG.md", + "required": true, + "description": "Version history for advisory feed updates" + }, { "path": "advisories/feed.json", "required": true, diff --git a/skills/clawsec-nanoclaw/CHANGELOG.md b/skills/clawsec-nanoclaw/CHANGELOG.md new file mode 100644 index 0000000..5645a87 --- /dev/null +++ b/skills/clawsec-nanoclaw/CHANGELOG.md @@ -0,0 +1,20 @@ +# Changelog + +All notable changes to the ClawSec NanoClaw compatibility skill will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.0.2] - 2026-02-28 + +### Added + +- Exploitability-aware advisory output in NanoClaw MCP tools (`exploitability_score`, `exploitability_rationale`). +- Exploitability filtering (`exploitabilityScore`) for `clawsec_list_advisories`. + +### Changed + +- Updated NanoClaw advisory sorting and pre-install safety recommendation logic to prioritize exploitability context. +- Updated NanoClaw integration docs to match current host/container integration points (`src/ipc.ts`, `src/index.ts`) and current cache schema. +- Removed duplicate exploitability normalization logic from MCP advisory tools and now reuse `normalizeExploitabilityScore` from `lib/risk.ts`. +- Reused `matchesAffectedSpecifier` from `lib/advisories.ts` in MCP advisory tools to keep skill/version matching logic centralized and consistent. diff --git a/skills/clawsec-nanoclaw/INSTALL.md b/skills/clawsec-nanoclaw/INSTALL.md index ed5a996..74033e5 100644 --- a/skills/clawsec-nanoclaw/INSTALL.md +++ b/skills/clawsec-nanoclaw/INSTALL.md @@ -8,7 +8,7 @@ ClawSec provides security advisory monitoring for NanoClaw through: - **MCP Tools**: Agents can check for vulnerabilities via `clawsec_check_advisories` - **Advisory Feed**: Automatic monitoring of https://clawsec.prompt.security/advisories/feed.json - **Signature Verification**: Ed25519-signed feeds ensure integrity -- **Platform Targeting**: Advisories can be NanoClaw-specific or cross-platform +- **Exploitability Context**: Advisories include exploitability score and rationale for triage ## Prerequisites @@ -57,18 +57,30 @@ in each tool file). Add the host-side IPC handlers for ClawSec operations. -**File**: `host/ipc-handler.ts` +**File**: `src/ipc.ts` ```typescript -// Add this import at the top -import { registerClawSecHandlers } from '../skills/clawsec-nanoclaw/host-services/ipc-handlers.js'; +// Add these imports at the top +import { handleAdvisoryIpc } from '../skills/clawsec-nanoclaw/host-services/ipc-handlers.js'; +import { AdvisoryCacheManager } from '../skills/clawsec-nanoclaw/host-services/advisory-cache.js'; +import { SkillSignatureVerifier } from '../skills/clawsec-nanoclaw/host-services/skill-signature-handler.js'; -// In your IPC handler setup function -export function setupIpcHandlers() { - // ... your existing handlers ... +// Initialize these once in host startup and pass through deps +const advisoryCacheManager = new AdvisoryCacheManager('/workspace/project/data', logger); +const signatureVerifier = new SkillSignatureVerifier(); - // Register ClawSec handlers - registerClawSecHandlers(); +// In processTaskIpc switch: +case 'refresh_advisory_cache': +case 'verify_skill_signature': + await handleAdvisoryIpc( + data, + { advisoryCacheManager, signatureVerifier }, + logger, + sourceGroup + ); + break; +default: + // existing task handling } ``` @@ -76,23 +88,25 @@ export function setupIpcHandlers() { Add the advisory cache manager to your host services. -**File**: `host/index.ts` (or your main entry point) +**File**: `src/index.ts` (or your main entry point) ```typescript -// Add this import -import { startAdvisoryCache } from '../skills/clawsec-nanoclaw/host-services/advisory-cache.js'; +import { AdvisoryCacheManager } from '../skills/clawsec-nanoclaw/host-services/advisory-cache.js'; // Start the service when your host process starts async function main() { // ... your existing initialization ... - // Start ClawSec advisory cache (fetches feed every 6 hours) - startAdvisoryCache({ - cacheFile: '/workspace/project/data/clawsec-advisory-cache.json', - feedUrl: 'https://clawsec.prompt.security/advisories/feed.json', - publicKeyPath: '/workspace/project/skills/clawsec-nanoclaw/advisories/feed-signing-public.pem', - refreshInterval: 6 * 60 * 60 * 1000, // 6 hours - }); + // Initialize cache manager and prime it at startup + const advisoryCacheManager = new AdvisoryCacheManager('/workspace/project/data', logger); + await advisoryCacheManager.initialize(); + + // Recommended refresh cadence (6h) + setInterval(() => { + advisoryCacheManager.refresh().catch((error) => { + logger.error({ error }, 'Periodic advisory cache refresh failed'); + }); + }, 6 * 60 * 60 * 1000); // ... rest of your startup ... } @@ -151,9 +165,9 @@ cat /workspace/project/data/clawsec-advisory-cache.json You should see: - `feed`: Array of advisories -- `signature`: Ed25519 signature -- `lastFetch`: Timestamp of last update +- `fetchedAt`: Timestamp of last update - `verified`: Should be `true` +- `publicKeyFingerprint`: SHA-256 fingerprint of the pinned signing key ## Usage Examples @@ -183,13 +197,13 @@ You can also call the MCP tools directly from agent code: ```typescript // Check all installed skills const result = await tools.clawsec_check_advisories({ - skillsRoot: '/workspace/project/skills' + installRoot: '/home/node/.claude/skills' }); // Check specific skill before installation const safetyCheck = await tools.clawsec_check_skill_safety({ skillName: 'risky-skill', - version: '1.0.0' + skillVersion: '1.0.0' }); ``` @@ -199,19 +213,19 @@ const safetyCheck = await tools.clawsec_check_skill_safety({ Default: `/workspace/project/data/clawsec-advisory-cache.json` -To change, update the `cacheFile` parameter in `startAdvisoryCache()`. +To change, pass a different data directory path to `new AdvisoryCacheManager(dataDir, logger)`. ### Refresh Interval Default: 6 hours -To change, update the `refreshInterval` parameter (in milliseconds). +To change, update the `setInterval(...)` duration (in milliseconds) in host startup. ### Feed URL Default: `https://clawsec.prompt.security/advisories/feed.json` -To use a mirror or custom feed, update the `feedUrl` parameter. +To use a mirror or custom feed, update `FEED_URL` in `skills/clawsec-nanoclaw/host-services/advisory-cache.ts`. ## Platform-Specific Advisories @@ -222,7 +236,7 @@ ClawSec advisories can target specific platforms: - **`platforms: ["openclaw", "nanoclaw"]`**: Affects both - **No `platforms` field**: Applies to all platforms -The MCP tools automatically filter advisories based on your platform. +Platform metadata is preserved in advisory records and can be filtered by your policy layer. ## Security @@ -260,7 +274,7 @@ Never manually edit the cache file - it will break signature verification. **Problem**: Advisory cache is empty or stale **Solution**: -1. Check that `startAdvisoryCache()` is called in your host entry point +1. Check that `AdvisoryCacheManager.initialize()` is called in your host entry point 2. Verify network access to `clawsec.prompt.security` 3. Check host logs for fetch errors 4. Manually trigger: `curl https://clawsec.prompt.security/advisories/feed.json` @@ -280,7 +294,7 @@ Never manually edit the cache file - it will break signature verification. **Problem**: Tools return errors about IPC **Solution**: -1. Verify IPC handlers are registered in `host/ipc-handler.ts` +1. Verify IPC handlers are registered in `src/ipc.ts` 2. Check that IPC directory exists and is writable 3. Ensure host process is running 4. Check host logs for handler errors @@ -290,8 +304,8 @@ Never manually edit the cache file - it will break signature verification. To remove ClawSec from NanoClaw: 1. Remove MCP tool registration from `ipc-mcp-stdio.ts` -2. Remove IPC handler registration from `host/ipc-handler.ts` -3. Remove `startAdvisoryCache()` call from host entry point +2. Remove IPC handler registration from `src/ipc.ts` +3. Remove `AdvisoryCacheManager` initialization from host entry point 4. Delete the skill directory: `rm -rf skills/clawsec-nanoclaw` 5. Delete the cache file: `rm /workspace/project/data/clawsec-advisory-cache.json` 6. Restart NanoClaw diff --git a/skills/clawsec-nanoclaw/README.md b/skills/clawsec-nanoclaw/README.md index bd1b2e2..9ca28a9 100644 --- a/skills/clawsec-nanoclaw/README.md +++ b/skills/clawsec-nanoclaw/README.md @@ -56,9 +56,9 @@ ClawSec provides a complete security skill for NanoClaw deployments: - `clawsec_integrity_status` - View file baseline status - `clawsec_verify_audit` - Verify audit log hash chain -- **Advisory Cache Service**: Automatic feed fetching every 6 hours +- **Advisory Cache Service**: Host-managed feed fetching with signature validation - **Signature Verification**: Ed25519-signed feeds ensure integrity -- **Platform Filtering**: Shows only relevant advisories for NanoClaw +- **Exploitability Context**: Surfaces `exploitability_score` and rationale to reduce alert fatigue - **IPC Communication**: Container-safe host communication ### Installation @@ -77,19 +77,19 @@ The skill integrates into three places: **1. MCP Tools** (container): ```typescript // container/agent-runner/src/ipc-mcp-stdio.ts -import { clawsecTools } from '../../../skills/clawsec-nanoclaw/mcp-tools/advisory-tools.js'; +import '../../../skills/clawsec-nanoclaw/mcp-tools/advisory-tools.js'; ``` **2. IPC Handlers** (host): ```typescript -// host/ipc-handler.ts -import { registerClawSecHandlers } from '../skills/clawsec-nanoclaw/host-services/ipc-handlers.js'; +// src/ipc.ts +import { handleAdvisoryIpc } from '../skills/clawsec-nanoclaw/host-services/ipc-handlers.js'; ``` **3. Cache Service** (host): ```typescript -// host/index.ts -import { startAdvisoryCache } from '../skills/clawsec-nanoclaw/host-services/advisory-cache.js'; +// src/index.ts +import { AdvisoryCacheManager } from '../skills/clawsec-nanoclaw/host-services/advisory-cache.js'; ``` ### Advisory Feed @@ -148,4 +148,4 @@ Planned features for future releases: - **Issues**: https://github.com/prompt-security/clawsec/issues - **Security**: security@prompt.security -- NanoClaw Repository: (link TBD) +- NanoClaw Repository: https://github.com/qwibitai/nanoclaw diff --git a/skills/clawsec-nanoclaw/SKILL.md b/skills/clawsec-nanoclaw/SKILL.md index 6dfe35e..44905c4 100644 --- a/skills/clawsec-nanoclaw/SKILL.md +++ b/skills/clawsec-nanoclaw/SKILL.md @@ -1,6 +1,6 @@ --- name: clawsec-nanoclaw -version: 0.0.1 +version: 0.0.2 description: Use when checking for security vulnerabilities in NanoClaw skills, before installing new skills, or when asked about security advisories affecting the bot --- @@ -10,7 +10,7 @@ Security advisory monitoring that protects your WhatsApp bot from known vulnerab ## Overview -ClawSec provides MCP tools that check installed skills against a curated feed of security advisories. It prevents installation of vulnerable skills and alerts you to issues in existing ones. +ClawSec provides MCP tools that check installed skills against a curated feed of security advisories. It prevents installation of vulnerable skills, includes exploitability context for triage, and alerts you to issues in existing ones. **Core principle:** Check before you install. Monitor what's running. @@ -36,7 +36,7 @@ Do NOT use for: // Before installing any skill const safety = await tools.clawsec_check_skill_safety({ skillName: 'new-skill', - version: '1.0.0' // optional + skillVersion: '1.0.0' // optional }); if (!safety.safe) { @@ -48,14 +48,16 @@ if (!safety.safe) { ### Security Audit ```typescript -// Check all installed skills +// Check all installed skills (defaults to ~/.claude/skills in the container) const result = await tools.clawsec_check_advisories({ - skillsRoot: '/workspace/project/skills' // optional + installRoot: '/home/node/.claude/skills' // optional }); -if (result.criticalCount > 0) { +if (result.matches.some((m) => + m.advisory.severity === 'critical' || m.advisory.exploitability_score === 'high' +)) { // Alert user immediately - console.error('CRITICAL vulnerabilities found!'); + console.error('Urgent advisories found!'); } ``` @@ -64,8 +66,8 @@ if (result.criticalCount > 0) { ```typescript // List advisories with filters const advisories = await tools.clawsec_list_advisories({ - platform: 'nanoclaw', // optional: nanoclaw, openclaw, or both - severity: 'critical' // optional: critical, high, medium, low + severity: 'high', // optional + exploitabilityScore: 'high' // optional }); ``` @@ -75,7 +77,7 @@ const advisories = await tools.clawsec_list_advisories({ |------|------|---------------| | Pre-install check | `clawsec_check_skill_safety` | `skillName` | | Audit all skills | `clawsec_check_advisories` | `installRoot` (optional) | -| Browse feed | `clawsec_list_advisories` | `severity`, `type` (optional) | +| Browse feed | `clawsec_list_advisories` | `severity`, `type`, `exploitabilityScore` (optional) | | Verify package signature | `clawsec_verify_skill_package` | `packagePath` | | Refresh advisory cache | `clawsec_refresh_cache` | (none) | | Check file integrity | `clawsec_check_integrity` | `mode`, `autoRestore` (optional) | @@ -110,7 +112,7 @@ if (safety.safe) { ```typescript // Add to scheduled tasks schedule_task({ - prompt: "Check for security advisories using clawsec_check_advisories and alert if any critical issues found", + prompt: "Check advisories using clawsec_check_advisories and alert when critical or high-exploitability matches appear", schedule_type: "cron", schedule_value: "0 9 * * *" // Daily at 9am }); @@ -125,8 +127,8 @@ You: I'll check installed skills for known vulnerabilities. [Use clawsec_check_advisories] Response: -✅ No critical issues found. -- 2 low-severity advisories (not urgent) +✅ No urgent issues found. +- 2 low-severity/low-exploitability advisories - All skills up to date ``` @@ -146,30 +148,33 @@ const safety = await tools.clawsec_check_skill_safety({ if (safety.safe) await installSkill('untrusted-skill'); ``` -### ❌ Ignoring platform filters +### ❌ Ignoring exploitability context ```typescript -// DON'T: Check OpenClaw advisories on NanoClaw -const advisories = await tools.clawsec_list_advisories({ - platform: 'openclaw' // Wrong platform! -}); +// DON'T: Use severity only +if (advisory.severity === 'high') { + notifyNow(advisory); +} ``` ```typescript -// DO: Use correct platform or let it auto-filter -const advisories = await tools.clawsec_list_advisories({ - platform: 'nanoclaw' // Correct -}); +// DO: Use exploitability + severity +if ( + advisory.exploitability_score === 'high' || + advisory.severity === 'critical' +) { + notifyNow(advisory); +} ``` ### ❌ Skipping critical severity ```typescript -// DON'T: Only check low severity -if (result.lowCount > 0) alert(); +// DON'T: Ignore high exploitability in medium severity advisories +if (advisory.severity === 'critical') alert(); ``` ```typescript -// DO: Prioritize critical and high -if (result.criticalCount > 0 || result.highCount > 0) { +// DO: Prioritize exploitability and severity together +if (advisory.exploitability_score === 'high' || advisory.severity === 'critical') { // Alert immediately } ``` @@ -182,7 +187,7 @@ if (result.criticalCount > 0 || result.highCount > 0) { **Signature Verification**: Ed25519 signed feeds -**Cache Location**: `/workspace/project/data/clawsec-cache.json` +**Cache Location**: `/workspace/project/data/clawsec-advisory-cache.json` See [INSTALL.md](./INSTALL.md) for setup and [docs/](./docs/) for advanced usage. diff --git a/skills/clawsec-nanoclaw/host-services/advisory-cache.ts b/skills/clawsec-nanoclaw/host-services/advisory-cache.ts index fad74fb..578ed37 100644 --- a/skills/clawsec-nanoclaw/host-services/advisory-cache.ts +++ b/skills/clawsec-nanoclaw/host-services/advisory-cache.ts @@ -16,6 +16,7 @@ import crypto from 'node:crypto'; import fs from 'node:fs/promises'; import https from 'node:https'; import path from 'node:path'; +import { evaluateAdvisoryRisk } from '../lib/risk.js'; // ClawSec public key (from clawsec-signing-public.pem) const PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY----- @@ -35,6 +36,8 @@ export interface Advisory { action?: string; published?: string; updated?: string; + exploitability_score?: 'high' | 'medium' | 'low' | 'unknown' | string; + exploitability_rationale?: string; affected: string[]; } @@ -376,42 +379,5 @@ export function evaluateSkillSafety(advisories: Advisory[]): { recommendation: 'install' | 'block' | 'review'; reason: string; } { - if (advisories.length === 0) { - return { safe: true, recommendation: 'install', reason: 'No advisories found' }; - } - - const hasMalicious = advisories.some((a) => a.type === 'malicious'); - const hasRemoveAction = advisories.some((a) => a.action === 'remove'); - const hasCritical = advisories.some((a) => a.severity === 'critical'); - const hasHigh = advisories.some((a) => a.severity === 'high'); - - if (hasMalicious || hasRemoveAction) { - return { - safe: false, - recommendation: 'block', - reason: 'Malicious skill or removal recommended', - }; - } - - if (hasCritical) { - return { - safe: false, - recommendation: 'block', - reason: 'Critical security advisory', - }; - } - - if (hasHigh) { - return { - safe: false, - recommendation: 'review', - reason: 'High severity advisory - user review recommended', - }; - } - - return { - safe: false, - recommendation: 'review', - reason: 'Advisory found - review before installing', - }; + return evaluateAdvisoryRisk(advisories); } diff --git a/skills/clawsec-nanoclaw/lib/advisories.ts b/skills/clawsec-nanoclaw/lib/advisories.ts index d7a34fa..efd6db3 100644 --- a/skills/clawsec-nanoclaw/lib/advisories.ts +++ b/skills/clawsec-nanoclaw/lib/advisories.ts @@ -121,6 +121,34 @@ export function versionMatches(version: string, versionSpec: string): boolean { return false; } +/** + * Checks whether an affected specifier matches a skill name/version. + * Optionally matches against a skill directory name as alias. + */ +export function matchesAffectedSpecifier( + affected: string, + skillName: string, + skillVersion: string | null, + skillDirName?: string +): boolean { + const parsed = parseAffectedSpecifier(affected); + if (!parsed) return false; + + const normalizedTarget = normalizeSkillName(parsed.name); + const normalizedSkillName = normalizeSkillName(skillName); + const normalizedDirName = skillDirName ? normalizeSkillName(skillDirName) : null; + + if (normalizedTarget !== normalizedSkillName && normalizedTarget !== normalizedDirName) { + return false; + } + + if (!skillVersion) { + return true; + } + + return versionMatches(skillVersion, parsed.versionSpec); +} + /** * Loads advisory feed from a remote URL with signature verification. */ @@ -269,10 +297,12 @@ export async function loadFeed( export function advisoryLooksHighRisk(advisory: Advisory): boolean { const type = advisory.type.toLowerCase(); const severity = advisory.severity.toLowerCase(); + const exploitability = (advisory.exploitability_score || 'unknown').toLowerCase(); const combined = `${advisory.title} ${advisory.description} ${advisory.action}`.toLowerCase(); - if (type === 'malicious_skill' || type === 'malicious_plugin') return true; + if (type.includes('malicious')) return true; if (severity === 'critical') return true; + if (exploitability === 'high') return true; if (/\b(malicious|exfiltrate|exfiltration|backdoor|trojan|stealer|credential theft)\b/.test(combined)) return true; if (/\b(remove|uninstall|disable|do not use|quarantine)\b/.test(combined)) return true; @@ -294,15 +324,7 @@ export function findAdvisoryMatches( if (affected.length === 0) continue; for (const specifier of affected) { - const parsed = parseAffectedSpecifier(specifier); - if (!parsed) continue; - - if (normalizeSkillName(parsed.name) !== normalizeSkillName(skillName)) { - continue; - } - - // If version specified, check if it matches - if (version && !versionMatches(version, parsed.versionSpec)) { + if (!matchesAffectedSpecifier(specifier, skillName, version)) { continue; } diff --git a/skills/clawsec-nanoclaw/lib/risk.ts b/skills/clawsec-nanoclaw/lib/risk.ts new file mode 100644 index 0000000..b5f2ae2 --- /dev/null +++ b/skills/clawsec-nanoclaw/lib/risk.ts @@ -0,0 +1,88 @@ +/** + * Shared advisory risk evaluation for NanoClaw host + MCP layers. + */ + +export type SkillSafetyRecommendation = 'install' | 'block' | 'review'; + +export interface AdvisoryRiskInput { + severity?: string; + type?: string; + action?: string; + exploitability_score?: string; +} + +export interface AdvisoryRiskEvaluation { + safe: boolean; + recommendation: SkillSafetyRecommendation; + reason: string; +} + +export function normalizeExploitabilityScore(score: unknown): 'high' | 'medium' | 'low' | 'unknown' { + const value = String(score || '').toLowerCase().trim(); + if (value === 'high' || value === 'medium' || value === 'low') { + return value; + } + return 'unknown'; +} + +export function evaluateAdvisoryRisk(advisories: AdvisoryRiskInput[]): AdvisoryRiskEvaluation { + if (advisories.length === 0) { + return { safe: true, recommendation: 'install', reason: 'No advisories found' }; + } + + const hasMalicious = advisories.some((a) => String(a.type || '').toLowerCase().includes('malicious')); + const hasRemoveAction = advisories.some((a) => + /\b(remove|uninstall|disable|quarantine|block)\b/i.test(String(a.action || '')) + ); + const hasCritical = advisories.some((a) => String(a.severity || '').toLowerCase() === 'critical'); + const hasHigh = advisories.some((a) => String(a.severity || '').toLowerCase() === 'high'); + const hasHighExploitability = advisories.some( + (a) => normalizeExploitabilityScore(a.exploitability_score) === 'high' + ); + + if (hasMalicious || hasRemoveAction) { + return { + safe: false, + recommendation: 'block', + reason: 'Malicious skill or removal recommended by ClawSec', + }; + } + + if (hasCritical && hasHighExploitability) { + return { + safe: false, + recommendation: 'block', + reason: 'Critical advisory with high exploitability context - do not install', + }; + } + + if (hasCritical) { + return { + safe: false, + recommendation: 'block', + reason: 'Critical security advisory - do not install', + }; + } + + if (hasHighExploitability) { + return { + safe: false, + recommendation: 'review', + reason: 'High exploitability advisory - urgent user review strongly recommended', + }; + } + + if (hasHigh) { + return { + safe: false, + recommendation: 'review', + reason: 'High severity advisory - user review strongly recommended', + }; + } + + return { + safe: false, + recommendation: 'review', + reason: 'Advisory found - review details before installing', + }; +} diff --git a/skills/clawsec-nanoclaw/lib/types.ts b/skills/clawsec-nanoclaw/lib/types.ts index d2eabc2..8fd068d 100644 --- a/skills/clawsec-nanoclaw/lib/types.ts +++ b/skills/clawsec-nanoclaw/lib/types.ts @@ -15,6 +15,8 @@ export interface Advisory { references: string[]; cvss_score?: number; nvd_url?: string; + exploitability_score?: 'high' | 'medium' | 'low' | 'unknown'; + exploitability_rationale?: string; source?: string; github_issue_url?: string; reporter?: { diff --git a/skills/clawsec-nanoclaw/mcp-tools/advisory-tools.ts b/skills/clawsec-nanoclaw/mcp-tools/advisory-tools.ts index 6058f3e..c705785 100644 --- a/skills/clawsec-nanoclaw/mcp-tools/advisory-tools.ts +++ b/skills/clawsec-nanoclaw/mcp-tools/advisory-tools.ts @@ -11,6 +11,8 @@ import fs from 'fs'; import path from 'path'; import { z } from 'zod'; +import { evaluateAdvisoryRisk, normalizeExploitabilityScore } from '../lib/risk.js'; +import { matchesAffectedSpecifier } from '../lib/advisories.js'; // These variables are provided by the host environment (ipc-mcp-stdio.ts) // when this code is integrated into the NanoClaw container agent. @@ -18,8 +20,10 @@ declare const server: { tool: (...args: any[]) => void }; declare function writeIpcFile(dir: string, data: any): void; declare const TASKS_DIR: string; declare const groupFolder: string; +const CACHE_FILE = '/workspace/project/data/clawsec-advisory-cache.json'; -// Add these helper functions to the file: +const severityOrder: Record = { critical: 0, high: 1, medium: 2, low: 3 }; +const exploitabilityOrder: Record = { high: 0, medium: 1, low: 2, unknown: 3 }; /** * Discover installed skills in a directory @@ -84,10 +88,7 @@ function findAdvisoryMatches( const matchedAffected: string[] = []; for (const affected of advisory.affected || []) { - const atIndex = affected.lastIndexOf('@'); - const affectedName = atIndex > 0 ? affected.slice(0, atIndex) : affected; - - if (affectedName === skill.name || affectedName === skill.dirName) { + if (matchesAffectedSpecifier(affected, skill.name, skill.version, skill.dirName)) { matchedAffected.push(affected); } } @@ -123,10 +124,8 @@ server.tool( } // Read cache from shared mount - const cacheFile = '/workspace/project/data/clawsec-advisory-cache.json'; - try { - const cacheData = JSON.parse(fs.readFileSync(cacheFile, 'utf8')); + const cacheData = JSON.parse(fs.readFileSync(CACHE_FILE, 'utf8')); const installRoot = args.installRoot || path.join(process.env.HOME || '~', '.claude', 'skills'); // Discover installed skills @@ -153,6 +152,8 @@ server.tool( description: m.advisory.description, action: m.advisory.action, published: m.advisory.published, + exploitability_score: normalizeExploitabilityScore(m.advisory.exploitability_score), + exploitability_rationale: m.advisory.exploitability_rationale || null, }, skill: m.skill, matchedAffected: m.matchedAffected, @@ -187,17 +188,13 @@ server.tool( skillVersion: z.string().optional().describe('Version of skill (optional, for version-specific checks)'), }, async (args) => { - const cacheFile = '/workspace/project/data/clawsec-advisory-cache.json'; - try { - const cacheData = JSON.parse(fs.readFileSync(cacheFile, 'utf8')); + const cacheData = JSON.parse(fs.readFileSync(CACHE_FILE, 'utf8')); // Find matching advisories for this skill const matchingAdvisories = cacheData.feed.advisories.filter((advisory: any) => advisory.affected.some((affected: string) => { - const atIndex = affected.lastIndexOf('@'); - const affectedName = atIndex > 0 ? affected.slice(0, atIndex) : affected; - return affectedName === args.skillName; + return matchesAffectedSpecifier(affected, args.skillName, args.skillVersion || null); }) ); @@ -215,34 +212,13 @@ server.tool( }; } - // Evaluate severity - const hasMalicious = matchingAdvisories.some((a: any) => a.type === 'malicious'); - const hasRemoveAction = matchingAdvisories.some((a: any) => a.action === 'remove'); - const hasCritical = matchingAdvisories.some((a: any) => a.severity === 'critical'); - const hasHigh = matchingAdvisories.some((a: any) => a.severity === 'high'); - - let recommendation: 'install' | 'block' | 'review'; - let reason: string; - - if (hasMalicious || hasRemoveAction) { - recommendation = 'block'; - reason = 'Malicious skill or removal recommended by ClawSec'; - } else if (hasCritical) { - recommendation = 'block'; - reason = 'Critical security advisory - do not install'; - } else if (hasHigh) { - recommendation = 'review'; - reason = 'High severity advisory - user review strongly recommended'; - } else { - recommendation = 'review'; - reason = 'Advisory found - review details before installing'; - } + const risk = evaluateAdvisoryRisk(matchingAdvisories); return { content: [{ type: 'text' as const, text: JSON.stringify({ - safe: false, // Always false when advisories exist + safe: risk.safe, advisories: matchingAdvisories.map((a: any) => ({ id: a.id, severity: a.severity, @@ -252,10 +228,13 @@ server.tool( action: a.action, published: a.published, affected: a.affected, + exploitability_score: normalizeExploitabilityScore(a.exploitability_score), + exploitability_rationale: a.exploitability_rationale || null, })), - recommendation, - reason, + recommendation: risk.recommendation, + reason: risk.reason, skillName: args.skillName, + skillVersion: args.skillVersion || null, advisoryCount: matchingAdvisories.length, }, null, 2), }], @@ -280,18 +259,18 @@ server.tool( server.tool( 'clawsec_list_advisories', - 'List ClawSec advisories with optional filtering. Use this to browse security advisories, filter by severity/type, or search for specific affected skills.', + 'List ClawSec advisories with optional filtering. Use this to browse security advisories, filter by severity/type/exploitability, or search for specific affected skills.', { severity: z.enum(['critical', 'high', 'medium', 'low']).optional().describe('Filter by severity level'), - type: z.enum(['vulnerability', 'malicious', 'deprecated']).optional().describe('Filter by advisory type'), + type: z.string().optional().describe('Filter by advisory type (for example: vulnerable_skill, malicious_skill, prompt_injection)'), + exploitabilityScore: z.enum(['high', 'medium', 'low', 'unknown']).optional() + .describe('Filter by exploitability score'), affectedSkill: z.string().optional().describe('Filter by affected skill name (partial match supported)'), limit: z.number().optional().describe('Maximum number of results (default: unlimited)'), }, async (args) => { - const cacheFile = '/workspace/project/data/clawsec-advisory-cache.json'; - try { - const cacheData = JSON.parse(fs.readFileSync(cacheFile, 'utf8')); + const cacheData = JSON.parse(fs.readFileSync(CACHE_FILE, 'utf8')); let advisories = [...cacheData.feed.advisories]; // Apply filters @@ -299,7 +278,13 @@ server.tool( advisories = advisories.filter((a: any) => a.severity === args.severity); } if (args.type) { - advisories = advisories.filter((a: any) => a.type === args.type); + const typeFilter = String(args.type).toLowerCase().trim(); + advisories = advisories.filter((a: any) => String(a.type || '').toLowerCase().trim() === typeFilter); + } + if (args.exploitabilityScore) { + advisories = advisories.filter( + (a: any) => normalizeExploitabilityScore(a.exploitability_score) === args.exploitabilityScore + ); } if (args.affectedSkill) { advisories = advisories.filter((a: any) => @@ -307,9 +292,13 @@ server.tool( ); } - // Sort by severity (critical first) and published date (newest first) - const severityOrder: Record = { critical: 0, high: 1, medium: 2, low: 3 }; + // Sort by exploitability first, then severity, then publish date (newest first). advisories.sort((a: any, b: any) => { + const exploitabilityDiff = + (exploitabilityOrder[normalizeExploitabilityScore(a.exploitability_score)] ?? 999) - + (exploitabilityOrder[normalizeExploitabilityScore(b.exploitability_score)] ?? 999); + if (exploitabilityDiff !== 0) return exploitabilityDiff; + const severityDiff = (severityOrder[a.severity] || 999) - (severityOrder[b.severity] || 999); if (severityDiff !== 0) return severityDiff; return (b.published || '').localeCompare(a.published || ''); @@ -336,6 +325,8 @@ server.tool( action: a.action, published: a.published, affected: a.affected, + exploitability_score: normalizeExploitabilityScore(a.exploitability_score), + exploitability_rationale: a.exploitability_rationale || null, })), total: cacheData.feed.advisories.length, filtered: originalCount, @@ -343,6 +334,7 @@ server.tool( filters: { severity: args.severity || null, type: args.type || null, + exploitabilityScore: args.exploitabilityScore || null, affectedSkill: args.affectedSkill || null, limit: args.limit || null, }, diff --git a/skills/clawsec-nanoclaw/skill.json b/skills/clawsec-nanoclaw/skill.json index 8546660..62d55a5 100644 --- a/skills/clawsec-nanoclaw/skill.json +++ b/skills/clawsec-nanoclaw/skill.json @@ -1,6 +1,6 @@ { "name": "clawsec-nanoclaw", - "version": "0.0.1", + "version": "0.0.2", "description": "ClawSec security suite for NanoClaw - Advisory feed monitoring, MCP tools for vulnerability checking, and Ed25519 signature verification for containerized WhatsApp bot agents", "author": "prompt-security", "license": "AGPL-3.0-or-later", @@ -27,6 +27,11 @@ "required": true, "description": "NanoClaw skill documentation" }, + { + "path": "CHANGELOG.md", + "required": true, + "description": "Version history and release notes" + }, { "path": "INSTALL.md", "required": true, @@ -62,6 +67,11 @@ "required": true, "description": "TypeScript type definitions" }, + { + "path": "lib/risk.ts", + "required": true, + "description": "Shared advisory risk evaluation logic for host and MCP tools" + }, { "path": "advisories/feed-signing-public.pem", "required": true, @@ -112,9 +122,10 @@ "capabilities": [ "Advisory feed monitoring from clawsec.prompt.security", "MCP tools for agent-initiated vulnerability scans", + "Exploitability-aware advisory prioritization for agent environments", "Pre-installation skill safety checks", "Ed25519 signature verification for advisory feeds", - "Platform-specific advisory filtering (nanoclaw vs openclaw)", + "Platform metadata preserved in advisory records for downstream filtering", "Containerized agent support with IPC communication" ], "nanoclaw": { @@ -135,7 +146,7 @@ }, "integration": { "mcp_tools_file": "container/agent-runner/src/ipc-mcp-stdio.ts", - "ipc_handlers_file": "host/ipc-handler.ts", + "ipc_handlers_file": "src/ipc.ts", "cache_location": "/workspace/project/data/clawsec-advisory-cache.json" } } diff --git a/skills/clawsec-suite/CHANGELOG.md b/skills/clawsec-suite/CHANGELOG.md index eaf0207..dd1c72d 100644 --- a/skills/clawsec-suite/CHANGELOG.md +++ b/skills/clawsec-suite/CHANGELOG.md @@ -5,6 +5,21 @@ All notable changes to the ClawSec Suite will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.1.4] - 2026-02-28 + +### Added + +- Advisory output snippets now include exploitability context in suite quick-check and heartbeat examples. + +### Changed + +- Clarified exploitability guidance to match runtime score values (`high|medium|low|unknown`). +- Prioritization guidance now emphasizes high-exploitability advisories for immediate handling. + +### Fixed + +- Kept exploitability enrichment in advisory workflows non-fatal per item so a single analysis failure does not abort feed updates. + ## [0.1.3] ### Added diff --git a/skills/clawsec-suite/HEARTBEAT.md b/skills/clawsec-suite/HEARTBEAT.md index 8f9b4d8..52b68c7 100644 --- a/skills/clawsec-suite/HEARTBEAT.md +++ b/skills/clawsec-suite/HEARTBEAT.md @@ -121,6 +121,7 @@ else while IFS= read -r id; do [ -z "$id" ] && continue jq -r --arg id "$id" '.advisories[] | select(.id == $id) | "- [\(.severity | ascii_upcase)] \(.id): \(.title)"' "$FEED_TMP" + jq -r --arg id "$id" '.advisories[] | select(.id == $id) | " Exploitability: \(.exploitability_score // "unknown" | ascii_upcase)"' "$FEED_TMP" jq -r --arg id "$id" '.advisories[] | select(.id == $id) | " Action: \(.action // "Review advisory details")"' "$FEED_TMP" done < "$NEW_IDS_FILE" else @@ -194,8 +195,18 @@ fi Heartbeat output should include: - suite version status, - advisory feed status, -- new advisory list (if any), +- new advisory list (if any) with exploitability scores, - installed skills that appear in advisory `affected` lists, - and a double-confirmation reminder before risky install/remove actions. -If your runtime sends alerts, treat `critical` and `high` advisories affecting installed skills as immediate notifications. +### Exploitability-Based Prioritization + +When alerting on advisories, prioritize by **exploitability score** in addition to severity: + +- `high` exploitability: Trivially or easily exploitable with public tooling, immediate action required +- `medium` exploitability: Exploitable with specific conditions, standard priority +- `low` exploitability: Difficult to exploit or theoretical, low priority + +**Priority Rule**: A HIGH severity + HIGH exploitability CVE should be treated more urgently than a CRITICAL severity + LOW exploitability CVE. + +If your runtime sends alerts, treat `high` exploitability advisories affecting installed skills as immediate notifications, regardless of severity rating. diff --git a/skills/clawsec-suite/SKILL.md b/skills/clawsec-suite/SKILL.md index ea4d64a..30d789e 100644 --- a/skills/clawsec-suite/SKILL.md +++ b/skills/clawsec-suite/SKILL.md @@ -1,6 +1,6 @@ --- name: clawsec-suite -version: 0.1.3 +version: 0.1.4 description: ClawSec suite manager with embedded advisory-feed monitoring, cryptographic signature verification, approval-gated malicious-skill response, and guided setup for additional security skills. homepage: https://clawsec.prompt.security clawdis: @@ -234,12 +234,25 @@ if [ -s "$NEW_IDS_FILE" ]; then while IFS= read -r id; do [ -z "$id" ] && continue jq -r --arg id "$id" '.advisories[] | select(.id == $id) | "- [\(.severity | ascii_upcase)] \(.id): \(.title)"' "$TMP/feed.json" + jq -r --arg id "$id" '.advisories[] | select(.id == $id) | " Exploitability: \(.exploitability_score // "unknown" | ascii_upcase)"' "$TMP/feed.json" done < "$NEW_IDS_FILE" else echo "FEED_OK - no new advisories" fi ``` +## Exploitability Context + +Advisories in the feed can include `exploitability_score` and `exploitability_rationale` fields to help agents prioritize real-world threats: + +- **Exploitability scores**: `high`, `medium`, `low`, or `unknown` +- **Context-aware assessment**: Considers attack vector, authentication requirements, and AI agent deployment patterns +- **Exploit availability**: Detects public exploits and weaponization status + +When processing advisories, prioritize by exploitability in addition to severity. A HIGH severity + HIGH exploitability CVE is more urgent than a CRITICAL severity + LOW exploitability CVE. + +For detailed methodology, see the [exploitability scoring documentation](../../wiki/exploitability-scoring.md). + ## Heartbeat Integration Use the suite heartbeat script as the single periodic security check entrypoint: diff --git a/skills/clawsec-suite/skill.json b/skills/clawsec-suite/skill.json index e5acca0..0c74206 100644 --- a/skills/clawsec-suite/skill.json +++ b/skills/clawsec-suite/skill.json @@ -1,6 +1,6 @@ { "name": "clawsec-suite", - "version": "0.1.3", + "version": "0.1.4", "description": "ClawSec suite manager with embedded advisory-feed monitoring, cryptographic signature verification, approval-gated malicious-skill response, and guided setup for additional security skills.", "author": "prompt-security", "license": "AGPL-3.0-or-later", diff --git a/utils/analyze_exploitability.py b/utils/analyze_exploitability.py new file mode 100755 index 0000000..67a4426 --- /dev/null +++ b/utils/analyze_exploitability.py @@ -0,0 +1,531 @@ +#!/usr/bin/env python3 +""" +Exploitability Analyzer - Analyzes CVE exploitability in OpenClaw/NanoClaw deployments + +Usage: + python utils/analyze_exploitability.py --help + echo '{"cve_id":"CVE-2026-27488","cvss_score":7.3}' | python utils/analyze_exploitability.py --json + python utils/analyze_exploitability.py --test-cases + +Example: + cat cve-data.json | python utils/analyze_exploitability.py --json +""" + +import argparse +import json +import sys +from typing import Any + + +def parse_cvss_vector(vector_string: str) -> dict[str, str]: + """ + Parse CVSS v2, v3.0, or v3.1 vector string into components. + + Args: + vector_string: CVSS vector (e.g., "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H") + + Returns: + Dictionary of CVSS metrics and values + """ + if not vector_string: + return {} + + metrics = {} + normalized = vector_string.strip() + + # Remove leading CVSS v3.x prefix if present (e.g., "CVSS:3.1/") + if normalized.startswith("CVSS:3"): + _, separator, remainder = normalized.partition("/") + normalized = remainder if separator else "" + + # Remove surrounding parentheses/whitespace used by some CVSS v2 strings. + normalized = normalized.strip().strip("()").strip() + if not normalized: + return metrics + + # Parse all vector formats with shared key/value extraction logic. + for part in normalized.split("/"): + if ":" in part: + key, value = part.split(":", 1) + metrics[key] = value + + return metrics + + +def analyze_attack_vector(cvss_metrics: dict[str, str]) -> dict[str, Any]: + """ + Analyze attack vector from CVSS metrics. + + Args: + cvss_metrics: Parsed CVSS metrics dictionary + + Returns: + Dictionary with attack vector analysis + """ + analysis = { + "is_network_accessible": False, + "requires_authentication": True, + "requires_user_interaction": True, + "complexity": "unknown" + } + + # Attack Vector (AV) + av = cvss_metrics.get("AV", "") + if av == "N": # Network + analysis["is_network_accessible"] = True + elif av == "A": # Adjacent Network + analysis["is_network_accessible"] = True + elif av in ["L", "P"]: # Local or Physical + analysis["is_network_accessible"] = False + + # Privileges Required (PR) / Authentication (AU for v2) + pr = cvss_metrics.get("PR", cvss_metrics.get("Au", "")) + if pr in ["N", "NONE"]: + analysis["requires_authentication"] = False + elif pr in ["L", "H", "SINGLE", "MULTIPLE"]: + analysis["requires_authentication"] = True + + # User Interaction (UI) + ui = cvss_metrics.get("UI", "") + if ui == "N": # None + analysis["requires_user_interaction"] = False + elif ui == "R": # Required + analysis["requires_user_interaction"] = True + + # Attack Complexity (AC) + ac = cvss_metrics.get("AC", "") + if ac == "L": + analysis["complexity"] = "low" + elif ac in ["M", "H"]: + analysis["complexity"] = "high" + + return analysis + + +def detect_exploit_availability(references: list[str]) -> dict[str, Any]: + """ + Detect if exploits are publicly available based on reference URLs. + + Args: + references: List of reference URLs + + Returns: + Dictionary with exploit_available (bool) and exploit_sources (list) + """ + exploit_indicators = [ + "exploit-db.com", + "exploit-database", + "exploitdb", + "packetstormsecurity.com", + "packetstorm", + "github.com/exploit", + "github.com/poc", + "github.com/proof-of-concept", + "metasploit", + "exploit/", + "/exploit", + "/poc", + "/proof-of-concept", + "exploitability", + "exploit-code", + ] + + exploit_sources = [] + for ref in references: + ref_lower = ref.lower() + for indicator in exploit_indicators: + if indicator in ref_lower: + exploit_sources.append(ref) + break + + return { + "exploit_available": len(exploit_sources) > 0, + "exploit_sources": exploit_sources + } + + +def analyze_exploitability(cve_data: dict[str, Any], check_exploits: bool = False) -> dict[str, Any]: + """ + Analyze CVE exploitability for OpenClaw/NanoClaw deployments. + + Args: + cve_data: Dictionary containing CVE information with keys: + - cve_id: CVE identifier + - cvss_score: CVSS base score (float) + - cvss_vector: CVSS vector string (optional) + - type: Vulnerability type + - description: CVE description text + - references: List of reference URLs (optional) + check_exploits: Whether to check references for exploit availability + + Returns: + Dictionary with exploitability_score (high/medium/low/unknown) and rationale + """ + cve_id = cve_data.get("cve_id", "unknown") + cvss_score = cve_data.get("cvss_score", 0.0) + cvss_vector = cve_data.get("cvss_vector", "") + vuln_type = cve_data.get("type", "") + description = cve_data.get("description", "") + references = cve_data.get("references", []) + + # Parse CVSS vector if available + cvss_metrics = parse_cvss_vector(cvss_vector) + attack_analysis = analyze_attack_vector(cvss_metrics) + + # Initial scoring based on CVSS + score = "unknown" + rationale_parts = [] + + # CVSS-based baseline + if cvss_score >= 9.0: + score = "high" + rationale_parts.append(f"Critical CVSS score ({cvss_score})") + elif cvss_score >= 7.0: + score = "high" + rationale_parts.append(f"High CVSS score ({cvss_score})") + elif cvss_score >= 4.0: + score = "medium" + rationale_parts.append(f"Medium CVSS score ({cvss_score})") + elif cvss_score > 0: + score = "low" + rationale_parts.append(f"Low CVSS score ({cvss_score})") + else: + score = "unknown" + rationale_parts.append("No CVSS score available") + + # Adjust based on attack vector analysis + if attack_analysis["is_network_accessible"]: + if not attack_analysis["requires_authentication"] and not attack_analysis["requires_user_interaction"]: + # Network accessible, no auth, no user interaction = highly exploitable + if score == "medium": + score = "high" + rationale_parts.append("remotely exploitable without authentication") + else: + rationale_parts.append("network accessible") + else: + # Local-only vulnerabilities are less critical in agent deployments + if score == "high": + score = "medium" + rationale_parts.append("requires local access") + + # OpenClaw/NanoClaw deployment context - adjust based on vulnerability type + vuln_type_lower = vuln_type.lower() + description_lower = description.lower() + + # High-risk vulnerability types in AI agent deployments + if any(keyword in vuln_type_lower or keyword in description_lower for keyword in [ + "ssrf", "server_side_request_forgery", "server-side request forgery" + ]): + # SSRF is critical for agents that make external API calls + if score != "high" and cvss_score >= 6.0: + score = "high" + rationale_parts.append("SSRF affects agents making external requests") + + elif any(keyword in vuln_type_lower or keyword in description_lower for keyword in [ + "path_traversal", "path traversal", "directory traversal", "file_inclusion" + ]): + # Path traversal is critical for agents with file system access + if score != "high" and cvss_score >= 6.0: + score = "high" + rationale_parts.append("path traversal affects agents with file access") + + elif any(keyword in vuln_type_lower or keyword in description_lower for keyword in [ + "rce", "remote_code_execution", "remote code execution", "code_injection", + "command_injection", "command injection", "arbitrary code" + ]): + # RCE is always critical regardless of other factors + score = "high" + rationale_parts.append("RCE is critical in agent deployments") + + elif any(keyword in vuln_type_lower or keyword in description_lower for keyword in [ + "prototype_pollution", "prototype pollution" + ]): + # Prototype pollution in Node.js agents can lead to RCE + if score == "low": + score = "medium" + rationale_parts.append("prototype pollution can escalate in Node.js agents") + + elif any(keyword in vuln_type_lower or keyword in description_lower for keyword in [ + "xss", "cross_site_scripting", "cross-site scripting", "reflected xss", "stored xss" + ]): + # XSS is lower risk in headless agent deployments (no browser rendering) + if score == "high" and not attack_analysis["is_network_accessible"]: + score = "medium" + rationale_parts.append("XSS has limited impact in headless agents") + + elif any(keyword in vuln_type_lower or keyword in description_lower for keyword in [ + "sql_injection", "sql injection", "nosql injection" + ]): + # SQL injection depends on whether agent uses databases + if attack_analysis["is_network_accessible"] and not attack_analysis["requires_authentication"]: + if score == "medium": + score = "high" + rationale_parts.append("injection affects agents with database access") + + # Check for exploit availability if requested + exploit_info = {"exploit_available": False, "exploit_sources": []} + if check_exploits and references: + exploit_info = detect_exploit_availability(references) + if exploit_info["exploit_available"]: + # Elevate score if public exploits exist + if score == "low": + score = "medium" + elif score == "medium": + score = "high" + elif score == "unknown" and cvss_score > 0: + # If we have some CVSS score but it was unknown, upgrade to at least medium + score = "medium" + + exploit_count = len(exploit_info["exploit_sources"]) + source_suffix = "s" if exploit_count > 1 else "" + rationale_parts.append( + f"public exploit available ({exploit_count} source{source_suffix})" + ) + + # Build rationale string + rationale = "; ".join(rationale_parts[:5]) # Limit to first 5 parts for context + + result = { + "cve_id": cve_id, + "exploitability_score": score, + "exploitability_rationale": rationale, + "attack_vector_analysis": attack_analysis + } + + # Include exploit info if check_exploits was enabled + if check_exploits: + result["exploit_detection"] = exploit_info + + return result + + +def run_test_cases(): + """ + Run comprehensive test cases for attack vector analysis. + Tests CVSS vector parsing and attack vector analysis logic. + """ + test_cases = [ + { + "name": "CVSS 3.1 - Network accessible, no auth, no UI (critical)", + "cvss_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + "expected": { + "is_network_accessible": True, + "requires_authentication": False, + "requires_user_interaction": False, + "complexity": "low" + } + }, + { + "name": "CVSS 3.1 - Network accessible, requires auth", + "cvss_vector": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + "expected": { + "is_network_accessible": True, + "requires_authentication": True, + "requires_user_interaction": False, + "complexity": "low" + } + }, + { + "name": "CVSS 3.1 - Network accessible, requires UI", + "cvss_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H", + "expected": { + "is_network_accessible": True, + "requires_authentication": False, + "requires_user_interaction": True, + "complexity": "low" + } + }, + { + "name": "CVSS 3.1 - Local access required", + "cvss_vector": "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + "expected": { + "is_network_accessible": False, + "requires_authentication": False, + "requires_user_interaction": False, + "complexity": "low" + } + }, + { + "name": "CVSS 3.1 - Adjacent network, high auth", + "cvss_vector": "CVSS:3.1/AV:A/AC:H/PR:H/UI:R/S:U/C:L/I:L/A:L", + "expected": { + "is_network_accessible": True, + "requires_authentication": True, + "requires_user_interaction": True, + "complexity": "high" + } + }, + { + "name": "CVSS 3.0 - Physical access required", + "cvss_vector": "CVSS:3.0/AV:P/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + "expected": { + "is_network_accessible": False, + "requires_authentication": False, + "requires_user_interaction": False, + "complexity": "low" + } + }, + { + "name": "CVSS v2 - Network, no auth required", + "cvss_vector": "(AV:N/AC:L/Au:N/C:C/I:C/A:C)", + "expected": { + "is_network_accessible": True, + "requires_authentication": False, + "requires_user_interaction": True, # v2 doesn't have UI, defaults to True + "complexity": "low" + } + }, + { + "name": "CVSS v2 - Network, single auth", + "cvss_vector": "AV:N/AC:M/Au:SINGLE/C:P/I:P/A:P", + "expected": { + "is_network_accessible": True, + "requires_authentication": True, + "requires_user_interaction": True, # v2 doesn't have UI, defaults to True + "complexity": "high" + } + }, + { + "name": "CVSS v2 - Local access, multiple auth", + "cvss_vector": "(AV:L/AC:L/Au:MULTIPLE/C:C/I:C/A:C)", + "expected": { + "is_network_accessible": False, + "requires_authentication": True, + "requires_user_interaction": True, # v2 doesn't have UI, defaults to True + "complexity": "low" + } + }, + { + "name": "Empty CVSS vector", + "cvss_vector": "", + "expected": { + "is_network_accessible": False, + "requires_authentication": True, + "requires_user_interaction": True, + "complexity": "unknown" + } + } + ] + + print("Running attack vector analysis test cases...") + print("=" * 70) + + passed = 0 + failed = 0 + + for i, test in enumerate(test_cases, 1): + print(f"\nTest {i}/{len(test_cases)}: {test['name']}") + print(f" CVSS Vector: {test['cvss_vector']}") + + # Parse CVSS vector and analyze attack vector + cvss_metrics = parse_cvss_vector(test['cvss_vector']) + result = analyze_attack_vector(cvss_metrics) + + # Compare with expected results + test_passed = True + for key, expected_value in test['expected'].items(): + actual_value = result.get(key) + if actual_value != expected_value: + print(f" ❌ FAILED: {key}") + print(f" Expected: {expected_value}") + print(f" Got: {actual_value}") + test_passed = False + failed += 1 + break + + if test_passed: + print(" ✓ PASSED") + passed += 1 + else: + # Show full result for debugging + print(f" Full result: {json.dumps(result, indent=6)}") + + print("\n" + "=" * 70) + print(f"Test Results: {passed} passed, {failed} failed out of {len(test_cases)} total") + + if failed > 0: + print("\n❌ Some tests failed!") + sys.exit(1) + else: + print("\n✅ All tests passed!") + sys.exit(0) + + +def main(): + parser = argparse.ArgumentParser( + description="Analyze CVE exploitability for OpenClaw/NanoClaw deployments", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +Examples: + # Analyze from JSON stdin + echo '{"cve_id":"CVE-2026-27488","cvss_score":7.3,"type":"ssrf"}' | python utils/analyze_exploitability.py --json + + # Analyze with CVSS vector + echo '{"cve_id":"CVE-2026-1234","cvss_score":9.8,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"}' \ + | python utils/analyze_exploitability.py --json + + # Run test cases + python utils/analyze_exploitability.py --test-cases + + # Parse CVSS vector only + python utils/analyze_exploitability.py --parse-vector "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H" +""" + ) + + parser.add_argument( + "--json", + action="store_true", + help="Read CVE data from stdin as JSON and output analysis" + ) + + parser.add_argument( + "--parse-vector", + type=str, + metavar="VECTOR", + help="Parse and display CVSS vector string" + ) + + parser.add_argument( + "--test-cases", + action="store_true", + help="Run built-in test cases to verify analyzer logic" + ) + + parser.add_argument( + "--check-exploits", + action="store_true", + help="Check references for publicly available exploits and adjust score accordingly" + ) + + args = parser.parse_args() + + # Handle --parse-vector + if args.parse_vector: + metrics = parse_cvss_vector(args.parse_vector) + print(json.dumps(metrics, indent=2)) + sys.exit(0) + + # Handle --test-cases + if args.test_cases: + run_test_cases() + sys.exit(0) + + # Handle --json (stdin) + if args.json: + try: + cve_data = json.load(sys.stdin) + except json.JSONDecodeError as e: + print(f"Error: Invalid JSON input: {e}", file=sys.stderr) + sys.exit(1) + + result = analyze_exploitability(cve_data, check_exploits=args.check_exploits) + print(json.dumps(result, indent=2)) + sys.exit(0) + + # No action specified - show help + parser.print_help() + sys.exit(0) + + +if __name__ == "__main__": + main() diff --git a/wiki/exploitability-scoring.md b/wiki/exploitability-scoring.md new file mode 100644 index 0000000..165a244 --- /dev/null +++ b/wiki/exploitability-scoring.md @@ -0,0 +1,420 @@ +# Exploitability Scoring Methodology + +## Overview + +ClawSec's exploitability scoring system provides context-aware vulnerability assessment specifically designed for AI agent deployments (OpenClaw/NanoClaw). Unlike generic CVSS scores that treat all environments equally, our scoring considers the unique attack surface and usage patterns of AI agents to reduce alert fatigue and prioritize actionable threats. + +## Scoring Levels + +| Level | Severity | Meaning | +|---|---|---| +| `high` | Critical/High | Exploitable in typical agent deployments, immediate attention required | +| `medium` | Medium | May be exploitable depending on configuration, warrants investigation | +| `low` | Low | Limited exploitability in agent context, low priority | +| `unknown` | Unknown | Insufficient data to assess exploitability | + +## Scoring Factors + +### 1. CVSS Base Score (Baseline) + +The analysis starts with the CVSS base score as a foundation: + +- **CVSS ≥ 9.0**: Critical severity → initial score `high` +- **CVSS 7.0-8.9**: High severity → initial score `high` +- **CVSS 4.0-6.9**: Medium severity → initial score `medium` +- **CVSS 1.0-3.9**: Low severity → initial score `low` +- **No CVSS**: → initial score `unknown` + +### 2. Attack Vector Analysis (CVSS Metrics) + +The analyzer parses CVSS v2, v3.0, and v3.1 vectors to assess: + +#### Network Accessibility +- **AV:N** (Network): Remotely exploitable over network +- **AV:A** (Adjacent): Requires local network access +- **AV:L** (Local): Requires local system access +- **AV:P** (Physical): Requires physical access + +**Impact on agents**: Network-accessible vulnerabilities are elevated because agents typically run as network services or make external API calls. + +#### Authentication Requirements +- **PR:N / Au:NONE**: No authentication required → elevates score +- **PR:L / Au:SINGLE**: Low privileges required +- **PR:H / Au:MULTIPLE**: High privileges required → reduces score + +**Impact on agents**: Unauthenticated exploits are critical for publicly exposed agent APIs. + +#### User Interaction +- **UI:N**: No user interaction required → elevates score +- **UI:R**: Requires user interaction → reduces score + +**Impact on agents**: Agents often operate autonomously, so vulnerabilities requiring user interaction are less critical. + +#### Attack Complexity +- **AC:L**: Low complexity → elevates score +- **AC:M / AC:H**: Medium/High complexity → neutral or reduces score + +**Impact on agents**: Low-complexity exploits are more likely to be automated and used in mass attacks. + +### 3. Vulnerability Type (Deployment Context) + +ClawSec adjusts scores based on how vulnerability types affect AI agent deployments: + +#### High-Risk Types in Agent Context + +**Remote Code Execution (RCE)** +``` +Score: Always HIGH +Rationale: RCE is critical in agent deployments +``` +AI agents execute arbitrary code as part of their function. RCE vulnerabilities allow attackers to hijack agent execution flow, exfiltrate credentials, or pivot to other systems. + +**Server-Side Request Forgery (SSRF)** +``` +Score: Elevated to HIGH if CVSS ≥ 6.0 +Rationale: SSRF affects agents making external requests +``` +Agents frequently call external APIs, access internal services, and fetch remote resources. SSRF allows attackers to: +- Access internal cloud metadata services (AWS IMDSv1, GCP metadata) +- Pivot to internal networks +- Exfiltrate data through DNS tunneling + +**Path Traversal / Directory Traversal** +``` +Score: Elevated to HIGH if CVSS ≥ 6.0 +Rationale: Path traversal affects agents with file access +``` +Agents read files, execute scripts, and manage codebases. Path traversal enables: +- Reading sensitive configuration files (.env, credentials) +- Accessing SSH keys, API tokens +- Overwriting critical system files + +**Command Injection** +``` +Score: Always HIGH +Rationale: Command injection is critical in agent deployments +``` +Similar to RCE, agents often execute shell commands to interact with systems. Command injection allows full system compromise. + +#### Medium-Risk Types + +**Prototype Pollution (Node.js)** +``` +Score: Elevated from LOW to MEDIUM +Rationale: Prototype pollution can escalate in Node.js agents +``` +Many agent frameworks run on Node.js. Prototype pollution can lead to: +- Bypass of authentication checks +- Privilege escalation +- Denial of service + +**SQL Injection / NoSQL Injection** +``` +Score: Elevated to HIGH if network-accessible and unauthenticated +Rationale: Injection affects agents with database access +``` +Agents that store conversation history, user data, or tool results in databases are vulnerable to injection attacks. + +#### Lower-Risk Types + +**Cross-Site Scripting (XSS)** +``` +Score: Reduced to MEDIUM if not network-accessible +Rationale: XSS has limited impact in headless agents +``` +Agents typically don't render HTML in browsers, reducing XSS impact. However, XSS in agent management UIs or chat interfaces remains a concern. + +### 4. Exploit Availability + +When `--check-exploits` is enabled, the analyzer checks reference URLs for public exploits: + +**Exploit Indicators:** +- exploit-db.com / exploit-database.com +- packetstormsecurity.com +- github.com/exploit, github.com/poc +- metasploit framework modules +- URLs containing "/exploit", "/poc", "/proof-of-concept" + +**Score Elevation:** +- `low` → `medium` (exploit available) +- `medium` → `high` (exploit available) +- `unknown` → `medium` (exploit available + CVSS > 0) + +**Rationale**: Public exploits lower the skill barrier for attackers and increase the likelihood of automated exploitation. + +## Scoring Algorithm + +The analyzer follows this decision tree: + +``` +1. Parse CVSS score → set baseline (high/medium/low/unknown) +2. Parse CVSS vector → analyze attack characteristics +3. Adjust for attack vector: + - Network-accessible + no auth + no UI → elevate to HIGH + - Local-only access → reduce HIGH to MEDIUM +4. Adjust for vulnerability type: + - Check against agent-specific risk categories + - Elevate or reduce score based on deployment context +5. Check for public exploits (if enabled): + - Elevate score if exploits detected +6. Generate rationale explaining the final score +``` + +## Examples + +### Example 1: Critical RCE (High Exploitability) + +```json +{ + "cve_id": "CVE-2024-12345", + "cvss_score": 9.8, + "cvss_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + "type": "remote_code_execution", + "description": "Unauthenticated RCE in Express.js framework" +} +``` + +**Analysis Output:** +```json +{ + "exploitability_score": "high", + "exploitability_rationale": "Critical CVSS score (9.8); remotely exploitable without authentication; RCE is critical in agent deployments" +} +``` + +**Why HIGH**: Critical CVSS + network accessible + no auth + RCE type. + +### Example 2: SSRF in Agent API (High Exploitability) + +```json +{ + "cve_id": "CVE-2024-23456", + "cvss_score": 7.3, + "cvss_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L", + "type": "server_side_request_forgery", + "description": "SSRF in webhook handler allows internal network access" +} +``` + +**Analysis Output:** +```json +{ + "exploitability_score": "high", + "exploitability_rationale": "High CVSS score (7.3); remotely exploitable without authentication; SSRF affects agents making external requests" +} +``` + +**Why HIGH**: SSRF is critical for agents that make API calls (most do). Network-accessible without authentication elevates risk. + +### Example 3: Path Traversal with Public Exploit (High Exploitability) + +```json +{ + "cve_id": "CVE-2024-34567", + "cvss_score": 6.5, + "cvss_vector": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N", + "type": "path_traversal", + "references": [ + "https://exploit-db.com/exploits/51234", + "https://nvd.nist.gov/vuln/detail/CVE-2024-34567" + ] +} +``` + +**Analysis Output (with --check-exploits):** +```json +{ + "exploitability_score": "high", + "exploitability_rationale": "Medium CVSS score (6.5); network accessible; path traversal affects agents with file access; public exploit available (1 source)" +} +``` + +**Why HIGH**: Path traversal + agent file access + public exploit elevates medium CVSS to high exploitability. + +### Example 4: XSS in Agent UI (Medium Exploitability) + +```json +{ + "cve_id": "CVE-2024-45678", + "cvss_score": 7.1, + "cvss_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:L", + "type": "cross_site_scripting", + "description": "Stored XSS in agent management dashboard" +} +``` + +**Analysis Output:** +```json +{ + "exploitability_score": "medium", + "exploitability_rationale": "High CVSS score (7.1); network accessible; XSS has limited impact in headless agents" +} +``` + +**Why MEDIUM**: Despite high CVSS, XSS is less critical in agent deployments (headless operation). Requires user interaction. + +### Example 5: Local Privilege Escalation (Medium Exploitability) + +```json +{ + "cve_id": "CVE-2024-56789", + "cvss_score": 8.8, + "cvss_vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H", + "type": "privilege_escalation", + "description": "Local privilege escalation via symbolic link attack" +} +``` + +**Analysis Output:** +```json +{ + "exploitability_score": "medium", + "exploitability_rationale": "High CVSS score (8.8); requires local access" +} +``` + +**Why MEDIUM**: Despite high CVSS, requires local access. Agents typically run in containerized/sandboxed environments where local escalation has limited impact. + +### Example 6: Prototype Pollution with Exploit (High Exploitability) + +```json +{ + "cve_id": "CVE-2024-67890", + "cvss_score": 5.3, + "cvss_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N", + "type": "prototype_pollution", + "description": "Prototype pollution in lodash merge function", + "references": [ + "https://github.com/exploit/prototype-pollution-poc", + "https://snyk.io/vuln/SNYK-JS-LODASH-1234567" + ] +} +``` + +**Analysis Output (with --check-exploits):** +```json +{ + "exploitability_score": "high", + "exploitability_rationale": "Medium CVSS score (5.3); remotely exploitable without authentication; prototype pollution can escalate in Node.js agents; public exploit available (1 source)" +} +``` + +**Why HIGH**: Prototype pollution in Node.js agents + public exploit + network-accessible without auth = high risk despite moderate CVSS. + +## Usage in ClawSec Workflows + +### Automated Scoring (NVD Feed) + +The `poll-nvd-cves.yml` workflow automatically scores new CVEs: + +```bash +# Workflow step +python utils/analyze_exploitability.py --json --check-exploits < cve-data.json +``` + +Advisories in `advisories/feed.json` can include: +```json +{ + "id": "CVE-2024-12345", + "severity": "high", + "exploitability_score": "high", + "exploitability_rationale": "Critical CVSS score (9.8); remotely exploitable without authentication; RCE is critical in agent deployments", + "attack_vector_analysis": { + "is_network_accessible": true, + "requires_authentication": false, + "requires_user_interaction": false, + "complexity": "low" + } +} +``` + +### Manual Analysis + +Security researchers can analyze CVEs manually: + +```bash +# Basic analysis +echo '{"cve_id":"CVE-2024-12345","cvss_score":7.3,"type":"ssrf"}' | \ + python utils/analyze_exploitability.py --json + +# With exploit detection +echo '{"cve_id":"CVE-2024-12345","cvss_score":7.3,"references":["https://exploit-db.com/exploits/51234"]}' | \ + python utils/analyze_exploitability.py --json --check-exploits +``` + +### Filtering by Exploitability + +Users can filter advisories by exploitability score: + +```bash +# Get only high-exploitability advisories +curl -s https://clawsec.prompt.security/feed.json | \ + jq '.advisories[] | select(.exploitability_score == "high")' + +# Prioritize by exploitability and severity +curl -s https://clawsec.prompt.security/feed.json | \ + jq '[.advisories[] | select(.exploitability_score == "high" and .severity == "critical")] | sort_by(.cvss_score) | reverse' +``` + +## Backfilling Existing Advisories (Historical Maintenance) + +`scripts/backfill-exploitability.sh` is retained as a historical maintainer utility for one-off repository maintenance. +It is not the primary path for normal advisory generation. + +Preferred paths: + +1. CI canonical path: run the NVD workflow with init/reset to rebuild advisories from NVD and sign artifacts in pipeline. +2. Local developer path: run `./scripts/populate-local-feed.sh --force` to repopulate local feeds with exploitability context. + +Use backfill only when explicitly repairing legacy feed content that already exists in-repo. + +## Community Contributions + +Community members can submit exploitability assessments: + +1. **Report via GitHub Issue**: Use the advisory template to report CVEs with exploitability context +2. **Automated Analysis**: The `community-advisory.yml` workflow automatically scores community-reported CVEs +3. **Manual Review**: Maintainers review and approve exploitability assessments +4. **Feed Update**: Approved advisories are added to the feed with exploitability scores + +## Limitations and Future Work + +### Current Limitations + +1. **Static Analysis**: Scoring is based on CVE metadata, not dynamic runtime analysis +2. **No Version Detection**: Doesn't check if specific versions are vulnerable +3. **Binary Classification**: Doesn't consider partial mitigations or defense-in-depth +4. **Limited Context**: Doesn't know exact agent configuration or deployed tools + +### Future Enhancements + +1. **EPSS Integration**: Incorporate EPSS (Exploit Prediction Scoring System) probability scores +2. **KEV Matching**: Cross-reference with CISA KEV (Known Exploited Vulnerabilities) catalog +3. **Agent Profiling**: Consider deployed agent capabilities and exposed APIs +4. **Mitigation Detection**: Check for WAF rules, sandboxing, or other compensating controls +5. **ML-Based Scoring**: Use machine learning to predict exploitability based on historical data + +## References + +- **CVSS v3.1 Specification**: [https://www.first.org/cvss/v3.1/specification-document](https://www.first.org/cvss/v3.1/specification-document) +- **CVSS v2 Guide**: [https://www.first.org/cvss/v2/guide](https://www.first.org/cvss/v2/guide) +- **EPSS**: [https://www.first.org/epss/](https://www.first.org/epss/) +- **CISA KEV**: [https://www.cisa.gov/known-exploited-vulnerabilities-catalog](https://www.cisa.gov/known-exploited-vulnerabilities-catalog) +- **NVD API**: [https://nvd.nist.gov/developers/vulnerabilities](https://nvd.nist.gov/developers/vulnerabilities) + +## Contributing + +To improve the exploitability scoring methodology: + +1. **Submit Test Cases**: Add test cases to `utils/analyze_exploitability.py` +2. **Report False Positives/Negatives**: Open GitHub issues with CVE examples +3. **Propose Scoring Adjustments**: Submit PRs with rationale and examples +4. **Share Agent Context**: Contribute agent-specific vulnerability patterns + +See [CONTRIBUTING.md](../CONTRIBUTING.md) for detailed contribution guidelines. + +--- + +**Maintained by**: [Prompt Security](https://prompt.security) +**License**: AGPL-3.0-or-later +**Last Updated**: 2026-03-01 From 3c336021d7cbdb87522be7ec68c2710e9e372fc6 Mon Sep 17 00:00:00 2001 From: davida-ps Date: Sun, 1 Mar 2026 18:54:32 +0200 Subject: [PATCH 2/6] fix(ci): use valid setup-python pin in advisory workflows (#92) --- .github/workflows/community-advisory.yml | 2 +- .github/workflows/poll-nvd-cves.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/community-advisory.yml b/.github/workflows/community-advisory.yml index d6bf077..a88f38e 100644 --- a/.github/workflows/community-advisory.yml +++ b/.github/workflows/community-advisory.yml @@ -195,7 +195,7 @@ jobs: - name: Set up Python for exploitability analysis if: steps.parse.outputs.already_exists != 'true' - uses: actions/setup-python@8d2f52b6169cf4c0f64d2e9f6f8f6d6a6e1c90f7 # v5.4.1 + uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0 with: python-version: '3.10' diff --git a/.github/workflows/poll-nvd-cves.yml b/.github/workflows/poll-nvd-cves.yml index 3ef6326..d01f213 100644 --- a/.github/workflows/poll-nvd-cves.yml +++ b/.github/workflows/poll-nvd-cves.yml @@ -588,7 +588,7 @@ jobs: - name: Set up Python for exploitability analysis if: steps.transform.outputs.new_count != '0' - uses: actions/setup-python@8d2f52b6169cf4c0f64d2e9f6f8f6d6a6e1c90f7 # v5.4.1 + uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0 with: python-version: '3.10' From 8ad38dfdc6eb85179e19bb92cc9753a8a10eb23b Mon Sep 17 00:00:00 2001 From: davida-ps Date: Sun, 1 Mar 2026 20:00:42 +0200 Subject: [PATCH 3/6] feat(ci): add full-scan rebuild mode to NVD polling (#96) --- .github/workflows/poll-nvd-cves.yml | 146 +++++++++++++++++++++++----- 1 file changed, 119 insertions(+), 27 deletions(-) diff --git a/.github/workflows/poll-nvd-cves.yml b/.github/workflows/poll-nvd-cves.yml index d01f213..6530ff9 100644 --- a/.github/workflows/poll-nvd-cves.yml +++ b/.github/workflows/poll-nvd-cves.yml @@ -7,7 +7,7 @@ on: workflow_dispatch: inputs: force_full_scan: - description: 'Ignore last poll date and scan all CVEs' + description: 'Ignore feed state and rebuild CVE advisories from full NVD history' required: false default: 'false' type: boolean @@ -86,6 +86,7 @@ jobs: run: | set -euo pipefail mkdir -p tmp + FORCE_FULL_SCAN="${{ inputs.force_full_scan }}" START_DATE="${{ steps.dates.outputs.start_date }}" END_DATE="${{ steps.dates.outputs.end_date }}" @@ -101,35 +102,93 @@ jobs: # Fetch for each keyword for KEYWORD in $KEYWORDS; do echo "Fetching keyword: $KEYWORD" - - URL="https://services.nvd.nist.gov/rest/json/cves/2.0?keywordSearch=${KEYWORD}&lastModStartDate=${START_ENC}&lastModEndDate=${END_ENC}" - echo "URL: $URL" - - # Fetch with retry logic + keyword_ok=false last_http_code="" - for i in 1 2 3; do - HTTP_CODE=$(curl -sS -w "%{http_code}" -o "tmp/nvd_${KEYWORD}.json" "$URL" || true) - if [ -z "$HTTP_CODE" ]; then - HTTP_CODE="000" - fi - last_http_code="$HTTP_CODE" - if [ "$HTTP_CODE" = "200" ]; then - if jq -e . "tmp/nvd_${KEYWORD}.json" >/dev/null 2>&1; then - echo "Success for $KEYWORD" + + if [ "$FORCE_FULL_SCAN" = "true" ]; then + echo "Full scan mode enabled: paginating complete NVD history for keyword '$KEYWORD'" + echo '{"vulnerabilities":[]}' > "tmp/nvd_${KEYWORD}.json" + START_INDEX=0 + RESULTS_PER_PAGE=2000 + + while true; do + URL="https://services.nvd.nist.gov/rest/json/cves/2.0?keywordSearch=${KEYWORD}&startIndex=${START_INDEX}&resultsPerPage=${RESULTS_PER_PAGE}" + PAGE_FILE="tmp/nvd_${KEYWORD}_${START_INDEX}.json" + echo "URL: $URL" + + page_ok=false + for i in 1 2 3; do + HTTP_CODE=$(curl -sS -w "%{http_code}" -o "$PAGE_FILE" "$URL" || true) + if [ -z "$HTTP_CODE" ]; then + HTTP_CODE="000" + fi + last_http_code="$HTTP_CODE" + if [ "$HTTP_CODE" = "200" ]; then + if jq -e . "$PAGE_FILE" >/dev/null 2>&1; then + page_ok=true + break + fi + echo "Invalid JSON for $KEYWORD page $START_INDEX, retry $i..." + sleep 5 + elif [ "$HTTP_CODE" = "403" ] || [ "$HTTP_CODE" = "429" ]; then + echo "Rate limited, waiting 30s before retry $i..." + sleep 30 + else + echo "HTTP $HTTP_CODE for $KEYWORD page $START_INDEX, retry $i..." + sleep 5 + fi + done + + if [ "$page_ok" != "true" ]; then + break + fi + + jq -s '.[0].vulnerabilities += .[1].vulnerabilities | .[0]' \ + "tmp/nvd_${KEYWORD}.json" "$PAGE_FILE" > "tmp/nvd_${KEYWORD}_merged.json" + mv "tmp/nvd_${KEYWORD}_merged.json" "tmp/nvd_${KEYWORD}.json" + + PAGE_COUNT=$(jq '.vulnerabilities | length' "$PAGE_FILE") + TOTAL_RESULTS=$(jq '.totalResults // 0' "$PAGE_FILE") + echo "Fetched $PAGE_COUNT results at startIndex=$START_INDEX (totalResults=$TOTAL_RESULTS)" + + START_INDEX=$((START_INDEX + RESULTS_PER_PAGE)) + if [ "$START_INDEX" -ge "$TOTAL_RESULTS" ] || [ "$PAGE_COUNT" -eq 0 ]; then keyword_ok=true break fi - echo "Invalid JSON for $KEYWORD, retry $i..." - sleep 5 - elif [ "$HTTP_CODE" = "403" ] || [ "$HTTP_CODE" = "429" ]; then - echo "Rate limited, waiting 30s before retry $i..." - sleep 30 - else - echo "HTTP $HTTP_CODE for $KEYWORD, retry $i..." - sleep 5 - fi - done + + # NVD recommends 6 second delay between requests + sleep 6 + done + else + URL="https://services.nvd.nist.gov/rest/json/cves/2.0?keywordSearch=${KEYWORD}&lastModStartDate=${START_ENC}&lastModEndDate=${END_ENC}" + echo "URL: $URL" + + # Fetch with retry logic + for i in 1 2 3; do + HTTP_CODE=$(curl -sS -w "%{http_code}" -o "tmp/nvd_${KEYWORD}.json" "$URL" || true) + if [ -z "$HTTP_CODE" ]; then + HTTP_CODE="000" + fi + last_http_code="$HTTP_CODE" + if [ "$HTTP_CODE" = "200" ]; then + if jq -e . "tmp/nvd_${KEYWORD}.json" >/dev/null 2>&1; then + echo "Success for $KEYWORD" + keyword_ok=true + break + fi + echo "Invalid JSON for $KEYWORD, retry $i..." + sleep 5 + elif [ "$HTTP_CODE" = "403" ] || [ "$HTTP_CODE" = "429" ]; then + echo "Rate limited, waiting 30s before retry $i..." + sleep 30 + else + echo "HTTP $HTTP_CODE for $KEYWORD, retry $i..." + sleep 5 + fi + done + fi if [ "$keyword_ok" != "true" ]; then echo "::error::Failed to fetch valid NVD response for keyword '$KEYWORD' (last HTTP code: ${last_http_code:-unknown})." @@ -212,6 +271,14 @@ jobs: - name: Check for updates to existing advisories id: updates run: | + if [ "${{ inputs.force_full_scan }}" = "true" ]; then + echo "Full scan mode enabled: skipping delta update detection." + echo '[]' > tmp/updated_advisories.json + echo "Advisories to update: 0" + echo "update_count=0" >> $GITHUB_OUTPUT + exit 0 + fi + # Compare existing CVE advisories against NVD data for changes # Only check advisories that start with "CVE-" (NVD-sourced) @@ -424,7 +491,12 @@ jobs: id: transform run: | # Read existing IDs into a jq-friendly format - EXISTING_IDS=$(cat tmp/existing_ids.txt | jq -R -s 'split("\n") | map(select(length > 0))') + if [ "${{ inputs.force_full_scan }}" = "true" ]; then + echo "Full scan mode enabled: rebuilding CVE advisories from scratch." + EXISTING_IDS='[]' + else + EXISTING_IDS=$(cat tmp/existing_ids.txt | jq -R -s 'split("\n") | map(select(length > 0))') + fi # Transform NVD CVEs to our advisory format jq --argjson existing "$EXISTING_IDS" ' @@ -686,8 +758,20 @@ jobs: if: steps.transform.outputs.new_count != '0' || steps.updates.outputs.update_count != '0' run: | NOW=$(date -u +%Y-%m-%dT%H:%M:%SZ) + FORCE_FULL_SCAN="${{ inputs.force_full_scan }}" - if [ -f "$FEED_PATH" ]; then + if [ -f "$FEED_PATH" ] && [ "$FORCE_FULL_SCAN" = "true" ]; then + # Full scan mode: replace all CVE advisories with rebuilt set and keep non-CVE entries. + jq --argjson rebuilt "$(cat tmp/new_advisories.json)" --arg now "$NOW" ' + .updated = $now | + .advisories = ( + ((.advisories // []) | map(select((.id // "") | startswith("CVE-") | not))) + + $rebuilt + | sort_by(.published) + | reverse + ) + ' "$FEED_PATH" > tmp/updated_feed.json + elif [ -f "$FEED_PATH" ]; then # Step 1: Apply updates to existing advisories jq --slurpfile updates tmp/updated_advisories.json ' .advisories = [ @@ -771,6 +855,7 @@ jobs: ## Summary Automated update from NVD CVE feed. + - **Mode:** ${{ inputs.force_full_scan == true && 'full-rebuild (ignore feed state)' || 'delta (incremental)' }} - **New advisories:** ${{ steps.transform.outputs.new_count }} - **Updated advisories:** ${{ steps.updates.outputs.update_count }} - **Poll window:** ${{ steps.dates.outputs.start_date }} → ${{ steps.dates.outputs.end_date }} @@ -830,10 +915,17 @@ jobs: - name: Summary run: | + if [ "${{ inputs.force_full_scan }}" = "true" ]; then + MODE="full-rebuild (ignore feed state)" + else + MODE="delta (incremental)" + fi + echo "## NVD CVE Poll Summary" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "| Metric | Value |" >> $GITHUB_STEP_SUMMARY echo "|--------|-------|" >> $GITHUB_STEP_SUMMARY + echo "| Mode | $MODE |" >> $GITHUB_STEP_SUMMARY echo "| Poll Window | ${{ steps.dates.outputs.start_date }} → ${{ steps.dates.outputs.end_date }} |" >> $GITHUB_STEP_SUMMARY echo "| Keywords | $KEYWORDS |" >> $GITHUB_STEP_SUMMARY echo "| CVEs Found (filtered) | ${{ steps.process.outputs.filtered_count }} |" >> $GITHUB_STEP_SUMMARY From 56a36b7e529735ccfca01a6f7291c95cdda95295 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 1 Mar 2026 20:14:58 +0200 Subject: [PATCH 4/6] chore: CVE advisories - 35 new, 0 updated (#97) Automated update from NVD CVE feed. Keywords: OpenClaw clawdbot Moltbot NanoClaw WhatsApp-bot baileys Poll window: 2025-11-01T18:07:01.000Z to 2026-03-01T18:07:01.000Z Co-authored-by: davida-ps <232346510+davida-ps@users.noreply.github.com> --- advisories/feed.json | 650 ++++++++++++++++--- advisories/feed.json.sig | 2 +- skills/clawsec-feed/advisories/feed.json | 650 ++++++++++++++++--- skills/clawsec-feed/advisories/feed.json.sig | 2 +- 4 files changed, 1096 insertions(+), 208 deletions(-) diff --git a/advisories/feed.json b/advisories/feed.json index d7555ee..247e8aa 100644 --- a/advisories/feed.json +++ b/advisories/feed.json @@ -1,8 +1,42 @@ { "version": "0.0.3", - "updated": "2026-02-25T16:48:57Z", + "updated": "2026-03-01T18:07:41Z", "description": "Community-driven security advisory feed for ClawSec. Automatically updated with OpenClaw-related CVEs from NVD and community-reported security incidents.", "advisories": [ + { + "id": "CVE-2026-28363", + "severity": "critical", + "type": "unknown_cwe_184", + "nvd_category_id": "CWE-184", + "title": "In OpenClaw before 2026.2.23, tools.exec.safeBins validation for sort could be bypassed via GNU long...", + "description": "In OpenClaw before 2026.2.23, tools.exec.safeBins validation for sort could be bypassed via GNU long-option abbreviations (such as --compress-prog) in allowlist mode, leading to approval-free execution paths that were intended to require approval. Only an exact string such as --compress-program was denied.", + "affected": [ + "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", + "openclaw@*" + ], + "platforms": [ + "openclaw" + ], + "action": "Review and update affected components. See NVD for remediation details.", + "published": "2026-02-27T04:16:03.227", + "references": [ + "https://github.com/openclaw/openclaw/security/advisories/GHSA-3c6h-g97w-fg78" + ], + "cvss_score": 9.9, + "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-28363", + "exploitability_score": "high", + "exploitability_rationale": "Critical CVSS score (9.9); network accessible", + "attack_vector_analysis": { + "is_network_accessible": true, + "requires_authentication": true, + "requires_user_interaction": false, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } + }, { "id": "CVE-2026-27576", "severity": "medium", @@ -14,6 +48,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-21T10:16:13.437", "references": [ @@ -23,9 +60,18 @@ ], "cvss_score": 4.0, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-27576", - "platforms": [ - "openclaw" - ] + "exploitability_score": "high", + "exploitability_rationale": "Medium CVSS score (4.0); requires local access; RCE is critical in agent deployments", + "attack_vector_analysis": { + "is_network_accessible": false, + "requires_authentication": false, + "requires_user_interaction": false, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-27488", @@ -38,6 +84,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-21T10:16:13.267", "references": [ @@ -47,9 +96,18 @@ ], "cvss_score": 7.3, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-27488", - "platforms": [ - "openclaw" - ] + "exploitability_score": "high", + "exploitability_rationale": "High CVSS score (7.3); remotely exploitable without authentication; SSRF affects agents making external requests", + "attack_vector_analysis": { + "is_network_accessible": true, + "requires_authentication": false, + "requires_user_interaction": false, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-27487", @@ -63,6 +121,9 @@ "cpe:2.3:o:apple:macos:-:*:*:*:*:*:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-21T10:16:13.100", "references": [ @@ -72,9 +133,18 @@ ], "cvss_score": 7.6, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-27487", - "platforms": [ - "openclaw" - ] + "exploitability_score": "high", + "exploitability_rationale": "High CVSS score (7.6); network accessible; RCE is critical in agent deployments", + "attack_vector_analysis": { + "is_network_accessible": true, + "requires_authentication": true, + "requires_user_interaction": true, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-27486", @@ -87,6 +157,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-21T10:16:12.903", "references": [ @@ -96,9 +169,18 @@ ], "cvss_score": 5.3, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-27486", - "platforms": [ - "openclaw" - ] + "exploitability_score": "high", + "exploitability_rationale": "Medium CVSS score (5.3); remotely exploitable without authentication", + "attack_vector_analysis": { + "is_network_accessible": true, + "requires_authentication": false, + "requires_user_interaction": false, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-27485", @@ -111,6 +193,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-21T10:16:12.723", "references": [ @@ -120,9 +205,18 @@ ], "cvss_score": 4.4, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-27485", - "platforms": [ - "openclaw" - ] + "exploitability_score": "medium", + "exploitability_rationale": "Medium CVSS score (4.4); requires local access", + "attack_vector_analysis": { + "is_network_accessible": false, + "requires_authentication": true, + "requires_user_interaction": false, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-27484", @@ -135,6 +229,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-21T10:16:12.557", "references": [ @@ -144,9 +241,18 @@ ], "cvss_score": 4.3, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-27484", - "platforms": [ - "openclaw" - ] + "exploitability_score": "medium", + "exploitability_rationale": "Medium CVSS score (4.3); network accessible", + "attack_vector_analysis": { + "is_network_accessible": true, + "requires_authentication": true, + "requires_user_interaction": false, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-27009", @@ -159,6 +265,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-20T00:16:17.620", "references": [ @@ -168,9 +277,18 @@ ], "cvss_score": 5.8, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-27009", - "platforms": [ - "openclaw" - ] + "exploitability_score": "medium", + "exploitability_rationale": "Medium CVSS score (5.8); requires local access; XSS has limited impact in headless agents", + "attack_vector_analysis": { + "is_network_accessible": false, + "requires_authentication": true, + "requires_user_interaction": true, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-27008", @@ -183,6 +301,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-20T00:16:17.460", "references": [ @@ -192,9 +313,18 @@ ], "cvss_score": 6.7, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-27008", - "platforms": [ - "openclaw" - ] + "exploitability_score": "medium", + "exploitability_rationale": "Medium CVSS score (6.7); requires local access", + "attack_vector_analysis": { + "is_network_accessible": false, + "requires_authentication": true, + "requires_user_interaction": false, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-27007", @@ -207,6 +337,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-20T00:16:17.303", "references": [ @@ -216,9 +349,18 @@ ], "cvss_score": 3.3, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-27007", - "platforms": [ - "openclaw" - ] + "exploitability_score": "low", + "exploitability_rationale": "Low CVSS score (3.3); requires local access", + "attack_vector_analysis": { + "is_network_accessible": false, + "requires_authentication": true, + "requires_user_interaction": false, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-27004", @@ -231,6 +373,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-20T00:16:17.140", "references": [ @@ -239,9 +384,18 @@ ], "cvss_score": 5.5, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-27004", - "platforms": [ - "openclaw" - ] + "exploitability_score": "medium", + "exploitability_rationale": "Medium CVSS score (5.5); requires local access", + "attack_vector_analysis": { + "is_network_accessible": false, + "requires_authentication": true, + "requires_user_interaction": false, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-27003", @@ -254,6 +408,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-20T00:16:16.983", "references": [ @@ -262,9 +419,18 @@ ], "cvss_score": 5.5, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-27003", - "platforms": [ - "openclaw" - ] + "exploitability_score": "medium", + "exploitability_rationale": "Medium CVSS score (5.5); requires local access", + "attack_vector_analysis": { + "is_network_accessible": false, + "requires_authentication": true, + "requires_user_interaction": false, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-27002", @@ -277,6 +443,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-20T00:16:16.827", "references": [ @@ -286,9 +455,18 @@ ], "cvss_score": 9.8, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-27002", - "platforms": [ - "openclaw" - ] + "exploitability_score": "high", + "exploitability_rationale": "Critical CVSS score (9.8); remotely exploitable without authentication; RCE is critical in agent deployments", + "attack_vector_analysis": { + "is_network_accessible": true, + "requires_authentication": false, + "requires_user_interaction": false, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-27001", @@ -301,6 +479,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-20T00:16:16.653", "references": [ @@ -310,9 +491,18 @@ ], "cvss_score": 7.8, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-27001", - "platforms": [ - "openclaw" - ] + "exploitability_score": "high", + "exploitability_rationale": "High CVSS score (7.8); requires local access; RCE is critical in agent deployments", + "attack_vector_analysis": { + "is_network_accessible": false, + "requires_authentication": true, + "requires_user_interaction": false, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-26972", @@ -325,6 +515,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-20T00:16:16.500", "references": [ @@ -334,9 +527,18 @@ ], "cvss_score": 6.7, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-26972", - "platforms": [ - "openclaw" - ] + "exploitability_score": "high", + "exploitability_rationale": "Medium CVSS score (6.7); requires local access; path traversal affects agents with file access", + "attack_vector_analysis": { + "is_network_accessible": false, + "requires_authentication": true, + "requires_user_interaction": false, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-26329", @@ -349,6 +551,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-20T00:16:15.687", "references": [ @@ -358,9 +563,18 @@ ], "cvss_score": 6.5, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-26329", - "platforms": [ - "openclaw" - ] + "exploitability_score": "high", + "exploitability_rationale": "Medium CVSS score (6.5); network accessible; path traversal affects agents with file access", + "attack_vector_analysis": { + "is_network_accessible": true, + "requires_authentication": true, + "requires_user_interaction": false, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-26328", @@ -370,8 +584,12 @@ "title": "OpenClaw is a personal AI assistant. Prior to version 2026.2.14, under iMessage `groupPolicy=allowli...", "description": "OpenClaw is a personal AI assistant. Prior to version 2026.2.14, under iMessage `groupPolicy=allowlist`, group authorization could be satisfied by sender identities coming from the DM pairing store, broadening DM trust into group contexts. Version 2026.2.14 fixes the issue.", "affected": [ + "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-20T00:16:15.523", "references": [ @@ -381,9 +599,18 @@ ], "cvss_score": 6.5, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-26328", - "platforms": [ - "openclaw" - ] + "exploitability_score": "medium", + "exploitability_rationale": "Medium CVSS score (6.5); network accessible", + "attack_vector_analysis": { + "is_network_accessible": true, + "requires_authentication": true, + "requires_user_interaction": false, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-26327", @@ -396,6 +623,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-19T23:16:26.100", "references": [ @@ -405,9 +635,18 @@ ], "cvss_score": 6.5, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-26327", - "platforms": [ - "openclaw" - ] + "exploitability_score": "high", + "exploitability_rationale": "Medium CVSS score (6.5); remotely exploitable without authentication", + "attack_vector_analysis": { + "is_network_accessible": true, + "requires_authentication": false, + "requires_user_interaction": false, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-26326", @@ -420,6 +659,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-19T23:16:25.950", "references": [ @@ -429,9 +671,18 @@ ], "cvss_score": 4.3, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-26326", - "platforms": [ - "openclaw" - ] + "exploitability_score": "medium", + "exploitability_rationale": "Medium CVSS score (4.3); network accessible", + "attack_vector_analysis": { + "is_network_accessible": true, + "requires_authentication": true, + "requires_user_interaction": false, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-26325", @@ -444,6 +695,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-19T23:16:25.800", "references": [ @@ -453,9 +707,18 @@ ], "cvss_score": 7.2, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-26325", - "platforms": [ - "openclaw" - ] + "exploitability_score": "high", + "exploitability_rationale": "High CVSS score (7.2); network accessible; RCE is critical in agent deployments", + "attack_vector_analysis": { + "is_network_accessible": true, + "requires_authentication": true, + "requires_user_interaction": false, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-26324", @@ -468,6 +731,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-19T23:16:25.653", "references": [ @@ -477,9 +743,18 @@ ], "cvss_score": 7.5, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-26324", - "platforms": [ - "openclaw" - ] + "exploitability_score": "high", + "exploitability_rationale": "High CVSS score (7.5); remotely exploitable without authentication; SSRF affects agents making external requests", + "attack_vector_analysis": { + "is_network_accessible": true, + "requires_authentication": false, + "requires_user_interaction": false, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-26323", @@ -492,6 +767,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-19T23:16:25.500", "references": [ @@ -501,9 +779,18 @@ ], "cvss_score": 8.8, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-26323", - "platforms": [ - "openclaw" - ] + "exploitability_score": "high", + "exploitability_rationale": "High CVSS score (8.8); network accessible; RCE is critical in agent deployments", + "attack_vector_analysis": { + "is_network_accessible": true, + "requires_authentication": false, + "requires_user_interaction": true, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-26322", @@ -516,6 +803,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-19T23:16:25.340", "references": [ @@ -525,9 +815,18 @@ ], "cvss_score": 7.6, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-26322", - "platforms": [ - "openclaw" - ] + "exploitability_score": "high", + "exploitability_rationale": "High CVSS score (7.6); network accessible; SSRF affects agents making external requests", + "attack_vector_analysis": { + "is_network_accessible": true, + "requires_authentication": true, + "requires_user_interaction": false, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-26321", @@ -540,6 +839,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-19T23:16:25.180", "references": [ @@ -549,9 +851,18 @@ ], "cvss_score": 7.5, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-26321", - "platforms": [ - "openclaw" - ] + "exploitability_score": "high", + "exploitability_rationale": "High CVSS score (7.5); remotely exploitable without authentication; path traversal affects agents with file access", + "attack_vector_analysis": { + "is_network_accessible": true, + "requires_authentication": false, + "requires_user_interaction": false, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-26320", @@ -565,6 +876,9 @@ "cpe:2.3:o:apple:macos:-:*:*:*:*:*:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-19T23:16:25.017", "references": [ @@ -574,9 +888,18 @@ ], "cvss_score": 6.5, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-26320", - "platforms": [ - "openclaw" - ] + "exploitability_score": "medium", + "exploitability_rationale": "Medium CVSS score (6.5); network accessible", + "attack_vector_analysis": { + "is_network_accessible": true, + "requires_authentication": false, + "requires_user_interaction": true, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-26319", @@ -589,6 +912,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-19T23:16:24.857", "references": [ @@ -598,9 +924,18 @@ ], "cvss_score": 7.5, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-26319", - "platforms": [ - "openclaw" - ] + "exploitability_score": "high", + "exploitability_rationale": "High CVSS score (7.5); remotely exploitable without authentication", + "attack_vector_analysis": { + "is_network_accessible": true, + "requires_authentication": false, + "requires_user_interaction": false, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-26317", @@ -610,8 +945,12 @@ "title": "OpenClaw is a personal AI assistant. Prior to 2026.2.14, browser-facing localhost mutation routes ac...", "description": "OpenClaw is a personal AI assistant. Prior to 2026.2.14, browser-facing localhost mutation routes accepted cross-origin browser requests without explicit Origin/Referer validation. Loopback binding reduces remote exposure but does not prevent browser-initiated requests from malicious origins. A malicious website can trigger unauthorized state changes against a victim's local OpenClaw browser control plane (for example opening tabs, starting/stopping the browser, mutating storage/cookies) if the browser control service is reachable on loopback in the victim's browser context. Starting in version 2026.2.14, mutating HTTP methods (POST/PUT/PATCH/DELETE) are rejected when the request indicates a non-loopback Origin/Referer (or `Sec-Fetch-Site: cross-site`). Other mitigations include enabling browser control auth (token/password) and avoid running with auth disabled.", "affected": [ + "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-19T22:16:47.270", "references": [ @@ -621,9 +960,18 @@ ], "cvss_score": 7.1, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-26317", - "platforms": [ - "openclaw" - ] + "exploitability_score": "high", + "exploitability_rationale": "High CVSS score (7.1); network accessible", + "attack_vector_analysis": { + "is_network_accessible": true, + "requires_authentication": false, + "requires_user_interaction": true, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-26316", @@ -636,6 +984,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-19T22:16:47.110", "references": [ @@ -645,9 +996,18 @@ ], "cvss_score": 7.5, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-26316", - "platforms": [ - "openclaw" - ] + "exploitability_score": "high", + "exploitability_rationale": "High CVSS score (7.5); remotely exploitable without authentication", + "attack_vector_analysis": { + "is_network_accessible": true, + "requires_authentication": false, + "requires_user_interaction": false, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-25474", @@ -660,6 +1020,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-19T07:17:45.847", "references": [ @@ -669,9 +1032,18 @@ ], "cvss_score": 7.5, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-25474", - "platforms": [ - "openclaw" - ] + "exploitability_score": "high", + "exploitability_rationale": "High CVSS score (7.5); remotely exploitable without authentication", + "attack_vector_analysis": { + "is_network_accessible": true, + "requires_authentication": false, + "requires_user_interaction": false, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-24764", @@ -684,6 +1056,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-19T07:17:44.957", "references": [ @@ -693,9 +1068,18 @@ ], "cvss_score": 3.7, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-24764", - "platforms": [ - "openclaw" - ] + "exploitability_score": "low", + "exploitability_rationale": "Low CVSS score (3.7); network accessible", + "attack_vector_analysis": { + "is_network_accessible": true, + "requires_authentication": true, + "requires_user_interaction": true, + "complexity": "high" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-25593", @@ -708,6 +1092,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-06T21:16:17.790", "references": [ @@ -715,9 +1102,18 @@ ], "cvss_score": 8.4, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-25593", - "platforms": [ - "openclaw" - ] + "exploitability_score": "high", + "exploitability_rationale": "High CVSS score (8.4); requires local access; RCE is critical in agent deployments", + "attack_vector_analysis": { + "is_network_accessible": false, + "requires_authentication": false, + "requires_user_interaction": false, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-25475", @@ -730,6 +1126,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-04T20:16:07.287", "references": [ @@ -737,9 +1136,18 @@ ], "cvss_score": 6.5, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-25475", - "platforms": [ - "openclaw" - ] + "exploitability_score": "high", + "exploitability_rationale": "Medium CVSS score (6.5); network accessible; path traversal affects agents with file access", + "attack_vector_analysis": { + "is_network_accessible": true, + "requires_authentication": true, + "requires_user_interaction": false, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-25157", @@ -753,6 +1161,9 @@ "cpe:2.3:o:apple:macos:-:*:*:*:*:*:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-04T20:16:06.577", "references": [ @@ -760,9 +1171,18 @@ ], "cvss_score": 7.7, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-25157", - "platforms": [ - "openclaw" - ] + "exploitability_score": "high", + "exploitability_rationale": "High CVSS score (7.7); requires local access; RCE is critical in agent deployments", + "attack_vector_analysis": { + "is_network_accessible": false, + "requires_authentication": false, + "requires_user_interaction": true, + "complexity": "high" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-24763", @@ -775,6 +1195,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-02T23:16:08.593", "references": [ @@ -784,9 +1207,18 @@ ], "cvss_score": 8.8, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-24763", - "platforms": [ - "openclaw" - ] + "exploitability_score": "high", + "exploitability_rationale": "High CVSS score (8.8); network accessible; RCE is critical in agent deployments", + "attack_vector_analysis": { + "is_network_accessible": true, + "requires_authentication": true, + "requires_user_interaction": false, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-25253", @@ -799,6 +1231,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-01T23:15:49.717", "references": [ @@ -808,9 +1243,18 @@ ], "cvss_score": 8.8, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-25253", - "platforms": [ - "openclaw" - ] + "exploitability_score": "high", + "exploitability_rationale": "High CVSS score (8.8); network accessible; RCE is critical in agent deployments", + "attack_vector_analysis": { + "is_network_accessible": true, + "requires_authentication": false, + "requires_user_interaction": true, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } } ] } diff --git a/advisories/feed.json.sig b/advisories/feed.json.sig index 1fa2469..ee332a8 100644 --- a/advisories/feed.json.sig +++ b/advisories/feed.json.sig @@ -1 +1 @@ -1W60LzEFSeZouvJEmO2lBGD3yVWtUVzed9yTQrReZoDLr6qYie7PY7O7Gy02SEI5bHKKdHbUDfYQzECiPVeBBw== \ No newline at end of file +SJ1weYVVi723M8f6s8es6rg34CSPKxbvlBy1QIXdS0giskd5KTADTDLr2STqUCuWpaV7U+JQa/1eWqNX2oJ+Aw== \ No newline at end of file diff --git a/skills/clawsec-feed/advisories/feed.json b/skills/clawsec-feed/advisories/feed.json index d7555ee..247e8aa 100644 --- a/skills/clawsec-feed/advisories/feed.json +++ b/skills/clawsec-feed/advisories/feed.json @@ -1,8 +1,42 @@ { "version": "0.0.3", - "updated": "2026-02-25T16:48:57Z", + "updated": "2026-03-01T18:07:41Z", "description": "Community-driven security advisory feed for ClawSec. Automatically updated with OpenClaw-related CVEs from NVD and community-reported security incidents.", "advisories": [ + { + "id": "CVE-2026-28363", + "severity": "critical", + "type": "unknown_cwe_184", + "nvd_category_id": "CWE-184", + "title": "In OpenClaw before 2026.2.23, tools.exec.safeBins validation for sort could be bypassed via GNU long...", + "description": "In OpenClaw before 2026.2.23, tools.exec.safeBins validation for sort could be bypassed via GNU long-option abbreviations (such as --compress-prog) in allowlist mode, leading to approval-free execution paths that were intended to require approval. Only an exact string such as --compress-program was denied.", + "affected": [ + "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", + "openclaw@*" + ], + "platforms": [ + "openclaw" + ], + "action": "Review and update affected components. See NVD for remediation details.", + "published": "2026-02-27T04:16:03.227", + "references": [ + "https://github.com/openclaw/openclaw/security/advisories/GHSA-3c6h-g97w-fg78" + ], + "cvss_score": 9.9, + "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-28363", + "exploitability_score": "high", + "exploitability_rationale": "Critical CVSS score (9.9); network accessible", + "attack_vector_analysis": { + "is_network_accessible": true, + "requires_authentication": true, + "requires_user_interaction": false, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } + }, { "id": "CVE-2026-27576", "severity": "medium", @@ -14,6 +48,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-21T10:16:13.437", "references": [ @@ -23,9 +60,18 @@ ], "cvss_score": 4.0, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-27576", - "platforms": [ - "openclaw" - ] + "exploitability_score": "high", + "exploitability_rationale": "Medium CVSS score (4.0); requires local access; RCE is critical in agent deployments", + "attack_vector_analysis": { + "is_network_accessible": false, + "requires_authentication": false, + "requires_user_interaction": false, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-27488", @@ -38,6 +84,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-21T10:16:13.267", "references": [ @@ -47,9 +96,18 @@ ], "cvss_score": 7.3, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-27488", - "platforms": [ - "openclaw" - ] + "exploitability_score": "high", + "exploitability_rationale": "High CVSS score (7.3); remotely exploitable without authentication; SSRF affects agents making external requests", + "attack_vector_analysis": { + "is_network_accessible": true, + "requires_authentication": false, + "requires_user_interaction": false, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-27487", @@ -63,6 +121,9 @@ "cpe:2.3:o:apple:macos:-:*:*:*:*:*:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-21T10:16:13.100", "references": [ @@ -72,9 +133,18 @@ ], "cvss_score": 7.6, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-27487", - "platforms": [ - "openclaw" - ] + "exploitability_score": "high", + "exploitability_rationale": "High CVSS score (7.6); network accessible; RCE is critical in agent deployments", + "attack_vector_analysis": { + "is_network_accessible": true, + "requires_authentication": true, + "requires_user_interaction": true, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-27486", @@ -87,6 +157,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-21T10:16:12.903", "references": [ @@ -96,9 +169,18 @@ ], "cvss_score": 5.3, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-27486", - "platforms": [ - "openclaw" - ] + "exploitability_score": "high", + "exploitability_rationale": "Medium CVSS score (5.3); remotely exploitable without authentication", + "attack_vector_analysis": { + "is_network_accessible": true, + "requires_authentication": false, + "requires_user_interaction": false, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-27485", @@ -111,6 +193,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-21T10:16:12.723", "references": [ @@ -120,9 +205,18 @@ ], "cvss_score": 4.4, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-27485", - "platforms": [ - "openclaw" - ] + "exploitability_score": "medium", + "exploitability_rationale": "Medium CVSS score (4.4); requires local access", + "attack_vector_analysis": { + "is_network_accessible": false, + "requires_authentication": true, + "requires_user_interaction": false, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-27484", @@ -135,6 +229,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-21T10:16:12.557", "references": [ @@ -144,9 +241,18 @@ ], "cvss_score": 4.3, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-27484", - "platforms": [ - "openclaw" - ] + "exploitability_score": "medium", + "exploitability_rationale": "Medium CVSS score (4.3); network accessible", + "attack_vector_analysis": { + "is_network_accessible": true, + "requires_authentication": true, + "requires_user_interaction": false, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-27009", @@ -159,6 +265,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-20T00:16:17.620", "references": [ @@ -168,9 +277,18 @@ ], "cvss_score": 5.8, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-27009", - "platforms": [ - "openclaw" - ] + "exploitability_score": "medium", + "exploitability_rationale": "Medium CVSS score (5.8); requires local access; XSS has limited impact in headless agents", + "attack_vector_analysis": { + "is_network_accessible": false, + "requires_authentication": true, + "requires_user_interaction": true, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-27008", @@ -183,6 +301,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-20T00:16:17.460", "references": [ @@ -192,9 +313,18 @@ ], "cvss_score": 6.7, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-27008", - "platforms": [ - "openclaw" - ] + "exploitability_score": "medium", + "exploitability_rationale": "Medium CVSS score (6.7); requires local access", + "attack_vector_analysis": { + "is_network_accessible": false, + "requires_authentication": true, + "requires_user_interaction": false, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-27007", @@ -207,6 +337,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-20T00:16:17.303", "references": [ @@ -216,9 +349,18 @@ ], "cvss_score": 3.3, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-27007", - "platforms": [ - "openclaw" - ] + "exploitability_score": "low", + "exploitability_rationale": "Low CVSS score (3.3); requires local access", + "attack_vector_analysis": { + "is_network_accessible": false, + "requires_authentication": true, + "requires_user_interaction": false, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-27004", @@ -231,6 +373,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-20T00:16:17.140", "references": [ @@ -239,9 +384,18 @@ ], "cvss_score": 5.5, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-27004", - "platforms": [ - "openclaw" - ] + "exploitability_score": "medium", + "exploitability_rationale": "Medium CVSS score (5.5); requires local access", + "attack_vector_analysis": { + "is_network_accessible": false, + "requires_authentication": true, + "requires_user_interaction": false, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-27003", @@ -254,6 +408,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-20T00:16:16.983", "references": [ @@ -262,9 +419,18 @@ ], "cvss_score": 5.5, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-27003", - "platforms": [ - "openclaw" - ] + "exploitability_score": "medium", + "exploitability_rationale": "Medium CVSS score (5.5); requires local access", + "attack_vector_analysis": { + "is_network_accessible": false, + "requires_authentication": true, + "requires_user_interaction": false, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-27002", @@ -277,6 +443,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-20T00:16:16.827", "references": [ @@ -286,9 +455,18 @@ ], "cvss_score": 9.8, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-27002", - "platforms": [ - "openclaw" - ] + "exploitability_score": "high", + "exploitability_rationale": "Critical CVSS score (9.8); remotely exploitable without authentication; RCE is critical in agent deployments", + "attack_vector_analysis": { + "is_network_accessible": true, + "requires_authentication": false, + "requires_user_interaction": false, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-27001", @@ -301,6 +479,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-20T00:16:16.653", "references": [ @@ -310,9 +491,18 @@ ], "cvss_score": 7.8, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-27001", - "platforms": [ - "openclaw" - ] + "exploitability_score": "high", + "exploitability_rationale": "High CVSS score (7.8); requires local access; RCE is critical in agent deployments", + "attack_vector_analysis": { + "is_network_accessible": false, + "requires_authentication": true, + "requires_user_interaction": false, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-26972", @@ -325,6 +515,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-20T00:16:16.500", "references": [ @@ -334,9 +527,18 @@ ], "cvss_score": 6.7, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-26972", - "platforms": [ - "openclaw" - ] + "exploitability_score": "high", + "exploitability_rationale": "Medium CVSS score (6.7); requires local access; path traversal affects agents with file access", + "attack_vector_analysis": { + "is_network_accessible": false, + "requires_authentication": true, + "requires_user_interaction": false, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-26329", @@ -349,6 +551,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-20T00:16:15.687", "references": [ @@ -358,9 +563,18 @@ ], "cvss_score": 6.5, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-26329", - "platforms": [ - "openclaw" - ] + "exploitability_score": "high", + "exploitability_rationale": "Medium CVSS score (6.5); network accessible; path traversal affects agents with file access", + "attack_vector_analysis": { + "is_network_accessible": true, + "requires_authentication": true, + "requires_user_interaction": false, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-26328", @@ -370,8 +584,12 @@ "title": "OpenClaw is a personal AI assistant. Prior to version 2026.2.14, under iMessage `groupPolicy=allowli...", "description": "OpenClaw is a personal AI assistant. Prior to version 2026.2.14, under iMessage `groupPolicy=allowlist`, group authorization could be satisfied by sender identities coming from the DM pairing store, broadening DM trust into group contexts. Version 2026.2.14 fixes the issue.", "affected": [ + "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-20T00:16:15.523", "references": [ @@ -381,9 +599,18 @@ ], "cvss_score": 6.5, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-26328", - "platforms": [ - "openclaw" - ] + "exploitability_score": "medium", + "exploitability_rationale": "Medium CVSS score (6.5); network accessible", + "attack_vector_analysis": { + "is_network_accessible": true, + "requires_authentication": true, + "requires_user_interaction": false, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-26327", @@ -396,6 +623,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-19T23:16:26.100", "references": [ @@ -405,9 +635,18 @@ ], "cvss_score": 6.5, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-26327", - "platforms": [ - "openclaw" - ] + "exploitability_score": "high", + "exploitability_rationale": "Medium CVSS score (6.5); remotely exploitable without authentication", + "attack_vector_analysis": { + "is_network_accessible": true, + "requires_authentication": false, + "requires_user_interaction": false, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-26326", @@ -420,6 +659,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-19T23:16:25.950", "references": [ @@ -429,9 +671,18 @@ ], "cvss_score": 4.3, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-26326", - "platforms": [ - "openclaw" - ] + "exploitability_score": "medium", + "exploitability_rationale": "Medium CVSS score (4.3); network accessible", + "attack_vector_analysis": { + "is_network_accessible": true, + "requires_authentication": true, + "requires_user_interaction": false, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-26325", @@ -444,6 +695,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-19T23:16:25.800", "references": [ @@ -453,9 +707,18 @@ ], "cvss_score": 7.2, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-26325", - "platforms": [ - "openclaw" - ] + "exploitability_score": "high", + "exploitability_rationale": "High CVSS score (7.2); network accessible; RCE is critical in agent deployments", + "attack_vector_analysis": { + "is_network_accessible": true, + "requires_authentication": true, + "requires_user_interaction": false, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-26324", @@ -468,6 +731,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-19T23:16:25.653", "references": [ @@ -477,9 +743,18 @@ ], "cvss_score": 7.5, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-26324", - "platforms": [ - "openclaw" - ] + "exploitability_score": "high", + "exploitability_rationale": "High CVSS score (7.5); remotely exploitable without authentication; SSRF affects agents making external requests", + "attack_vector_analysis": { + "is_network_accessible": true, + "requires_authentication": false, + "requires_user_interaction": false, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-26323", @@ -492,6 +767,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-19T23:16:25.500", "references": [ @@ -501,9 +779,18 @@ ], "cvss_score": 8.8, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-26323", - "platforms": [ - "openclaw" - ] + "exploitability_score": "high", + "exploitability_rationale": "High CVSS score (8.8); network accessible; RCE is critical in agent deployments", + "attack_vector_analysis": { + "is_network_accessible": true, + "requires_authentication": false, + "requires_user_interaction": true, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-26322", @@ -516,6 +803,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-19T23:16:25.340", "references": [ @@ -525,9 +815,18 @@ ], "cvss_score": 7.6, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-26322", - "platforms": [ - "openclaw" - ] + "exploitability_score": "high", + "exploitability_rationale": "High CVSS score (7.6); network accessible; SSRF affects agents making external requests", + "attack_vector_analysis": { + "is_network_accessible": true, + "requires_authentication": true, + "requires_user_interaction": false, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-26321", @@ -540,6 +839,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-19T23:16:25.180", "references": [ @@ -549,9 +851,18 @@ ], "cvss_score": 7.5, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-26321", - "platforms": [ - "openclaw" - ] + "exploitability_score": "high", + "exploitability_rationale": "High CVSS score (7.5); remotely exploitable without authentication; path traversal affects agents with file access", + "attack_vector_analysis": { + "is_network_accessible": true, + "requires_authentication": false, + "requires_user_interaction": false, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-26320", @@ -565,6 +876,9 @@ "cpe:2.3:o:apple:macos:-:*:*:*:*:*:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-19T23:16:25.017", "references": [ @@ -574,9 +888,18 @@ ], "cvss_score": 6.5, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-26320", - "platforms": [ - "openclaw" - ] + "exploitability_score": "medium", + "exploitability_rationale": "Medium CVSS score (6.5); network accessible", + "attack_vector_analysis": { + "is_network_accessible": true, + "requires_authentication": false, + "requires_user_interaction": true, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-26319", @@ -589,6 +912,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-19T23:16:24.857", "references": [ @@ -598,9 +924,18 @@ ], "cvss_score": 7.5, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-26319", - "platforms": [ - "openclaw" - ] + "exploitability_score": "high", + "exploitability_rationale": "High CVSS score (7.5); remotely exploitable without authentication", + "attack_vector_analysis": { + "is_network_accessible": true, + "requires_authentication": false, + "requires_user_interaction": false, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-26317", @@ -610,8 +945,12 @@ "title": "OpenClaw is a personal AI assistant. Prior to 2026.2.14, browser-facing localhost mutation routes ac...", "description": "OpenClaw is a personal AI assistant. Prior to 2026.2.14, browser-facing localhost mutation routes accepted cross-origin browser requests without explicit Origin/Referer validation. Loopback binding reduces remote exposure but does not prevent browser-initiated requests from malicious origins. A malicious website can trigger unauthorized state changes against a victim's local OpenClaw browser control plane (for example opening tabs, starting/stopping the browser, mutating storage/cookies) if the browser control service is reachable on loopback in the victim's browser context. Starting in version 2026.2.14, mutating HTTP methods (POST/PUT/PATCH/DELETE) are rejected when the request indicates a non-loopback Origin/Referer (or `Sec-Fetch-Site: cross-site`). Other mitigations include enabling browser control auth (token/password) and avoid running with auth disabled.", "affected": [ + "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-19T22:16:47.270", "references": [ @@ -621,9 +960,18 @@ ], "cvss_score": 7.1, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-26317", - "platforms": [ - "openclaw" - ] + "exploitability_score": "high", + "exploitability_rationale": "High CVSS score (7.1); network accessible", + "attack_vector_analysis": { + "is_network_accessible": true, + "requires_authentication": false, + "requires_user_interaction": true, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-26316", @@ -636,6 +984,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-19T22:16:47.110", "references": [ @@ -645,9 +996,18 @@ ], "cvss_score": 7.5, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-26316", - "platforms": [ - "openclaw" - ] + "exploitability_score": "high", + "exploitability_rationale": "High CVSS score (7.5); remotely exploitable without authentication", + "attack_vector_analysis": { + "is_network_accessible": true, + "requires_authentication": false, + "requires_user_interaction": false, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-25474", @@ -660,6 +1020,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-19T07:17:45.847", "references": [ @@ -669,9 +1032,18 @@ ], "cvss_score": 7.5, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-25474", - "platforms": [ - "openclaw" - ] + "exploitability_score": "high", + "exploitability_rationale": "High CVSS score (7.5); remotely exploitable without authentication", + "attack_vector_analysis": { + "is_network_accessible": true, + "requires_authentication": false, + "requires_user_interaction": false, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-24764", @@ -684,6 +1056,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-19T07:17:44.957", "references": [ @@ -693,9 +1068,18 @@ ], "cvss_score": 3.7, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-24764", - "platforms": [ - "openclaw" - ] + "exploitability_score": "low", + "exploitability_rationale": "Low CVSS score (3.7); network accessible", + "attack_vector_analysis": { + "is_network_accessible": true, + "requires_authentication": true, + "requires_user_interaction": true, + "complexity": "high" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-25593", @@ -708,6 +1092,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-06T21:16:17.790", "references": [ @@ -715,9 +1102,18 @@ ], "cvss_score": 8.4, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-25593", - "platforms": [ - "openclaw" - ] + "exploitability_score": "high", + "exploitability_rationale": "High CVSS score (8.4); requires local access; RCE is critical in agent deployments", + "attack_vector_analysis": { + "is_network_accessible": false, + "requires_authentication": false, + "requires_user_interaction": false, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-25475", @@ -730,6 +1126,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-04T20:16:07.287", "references": [ @@ -737,9 +1136,18 @@ ], "cvss_score": 6.5, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-25475", - "platforms": [ - "openclaw" - ] + "exploitability_score": "high", + "exploitability_rationale": "Medium CVSS score (6.5); network accessible; path traversal affects agents with file access", + "attack_vector_analysis": { + "is_network_accessible": true, + "requires_authentication": true, + "requires_user_interaction": false, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-25157", @@ -753,6 +1161,9 @@ "cpe:2.3:o:apple:macos:-:*:*:*:*:*:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-04T20:16:06.577", "references": [ @@ -760,9 +1171,18 @@ ], "cvss_score": 7.7, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-25157", - "platforms": [ - "openclaw" - ] + "exploitability_score": "high", + "exploitability_rationale": "High CVSS score (7.7); requires local access; RCE is critical in agent deployments", + "attack_vector_analysis": { + "is_network_accessible": false, + "requires_authentication": false, + "requires_user_interaction": true, + "complexity": "high" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-24763", @@ -775,6 +1195,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-02T23:16:08.593", "references": [ @@ -784,9 +1207,18 @@ ], "cvss_score": 8.8, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-24763", - "platforms": [ - "openclaw" - ] + "exploitability_score": "high", + "exploitability_rationale": "High CVSS score (8.8); network accessible; RCE is critical in agent deployments", + "attack_vector_analysis": { + "is_network_accessible": true, + "requires_authentication": true, + "requires_user_interaction": false, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } }, { "id": "CVE-2026-25253", @@ -799,6 +1231,9 @@ "cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*", "openclaw@*" ], + "platforms": [ + "openclaw" + ], "action": "Review and update affected components. See NVD for remediation details.", "published": "2026-02-01T23:15:49.717", "references": [ @@ -808,9 +1243,18 @@ ], "cvss_score": 8.8, "nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-25253", - "platforms": [ - "openclaw" - ] + "exploitability_score": "high", + "exploitability_rationale": "High CVSS score (8.8); network accessible; RCE is critical in agent deployments", + "attack_vector_analysis": { + "is_network_accessible": true, + "requires_authentication": false, + "requires_user_interaction": true, + "complexity": "low" + }, + "exploit_detection": { + "exploit_available": false, + "exploit_sources": [] + } } ] } diff --git a/skills/clawsec-feed/advisories/feed.json.sig b/skills/clawsec-feed/advisories/feed.json.sig index 1fa2469..ee332a8 100644 --- a/skills/clawsec-feed/advisories/feed.json.sig +++ b/skills/clawsec-feed/advisories/feed.json.sig @@ -1 +1 @@ -1W60LzEFSeZouvJEmO2lBGD3yVWtUVzed9yTQrReZoDLr6qYie7PY7O7Gy02SEI5bHKKdHbUDfYQzECiPVeBBw== \ No newline at end of file +SJ1weYVVi723M8f6s8es6rg34CSPKxbvlBy1QIXdS0giskd5KTADTDLr2STqUCuWpaV7U+JQa/1eWqNX2oJ+Aw== \ No newline at end of file From e0eae65586bce3fb7db3561b78ffe51c351a246f Mon Sep 17 00:00:00 2001 From: davida-ps Date: Sun, 1 Mar 2026 21:50:10 +0200 Subject: [PATCH 5/6] refactor(ci): extract shared exploitability enrichment helper (#95) * refactor(ci): share exploitability enrichment script * refactor(scripts): reuse shared exploitability enricher in local feed --- .github/workflows/community-advisory.yml | 59 +---- .github/workflows/poll-nvd-cves.yml | 67 +----- scripts/ci/enrich_exploitability.sh | 263 +++++++++++++++++++++++ scripts/populate-local-feed.sh | 87 +------- 4 files changed, 283 insertions(+), 193 deletions(-) create mode 100755 scripts/ci/enrich_exploitability.sh diff --git a/.github/workflows/community-advisory.yml b/.github/workflows/community-advisory.yml index a88f38e..e6729b7 100644 --- a/.github/workflows/community-advisory.yml +++ b/.github/workflows/community-advisory.yml @@ -206,60 +206,13 @@ jobs: 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) + scripts/ci/enrich_exploitability.sh \ + --mode single \ + --input tmp_advisory.json \ + --output 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 + echo "=== Exploitability analysis complete ===" + echo "Exploitability score: $(jq -r '.exploitability_score // "unknown"' tmp_advisory.json)" - name: Update feed if: steps.parse.outputs.already_exists != 'true' diff --git a/.github/workflows/poll-nvd-cves.yml b/.github/workflows/poll-nvd-cves.yml index 6530ff9..5264a07 100644 --- a/.github/workflows/poll-nvd-cves.yml +++ b/.github/workflows/poll-nvd-cves.yml @@ -684,68 +684,11 @@ jobs: }] | 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 + scripts/ci/enrich_exploitability.sh \ + --mode batch \ + --input tmp/new_advisories.json \ + --output tmp/new_advisories.json \ + --cvss-vectors tmp/cvss_vectors.json echo "=== Exploitability analysis complete ===" diff --git a/scripts/ci/enrich_exploitability.sh b/scripts/ci/enrich_exploitability.sh new file mode 100755 index 0000000..2f7c57d --- /dev/null +++ b/scripts/ci/enrich_exploitability.sh @@ -0,0 +1,263 @@ +#!/usr/bin/env bash +set -euo pipefail + +usage() { + cat <<'EOF' +Usage: + scripts/ci/enrich_exploitability.sh --mode single|batch --input --output [--cvss-vectors ] [--analyzer ] + +Options: + --mode Processing mode: single advisory object or batch advisory array + --input Input JSON path + --output Output JSON path + --cvss-vectors Optional JSON object mapping advisory id -> CVSS vector + --analyzer Optional analyzer path (default: utils/analyze_exploitability.py) + --help Show this help +EOF +} + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +cd "$REPO_ROOT" + +MODE="" +INPUT_PATH="" +OUTPUT_PATH="" +CVSS_VECTORS_PATH="" +ANALYZER_PATH="utils/analyze_exploitability.py" + +while [[ $# -gt 0 ]]; do + case "$1" in + --mode) + MODE="${2:-}" + shift 2 + ;; + --input) + INPUT_PATH="${2:-}" + shift 2 + ;; + --output) + OUTPUT_PATH="${2:-}" + shift 2 + ;; + --cvss-vectors) + CVSS_VECTORS_PATH="${2:-}" + shift 2 + ;; + --analyzer) + ANALYZER_PATH="${2:-}" + shift 2 + ;; + --help|-h) + usage + exit 0 + ;; + *) + echo "ERROR: Unknown argument: $1" >&2 + usage >&2 + exit 1 + ;; + esac +done + +if [[ "$MODE" != "single" && "$MODE" != "batch" ]]; then + echo "ERROR: --mode must be one of: single, batch" >&2 + exit 1 +fi + +if [[ -z "$INPUT_PATH" || -z "$OUTPUT_PATH" ]]; then + echo "ERROR: --input and --output are required" >&2 + exit 1 +fi + +if [[ ! -f "$INPUT_PATH" ]]; then + echo "ERROR: input file not found: $INPUT_PATH" >&2 + exit 1 +fi + +if [[ ! -f "$ANALYZER_PATH" ]]; then + echo "ERROR: analyzer file not found: $ANALYZER_PATH" >&2 + exit 1 +fi + +if [[ -n "$CVSS_VECTORS_PATH" && ! -f "$CVSS_VECTORS_PATH" ]]; then + echo "ERROR: --cvss-vectors file not found: $CVSS_VECTORS_PATH" >&2 + exit 1 +fi + +if ! command -v jq >/dev/null 2>&1; then + echo "ERROR: jq is required" >&2 + exit 1 +fi + +if command -v python >/dev/null 2>&1; then + PYTHON_BIN="python" +elif command -v python3 >/dev/null 2>&1; then + PYTHON_BIN="python3" +else + echo "ERROR: python or python3 is required" >&2 + exit 1 +fi + +tmpdir="$(mktemp -d)" +trap 'rm -rf "$tmpdir"' EXIT + +resolve_cvss_vector() { + local advisory_json="$1" + local advisory_id + advisory_id="$(echo "$advisory_json" | jq -r '.id // ""')" + + if [[ -n "$CVSS_VECTORS_PATH" ]]; then + jq -r --arg id "$advisory_id" '.[$id] // ""' "$CVSS_VECTORS_PATH" + else + echo "$advisory_json" | jq -r '.cvss_vector // ""' + fi +} + +severity_to_cvss() { + case "$1" in + critical) echo "9.5" ;; + high) echo "7.5" ;; + medium) echo "5.5" ;; + low) echo "3.0" ;; + *) echo "5.0" ;; + esac +} + +build_analysis_input() { + local advisory_json="$1" + local mode="$2" + local cve_id cvss_score cvss_vector vuln_type description references severity + + cve_id="$(echo "$advisory_json" | jq -r '.id // ""')" + vuln_type="$(echo "$advisory_json" | jq -r '.type // ""')" + description="$(echo "$advisory_json" | jq -r '.description // ""')" + references="$(echo "$advisory_json" | jq -c '.references // []')" + cvss_vector="$(resolve_cvss_vector "$advisory_json")" + + if [[ "$mode" == "single" ]]; then + severity="$(echo "$advisory_json" | jq -r '.severity // "medium"')" + cvss_score="$(severity_to_cvss "$severity")" + else + cvss_score="$(echo "$advisory_json" | jq -r '.cvss_score // 0')" + fi + + 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_analysis() { + local advisory_json="$1" + local mode="$2" + local output_file="$3" + local advisory_id analysis_input analysis + + advisory_id="$(echo "$advisory_json" | jq -r '.id // "unknown"')" + analysis_input="$(build_analysis_input "$advisory_json" "$mode")" + + if analysis="$(echo "$analysis_input" | "$PYTHON_BIN" "$ANALYZER_PATH" --json --check-exploits 2>/dev/null)"; then + echo "$analysis" > "$output_file" + return 0 + fi + + echo "::warning::Failed to analyze exploitability for $advisory_id, continuing without enrichment" + return 1 +} + +enrich_single() { + if ! jq -e 'type == "object"' "$INPUT_PATH" >/dev/null; then + echo "ERROR: single mode expects JSON object at $INPUT_PATH" >&2 + exit 1 + fi + + local advisory analysis_file output_tmp + advisory="$(cat "$INPUT_PATH")" + analysis_file="$tmpdir/analysis_single.json" + output_tmp="$tmpdir/output_single.json" + + if run_analysis "$advisory" "single" "$analysis_file"; then + jq --slurpfile analysis "$analysis_file" ' + . + { + 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 + } + ' "$INPUT_PATH" > "$output_tmp" + else + cp "$INPUT_PATH" "$output_tmp" + fi + + mv "$output_tmp" "$OUTPUT_PATH" + echo "Exploitability enrichment complete (single): $OUTPUT_PATH" +} + +enrich_batch() { + if ! jq -e 'type == "array"' "$INPUT_PATH" >/dev/null; then + echo "ERROR: batch mode expects JSON array at $INPUT_PATH" >&2 + exit 1 + fi + + local analyzed_count failed_count index advisory analysis_file output_tmp analyses_json + analyzed_count=0 + failed_count=0 + index=0 + analyses_json="$tmpdir/analyses.json" + output_tmp="$tmpdir/output_batch.json" + + while IFS= read -r advisory; do + analysis_file="$tmpdir/analysis_${index}.json" + if run_analysis "$advisory" "batch" "$analysis_file"; then + analyzed_count=$((analyzed_count + 1)) + else + failed_count=$((failed_count + 1)) + rm -f "$analysis_file" + fi + index=$((index + 1)) + done < <(jq -c '.[]' "$INPUT_PATH") + + if ls "$tmpdir"/analysis_*.json >/dev/null 2>&1; then + jq -s '.' "$tmpdir"/analysis_*.json > "$analyses_json" + else + echo '[]' > "$analyses_json" + fi + + jq --slurpfile analyses "$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 + ) + ' "$INPUT_PATH" > "$output_tmp" + + mv "$output_tmp" "$OUTPUT_PATH" + echo "Exploitability enrichment complete (batch): $OUTPUT_PATH" + echo "Analyzed: $analyzed_count, failed: $failed_count" +} + +if [[ "$MODE" == "single" ]]; then + enrich_single +else + enrich_batch +fi diff --git a/scripts/populate-local-feed.sh b/scripts/populate-local-feed.sh index ec67d61..d02a3c5 100755 --- a/scripts/populate-local-feed.sh +++ b/scripts/populate-local-feed.sh @@ -18,7 +18,7 @@ source "$SCRIPT_DIR/feed-utils.sh" init_feed_paths "$PROJECT_ROOT" KEYWORDS="OpenClaw clawdbot Moltbot NanoClaw WhatsApp-bot baileys" GITHUB_REF_PATTERN="github.com/openclaw/openclaw github.com/qwibitai/NanoClaw" -ANALYZER="$PROJECT_ROOT/utils/analyze_exploitability.py" +ENRICH_SCRIPT="$PROJECT_ROOT/scripts/ci/enrich_exploitability.sh" # Parse args DAYS_BACK=120 @@ -47,19 +47,9 @@ echo "Days back: $DAYS_BACK" echo "Force mode: $FORCE" echo "" -# Verify exploitability analyzer prerequisites -if ! command -v python3 &> /dev/null; then - echo "Error: python3 is required but not found in PATH" - exit 1 -fi - -if [ ! -f "$ANALYZER" ]; then - echo "Error: Exploitability analyzer not found: $ANALYZER" - exit 1 -fi - -if ! python3 "$ANALYZER" --help &> /dev/null; then - echo "Error: Exploitability analyzer failed to run. Check Python environment." +# Verify enrichment helper exists (it validates Python/analyzer prerequisites internally). +if [ ! -x "$ENRICH_SCRIPT" ]; then + echo "Error: Exploitability enrichment helper not found or not executable: $ENRICH_SCRIPT" exit 1 fi @@ -331,70 +321,11 @@ jq ' }] | map({(.id): .cvss_vector}) | add ' "$TEMP_DIR/filtered_cves.json" > "$TEMP_DIR/cvss_vectors.json" -ANALYZED_COUNT=0 -FAILED_ANALYSIS=0 - -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] // ""' "$TEMP_DIR/cvss_vectors.json") - VULN_TYPE=$(echo "$advisory" | jq -r '.type // ""') - DESCRIPTION=$(echo "$advisory" | jq -r '.description // ""') - REFERENCES=$(echo "$advisory" | jq -c '.references // []') - - 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 - }') - - if ANALYSIS=$(echo "$INPUT_JSON" | python3 "$ANALYZER" --json --check-exploits 2>/dev/null); then - echo "$ANALYSIS" > "$TEMP_DIR/exploitability_${CVE_ID}.json" - SCORE=$(echo "$ANALYSIS" | jq -r '.exploitability_score // "unknown"') - echo " ✓ $CVE_ID -> $SCORE" - ANALYZED_COUNT=$((ANALYZED_COUNT + 1)) - else - echo " ⚠ $CVE_ID analysis failed; keeping null exploitability fields" - FAILED_ANALYSIS=$((FAILED_ANALYSIS + 1)) - fi -done < <(jq -c '.[]' "$TEMP_DIR/new_advisories.json") - -if ls "$TEMP_DIR"/exploitability_*.json >/dev/null 2>&1; then - jq -s '.' "$TEMP_DIR"/exploitability_*.json > "$TEMP_DIR/exploitability_analyses.json" -else - echo '[]' > "$TEMP_DIR/exploitability_analyses.json" -fi - -jq --slurpfile analyses "$TEMP_DIR/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 - ) -' "$TEMP_DIR/new_advisories.json" > "$TEMP_DIR/new_advisories_enriched.json" - -mv "$TEMP_DIR/new_advisories_enriched.json" "$TEMP_DIR/new_advisories.json" - -echo "Exploitability analysis complete: $ANALYZED_COUNT analyzed, $FAILED_ANALYSIS failed" +"$ENRICH_SCRIPT" \ + --mode batch \ + --input "$TEMP_DIR/new_advisories.json" \ + --output "$TEMP_DIR/new_advisories.json" \ + --cvss-vectors "$TEMP_DIR/cvss_vectors.json" echo "" echo "=== New Advisories ===" From 79c303fa3f68f5f79be5e16fcba2ae397c4c472e Mon Sep 17 00:00:00 2001 From: davida-ps Date: Mon, 2 Mar 2026 09:47:42 +0200 Subject: [PATCH 6/6] fix(ci): restore github token flow for skill release (#99) --- .github/workflows/skill-release.yml | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/.github/workflows/skill-release.yml b/.github/workflows/skill-release.yml index f631682..ab8d4f0 100644 --- a/.github/workflows/skill-release.yml +++ b/.github/workflows/skill-release.yml @@ -540,7 +540,7 @@ jobs: if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') runs-on: ubuntu-latest permissions: - contents: read + contents: write outputs: skill_name: ${{ steps.parse.outputs.skill_name }} version: ${{ steps.parse.outputs.version }} @@ -877,15 +877,6 @@ jobs: } >> $GITHUB_OUTPUT fi - - name: Require automation token for release publishing - env: - AUTOMATION_TOKEN: ${{ secrets.POLL_NVD_CVES_PAT }} - run: | - if [ -z "$AUTOMATION_TOKEN" ]; then - echo "::error::Set POLL_NVD_CVES_PAT with repo write permissions." - exit 1 - fi - - name: Create GitHub Release uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0 with: @@ -945,7 +936,7 @@ jobs: draft: false prerelease: ${{ contains(github.ref_name, 'alpha') || contains(github.ref_name, 'beta') || contains(github.ref_name, 'rc') }} env: - GITHUB_TOKEN: ${{ secrets.POLL_NVD_CVES_PAT }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Delete superseded releases run: | @@ -982,7 +973,7 @@ jobs: echo "Superseded release cleanup complete" env: - GITHUB_TOKEN: ${{ secrets.POLL_NVD_CVES_PAT }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} publish-clawhub: # Separate job for ClawHub publishing - runs after GitHub release