dependency-cve-monitor #100
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: "dependency-cve-monitor" | |
| on: | |
| schedule: | |
| - cron: '0 9 * * *' | |
| workflow_dispatch: | |
| env: | |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true | |
| jobs: | |
| submit-dependencies: | |
| if: github.repository == 'aws/aws-sdk-java-v2' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: advanced-security/maven-dependency-submission-action@b275d12641ac2d2108b2cbb7598b154ad2f2cee8 # v5 | |
| with: | |
| maven-args: >- | |
| -DtransitiveExcludes=*:* | |
| -DclasspathScope=runtime | |
| -pl !build-tools,!release-scripts,!archetypes,!test/test-utils,!test/sdk-benchmarks,!test/http-client-tests,!test/http-client-benchmarks,!test/s3-benchmarks,!test/protocol-tests-core,!test/ruleset-testing-core,!test/protocol-tests,!test/service-test-utils,!test/codegen-generated-classes-test,!test/sdk-standard-benchmarks,!test/module-path-tests,!test/tests-coverage-reporting,!test/stability-tests,!test/sdk-native-image-test,!test/auth-tests,!test/service-client-backward-compat-test,!test/bundle-logging-bridge-binding-test,!test/v2-migration-tests,!test/bundle-shading-tests,!test/crt-unavailable-tests,!test/architecture-tests,!test/s3-tests | |
| notify-alerts: | |
| if: github.repository == 'aws/aws-sdk-java-v2' | |
| needs: submit-dependencies | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| permissions: {} | |
| steps: | |
| - name: Wait for alert processing | |
| run: sleep 60 | |
| - name: Check and notify actionable alerts | |
| env: | |
| SLACK_WEBHOOK_URL: ${{ secrets.CI_SLACK_WEBHOOK_URL }} | |
| GH_TOKEN: ${{ secrets.DEPENDABOT_ALERTS_TOKEN }} | |
| JQ_FILTER_B64: "Wy5bXSB8IHNlbGVjdCguY3JlYXRlZF9hdCA+ICRjdXRvZmYgYW5kIC5zZWN1cml0eV92dWxuZXJhYmlsaXR5LmZpcnN0X3BhdGNoZWRfdmVyc2lvbiAhPSBudWxsKV0KfCBncm91cF9ieSguc2VjdXJpdHlfdnVsbmVyYWJpbGl0eS5wYWNrYWdlLm5hbWUpCnwgbWFwKHsKICAgIHBrZzogLlswXS5zZWN1cml0eV92dWxuZXJhYmlsaXR5LnBhY2thZ2UubmFtZSwKICAgIGZpeDogLlswXS5zZWN1cml0eV92dWxuZXJhYmlsaXR5LmZpcnN0X3BhdGNoZWRfdmVyc2lvbi5pZGVudGlmaWVyLAogICAgbWFuaWZlc3RzOiAoWy5bXS5kZXBlbmRlbmN5Lm1hbmlmZXN0X3BhdGhdIHwgdW5pcXVlIHwgbWFwKGdzdWIoIi9wb20ueG1sJCI7ICIiKSB8IGdzdWIoIi4qLyI7ICIiKSkpLAogICAgY3ZlczogKGdyb3VwX2J5KC5zZWN1cml0eV9hZHZpc29yeS5jdmVfaWQgLy8gLnNlY3VyaXR5X2Fkdmlzb3J5Lmdoc2FfaWQpIHwgbWFwKHsKICAgICAgY3ZlOiAoLlswXS5zZWN1cml0eV9hZHZpc29yeS5jdmVfaWQgLy8gLlswXS5zZWN1cml0eV9hZHZpc29yeS5naHNhX2lkKSwKICAgICAgZ2hzYTogLlswXS5zZWN1cml0eV9hZHZpc29yeS5naHNhX2lkLAogICAgICBzZXZlcml0eTogKC5bMF0uc2VjdXJpdHlfYWR2aXNvcnkuc2V2ZXJpdHkgfCBhc2NpaV91cGNhc2UpLAogICAgICBzdW1tYXJ5OiAoLlswXS5zZWN1cml0eV9hZHZpc29yeS5zdW1tYXJ5IHwgc3BsaXQoIi4gIikgfCBmaXJzdCB8IGlmIGxlbmd0aCA+IDE1MCB0aGVuIC5bOjE0N10gKyAiLi4uIiBlbHNlIC4gZW5kKQogICAgfSkpCiAgfSkKfCBpZiBsZW5ndGggPT0gMCB0aGVuIGVtcHR5CiAgZWxzZQogICAgIjp3YXJuaW5nOiAqXChtYXAoLmN2ZXMgfCBsZW5ndGgpIHwgYWRkKSBDVkUocykgZGV0ZWN0ZWQgdmlhIDxodHRwczovL2dpdGh1Yi5jb20vYXdzL2F3cy1zZGstamF2YS12Mi9zZWN1cml0eS9kZXBlbmRhYm90fERlcGVuZGFib3QgYWxlcnRzPipcblxuIiArCiAgICAobWFwKAogICAgICAiYFwoLnBrZylgIChcKC5tYW5pZmVzdHMgfCBqb2luKCIsICIpKSkgXHUyMTkyIFVwZ3JhZGUgdG8gYFwoLmZpeClgXG4iICsKICAgICAgKC5jdmVzIHwgbWFwKCIgIFx1MjAyMiA8aHR0cHM6Ly9naXRodWIuY29tL2Fkdmlzb3JpZXMvXCguZ2hzYSl8XCguY3ZlKT4gKFwoLnNldmVyaXR5KSlcbiAgICBcKC5zdW1tYXJ5KSIpIHwgam9pbigiXG4iKSkKICAgICkgfCBqb2luKCJcblxuIikpICsKICAgICJcblxuKkFjdGlvbiBuZWVkZWQqOiBVcGdyYWRlIHRoZSBhZmZlY3RlZCBkZXBlbmRlbmN5IHZlcnNpb25zIHRvIHRoZSBmaXggdmVyc2lvbnMgbGlzdGVkIGFib3ZlLiIKICBlbmQK" | |
| shell: bash | |
| run: | | |
| ONE_DAY_AGO=$(date -u -d '24 hours ago' +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -v-24H +%Y-%m-%dT%H:%M:%SZ) | |
| ALERTS=$(gh api repos/${{ github.repository }}/dependabot/alerts?state=open\&per_page=100) | |
| if [ $? -ne 0 ]; then | |
| echo "Failed to fetch alerts" | |
| exit 1 | |
| fi | |
| echo "$JQ_FILTER_B64" | base64 -d > /tmp/filter.jq | |
| MESSAGE=$(echo "$ALERTS" | jq -r --arg cutoff "$ONE_DAY_AGO" -f /tmp/filter.jq) | |
| if [ -z "$MESSAGE" ]; then | |
| echo "No new actionable alerts in the last 24 hours." | |
| exit 0 | |
| fi | |
| echo "Sending Slack notification..." | |
| echo "$MESSAGE" | |
| PAYLOAD=$(jq -n --arg text "$MESSAGE" '{text: $text}') | |
| curl -s -X POST "$SLACK_WEBHOOK_URL" \ | |
| -H 'Content-Type: application/json' \ | |
| -d "$PAYLOAD" | |
| echo "" | |
| echo "Slack notification sent." | |
| check-upstream-advisories: | |
| if: github.repository == 'aws/aws-sdk-java-v2' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 3 | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| sparse-checkout: pom.xml | |
| sparse-checkout-cone-mode: false | |
| - name: Check for new CVEs in upstream dependencies | |
| env: | |
| GH_TOKEN: ${{ secrets.DEPENDABOT_ALERTS_TOKEN }} | |
| SLACK_WEBHOOK_URL: ${{ secrets.CI_SLACK_WEBHOOK_URL }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| ALERTS="" | |
| NOW=$(date -u +%s) | |
| # Check for CVEs published in the last 24 hours | |
| ONE_DAY_AGO=$(date -u -d '24 hours ago' +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -v-24H +%Y-%m-%dT%H:%M:%SZ) | |
| # ============================================================ | |
| # SDK versions from pom.xml | |
| # ============================================================ | |
| NETTY_VERSION=$(grep -oP '(?<=<netty.version>)[^<]+' pom.xml | sed 's/.Final//') | |
| NETTY_PATCH=$(echo "$NETTY_VERSION" | grep -oP '\d+$') | |
| NETTY_MAJOR_MINOR=$(echo "$NETTY_VERSION" | grep -oP '^\d+\.\d+') | |
| JACKSON_VERSION=$(grep -oP '(?<=<jackson.version>)[^<]+' pom.xml) | |
| JACKSON_DATABIND_VERSION=$(grep -oP '(?<=<jackson.databind.version>)[^<]+' pom.xml) | |
| HTTPCLIENT5_VERSION=$(grep -oP '(?<=<httpcomponents.client5.version>)[^<]+' pom.xml) | |
| HTTPCORE5_VERSION=$(grep -oP '(?<=<httpcomponents.core5.version>)[^<]+' pom.xml) | |
| echo "SDK versions:" | |
| echo " Netty: $NETTY_VERSION" | |
| echo " Jackson: $JACKSON_VERSION (databind: $JACKSON_DATABIND_VERSION)" | |
| echo " httpclient5: $HTTPCLIENT5_VERSION" | |
| echo " httpcore5: $HTTPCORE5_VERSION" | |
| # ============================================================ | |
| # NETTY: Check repo-level security advisories | |
| # ============================================================ | |
| echo "" | |
| echo "=== Checking Netty repo advisories ===" | |
| SDK_NETTY_PACKAGES=( | |
| "io.netty:netty-codec-http" | |
| "io.netty:netty-codec-http2" | |
| "io.netty:netty-codec" | |
| "io.netty:netty-transport" | |
| "io.netty:netty-transport-native-epoll" | |
| "io.netty:netty-transport-classes-epoll" | |
| "io.netty:netty-common" | |
| "io.netty:netty-buffer" | |
| "io.netty:netty-handler" | |
| "io.netty:netty-resolver" | |
| "io.netty:netty-resolver-dns" | |
| ) | |
| NETTY_ADVISORIES=$(gh api "repos/netty/netty/security-advisories?sort=published&direction=desc&per_page=30") | |
| NETTY_NEW=$(echo "$NETTY_ADVISORIES" | jq --arg cutoff "$ONE_DAY_AGO" --arg major_minor "$NETTY_MAJOR_MINOR" --arg sdk_patch "$NETTY_PATCH" \ | |
| --argjson sdk_packages "$(printf '%s\n' "${SDK_NETTY_PACKAGES[@]}" | jq -R . | jq -s .)" \ | |
| '[.[] | | |
| select(.published_at > $cutoff) | | |
| select([.vulnerabilities[].package.name] | any(. as $pkg | $sdk_packages | index($pkg))) | | |
| select(.vulnerabilities[] | | |
| select(.package.name as $pkg | $sdk_packages | index($pkg)) | | |
| (.patched_versions | capture("(?<v>" + $major_minor + "\\.(?<p>[0-9]+))") | (.p | tonumber) > ($sdk_patch | tonumber)) | |
| ) | | |
| {ghsa_id, severity, summary, | |
| patched: ([.vulnerabilities[] | select(.package.name as $pkg | $sdk_packages | index($pkg)) | .patched_versions | capture("(?<v>" + $major_minor + "\\.(?<p>[0-9]+))").v] | max), | |
| packages: [.vulnerabilities[] | select(.package.name as $pkg | $sdk_packages | index($pkg)) | .package.name] | unique} | |
| ]') | |
| NETTY_COUNT=$(echo "$NETTY_NEW" | jq 'length') | |
| echo "Found $NETTY_COUNT new Netty advisories affecting SDK (published in last 24h)" | |
| if [ "$NETTY_COUNT" -gt 0 ]; then | |
| NETTY_MSG=$(echo "$NETTY_NEW" | jq -r ' | |
| (map(.patched) | max) as $max_patched | | |
| "⚠️ *" + (length | tostring) + " Netty CVE(s) detected via <https://github.com/netty/netty/security/advisories|upstream repo advisory>*\n\n" + | |
| (map(" • <https://github.com/netty/netty/security/advisories/\(.ghsa_id)|\(.ghsa_id)> (\(.severity | ascii_upcase)) — \(.packages | join(", "))\n \(.summary | split(". ") | first | if length > 150 then .[:147] + "..." else . end)") | join("\n")) + | |
| "\n\n*Action needed*: Upgrade Netty from `'"$NETTY_VERSION"'.Final` → `" + $max_patched + ".Final`" | |
| ') | |
| ALERTS="${ALERTS}${NETTY_MSG}\n\n" | |
| fi | |
| # ============================================================ | |
| # JACKSON: Check repo-level security advisories | |
| # ============================================================ | |
| echo "" | |
| echo "=== Checking Jackson repo advisories ===" | |
| JACKSON_ALERTS="" | |
| for REPO in "FasterXML/jackson-databind" "FasterXML/jackson-core" "FasterXML/jackson-dataformats-binary"; do | |
| PKG_NAME=$(echo "$REPO" | sed 's|FasterXML/||') | |
| if [ "$PKG_NAME" = "jackson-databind" ]; then | |
| SDK_VER="$JACKSON_DATABIND_VERSION" | |
| else | |
| SDK_VER="$JACKSON_VERSION" | |
| fi | |
| SDK_MINOR=$(echo "$SDK_VER" | grep -oP '^\d+\.\d+') | |
| SDK_PATCH=$(echo "$SDK_VER" | grep -oP '\d+$') | |
| ADVISORIES=$(gh api "repos/$REPO/security-advisories?sort=published&direction=desc&per_page=10") | |
| NEW_ADVISORIES=$(echo "$ADVISORIES" | jq --arg cutoff "$ONE_DAY_AGO" --arg sdk_minor "$SDK_MINOR" --arg sdk_patch "$SDK_PATCH" --arg sdk_ver "$SDK_VER" \ | |
| '[.[] | | |
| select(.published_at > $cutoff) | | |
| # Find vulnerabilities that match our version line AND where SDK is in the affected range | |
| select([.vulnerabilities[] | | |
| select(.package.name | startswith("com.fasterxml.jackson")) | | |
| select(.patched_versions != null and .patched_versions != "") | | |
| select(.vulnerable_version_range | test($sdk_minor)) | | |
| select( | |
| (.patched_versions // "") | | |
| if . == "" then | |
| false | |
| else | |
| capture("(?<v>" + $sdk_minor + "\\.(?<p>[0-9]+))") | | |
| (.p | tonumber) > ($sdk_patch | tonumber) | |
| end | |
| ) | |
| ] | length > 0) | | |
| { | |
| ghsa_id, | |
| severity, | |
| summary, | |
| patched: ([.vulnerabilities[] | | |
| select(.package.name | startswith("com.fasterxml.jackson")) | | |
| select(.patched_versions != null and .patched_versions != "") | | |
| select(.vulnerable_version_range | test($sdk_minor)) | | |
| .patched_versions // "" | | |
| capture("(?<v>" + $sdk_minor + "\\.(?<p>[0-9]+))").v // null | |
| ] | map(select(. != null)) | max // "unknown") | |
| } | |
| ]') | |
| COUNT=$(echo "$NEW_ADVISORIES" | jq 'length') | |
| echo " $REPO: $COUNT new advisories (last 24h, affecting SDK)" | |
| if [ "$COUNT" -gt 0 ]; then | |
| MAX_PATCHED=$(echo "$NEW_ADVISORIES" | jq -r '[.[].patched] | map(select(. != "unknown")) | max // "latest"') | |
| JACKSON_MSG=$(echo "$NEW_ADVISORIES" | jq -r --arg repo "$REPO" --arg max_patched "$MAX_PATCHED" ' | |
| "⚠️ *" + (length | tostring) + " '"$PKG_NAME"' CVE(s) detected via <https://github.com/" + $repo + "/security/advisories|upstream repo advisory>*\n\n" + | |
| (map(" • <https://github.com/" + $repo + "/security/advisories/\(.ghsa_id)|\(.ghsa_id)> (\(.severity | ascii_upcase)) — \(.summary | split(". ") | first | if length > 150 then .[:147] + "..." else . end)") | join("\n")) + | |
| "\n\n*Action needed*: Upgrade '"$PKG_NAME"' from `'"$SDK_VER"'` → `" + $max_patched + "`" | |
| ') | |
| ALERTS="${ALERTS}${JACKSON_MSG}\n\n" | |
| fi | |
| done | |
| # ============================================================ | |
| # APACHE: Check NVD for new CVEs, get details from cve.org | |
| # ============================================================ | |
| echo "" | |
| echo "=== Checking Apache httpcomponents via NVD ===" | |
| NVD_RESULTS=$(curl -s "https://services.nvd.nist.gov/rest/json/cves/2.0?keywordSearch=httpcomponents&pubStartDate=${ONE_DAY_AGO}&pubEndDate=$(date -u +%Y-%m-%dT%H:%M:%S.000)&resultsPerPage=10") | |
| APACHE_CVES=$(echo "$NVD_RESULTS" | jq -r '.vulnerabilities[]?.cve.id // empty') | |
| if [ -z "$APACHE_CVES" ]; then | |
| echo " No new Apache httpcomponents CVEs in last 24h" | |
| else | |
| APACHE_ALERTS="" | |
| APACHE_ACTION="" | |
| while IFS= read -r CVE_ID; do | |
| echo " Checking $CVE_ID..." | |
| # Get structured data from cve.org | |
| CVE_DATA=$(curl -s "https://cveawg.mitre.org/api/cve/$CVE_ID") | |
| # Extract affected package and version | |
| PACKAGE=$(echo "$CVE_DATA" | jq -r '.containers.cna.affected[0].packageName // .containers.cna.affected[0].product // "unknown"') | |
| # Check if this affects SDK's versions | |
| AFFECTS_SDK=false | |
| FIX_VERSION="" | |
| DESCRIPTION_FULL=$(echo "$CVE_DATA" | jq -r '.containers.cna.descriptions[0].value // ""') | |
| DESCRIPTION=$(echo "$DESCRIPTION_FULL" | tr '\n' ' ' | sed 's/ */ /g' | sed 's/\. /.\n/' | head -1) | |
| if [ ${#DESCRIPTION} -gt 150 ]; then | |
| DESCRIPTION="${DESCRIPTION:0:147}..." | |
| fi | |
| # Try to extract fix version from description | |
| FIX_FROM_DESC=$(echo "$DESCRIPTION_FULL" | grep -oP 'upgrade to (?:at least )?version \K[0-9.]+' || echo "") | |
| # Determine SDK current version based on package | |
| SDK_CURRENT="" | |
| if echo "$PACKAGE" | grep -q "httpclient5"; then | |
| SDK_CURRENT="$HTTPCLIENT5_VERSION" | |
| elif echo "$PACKAGE" | grep -q "httpcore5"; then | |
| SDK_CURRENT="$HTTPCORE5_VERSION" | |
| else | |
| continue | |
| fi | |
| # Check ALL version entries — SDK is affected if its version <= lessThanOrEqual or < lessThan | |
| VERSIONS_COUNT=$(echo "$CVE_DATA" | jq '.containers.cna.affected[0].versions | length') | |
| SDK_MAJOR_MINOR_APACHE=$(echo "$SDK_CURRENT" | sed 's/\.[0-9]*$//') | |
| for i in $(seq 0 $((VERSIONS_COUNT - 1))); do | |
| LESS_THAN=$(echo "$CVE_DATA" | jq -r ".containers.cna.affected[0].versions[$i].lessThan // \"\"") | |
| LESS_THAN_EQ=$(echo "$CVE_DATA" | jq -r ".containers.cna.affected[0].versions[$i].lessThanOrEqual // \"\"") | |
| VER_BOUND="${LESS_THAN_EQ:-$LESS_THAN}" | |
| # Skip entries for different version lines (alpha/beta entries that don't match our major.minor) | |
| if echo "$VER_BOUND" | grep -q "alpha\|beta"; then | |
| continue | |
| fi | |
| # Skip if the bound doesn't start with our major.minor | |
| if ! echo "$VER_BOUND" | grep -q "^${SDK_MAJOR_MINOR_APACHE}\."; then | |
| continue | |
| fi | |
| if [ -n "$LESS_THAN" ]; then | |
| # Affected if SDK_CURRENT < LESS_THAN | |
| if [ "$(printf '%s\n' "$SDK_CURRENT" "$LESS_THAN" | sort -V | head -1)" = "$SDK_CURRENT" ] && [ "$SDK_CURRENT" != "$LESS_THAN" ]; then | |
| AFFECTS_SDK=true | |
| FIX_VERSION="${FIX_FROM_DESC:-$LESS_THAN}" | |
| break | |
| fi | |
| elif [ -n "$LESS_THAN_EQ" ]; then | |
| # Affected if SDK_CURRENT <= LESS_THAN_EQ | |
| if [ "$(printf '%s\n' "$SDK_CURRENT" "$LESS_THAN_EQ" | sort -V | head -1)" = "$SDK_CURRENT" ]; then | |
| AFFECTS_SDK=true | |
| FIX_VERSION="${FIX_FROM_DESC:-"> $LESS_THAN_EQ"}" | |
| break | |
| fi | |
| fi | |
| done | |
| if [ "$AFFECTS_SDK" = true ]; then | |
| APACHE_ALERTS="${APACHE_ALERTS} • <https://nvd.nist.gov/vuln/detail/$CVE_ID|$CVE_ID> — $PACKAGE\n $DESCRIPTION\n" | |
| APACHE_ACTION="*Action needed*: Upgrade \`$PACKAGE\` from \`$SDK_CURRENT\` → \`$FIX_VERSION\`" | |
| fi | |
| done <<< "$APACHE_CVES" | |
| if [ -n "$APACHE_ALERTS" ]; then | |
| APACHE_COUNT=$(echo -e "$APACHE_ALERTS" | grep -c "•") | |
| APACHE_MSG="⚠️ *${APACHE_COUNT} Apache HttpComponents CVE(s) detected via <https://nvd.nist.gov/vuln/search|NVD>*\n\n${APACHE_ALERTS}\n${APACHE_ACTION}" | |
| ALERTS="${ALERTS}${APACHE_MSG}\n\n" | |
| fi | |
| fi | |
| # ============================================================ | |
| # Send Slack notification if any alerts found | |
| # ============================================================ | |
| echo "" | |
| if [ -z "$ALERTS" ]; then | |
| echo "No new CVEs found across all upstream sources." | |
| exit 0 | |
| fi | |
| echo "Sending Slack notification..." | |
| FINAL_MESSAGE=$(echo -e "$ALERTS") | |
| echo "$FINAL_MESSAGE" | |
| if [ -n "$SLACK_WEBHOOK_URL" ]; then | |
| PAYLOAD=$(jq -n --arg text "$FINAL_MESSAGE" '{text: $text}') | |
| curl -s -X POST "$SLACK_WEBHOOK_URL" \ | |
| -H 'Content-Type: application/json' \ | |
| -d "$PAYLOAD" | |
| echo "" | |
| echo "Slack notification sent." | |
| else | |
| echo "No SLACK_WEBHOOK_URL configured. Skipping notification." | |
| fi |