From d7f2c482f3a0cb0e69813cae21c188893c11ba2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20S=C3=A1zel?= Date: Fri, 31 Jul 2026 21:08:58 +0200 Subject: [PATCH] [CI] add OWASP dependency-check plugin + CI job for known-vulnerability scanning (#16) --- .github/workflows/build.yml | 84 +++++++++++++++++++++++++ build.gradle.kts | 11 ++++ gradle/dependency-check/suppression.xml | 5 ++ gradle/libs.versions.toml | 2 + 4 files changed, 102 insertions(+) create mode 100644 gradle/dependency-check/suppression.xml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index dc25af3..8ceabbf 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -58,6 +58,90 @@ jobs: PACKAGE_VERSION: ${{ github.event.release.tag_name }} run: ./gradlew build + dependency-check: + + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + + steps: + - uses: actions/checkout@v4 + - name: set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'adopt' + cache: gradle + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + - name: Get cache week + id: date + run: echo "week=$(date -u +%Y-W%U)" >> "$GITHUB_OUTPUT" + + - name: Cache dependency-check NVD data + uses: actions/cache@v4 + with: + path: ~/.gradle/dependency-check-data + key: dependency-check-data-${{ steps.date.outputs.week }} + restore-keys: dependency-check-data- + + - name: Run OWASP dependency-check + id: analyze + continue-on-error: true + env: + NVD_API_KEY: ${{ secrets.NVD_API_KEY }} + run: ./gradlew dependencyCheckAnalyze + + - name: Upload dependency-check report + if: always() + uses: actions/upload-artifact@v4 + with: + name: dependency-check-report + path: build/reports/dependency-check-report.html + + - name: Build Markdown summary + if: always() + run: | + REPORT=build/reports/dependency-check-report.json + # Prefer NPM/GHSA scoring over NVD for npm deps, NVD often over-scores JS-ecosystem CVEs. + FINDINGS=' + def score: (.cvssv3.baseScore // .cvssv2.score // 0); + def relevant($d): ($d.vulnerabilities | any(.source == "NPM")); + [ .dependencies[]? | select(.vulnerabilities != null) as $d + | $d.vulnerabilities[] + | select(if (relevant($d)) then .source == "NPM" else true end) + | select(score >= 7) + | {fileName: $d.fileName, name, severity: (.severity // "n/a"), cvss: score, source: (.source // "n/a")} + ] + ' + { + echo "### OWASP Dependency-Check results" + echo + echo "_Showing HIGH/CRITICAL findings (CVSS >= 7), GHSA-preferred over NVD for npm deps. Full report in the workflow artifact._" + echo + COUNT=$(jq "$FINDINGS | length" "$REPORT") + if [ "$COUNT" -eq 0 ]; then + echo "No HIGH/CRITICAL vulnerabilities found." + else + echo "| Dependency | CVE | Severity | CVSS | Source |" + echo "|---|---|---|---|---|" + jq -r "$FINDINGS"' | .[] | "| \(.fileName) | \(.name) | \(.severity) | \(.cvss) | \(.source) |"' "$REPORT" + fi + } > dependency-check-summary.md + + - name: Comment PR with results + if: always() && github.event_name == 'pull_request' + uses: marocchino/sticky-pull-request-comment@v2 + with: + header: dependency-check + path: dependency-check-summary.md + + - name: Fail on vulnerabilities above threshold + if: steps.analyze.outcome == 'failure' + run: exit 1 + release: if: ${{ github.event_name == 'release' }} runs-on: ubuntu-latest diff --git a/build.gradle.kts b/build.gradle.kts index 9e7cc27..ce92bde 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -12,6 +12,7 @@ plugins { alias(libs.plugins.kover) alias(libs.plugins.maven.publish) alias(libs.plugins.sqldelight) + alias(libs.plugins.owasp.dependencycheck) id("signing") } @@ -211,3 +212,13 @@ detekt { html.required.set(true) // observe findings in your browser with structure and code snippets } } + +dependencyCheck { + failBuildOnCVSS = 7.0f // fail on high/critical CVEs, don't block on low/medium noise + formats = listOf("HTML", "JSON") // JSON used to build the PR comment summary + suppressionFile = "$projectDir/gradle/dependency-check/suppression.xml" + scanSet.setFrom(file("$projectDir/kotlin-js-store/yarn.lock")) // npm sqlite3 dependency lockfile + nvd { + apiKey = System.getenv("NVD_API_KEY") ?: localProperties["nvd.apiKey"] as String? + } +} diff --git a/gradle/dependency-check/suppression.xml b/gradle/dependency-check/suppression.xml new file mode 100644 index 0000000..7327181 --- /dev/null +++ b/gradle/dependency-check/suppression.xml @@ -0,0 +1,5 @@ + + + + diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5721828..d9aa776 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,6 +7,7 @@ kover = "0.9.9" node-sqlite3 = "6.0.1" node-js = "24.18.1" maven-publish = "0.37.0" +owasp-dependencycheck = "12.2.2" sqldelight = "2.3.2" [libraries] @@ -22,3 +23,4 @@ kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" } sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" } maven-publish = { id = "com.vanniktech.maven.publish", version.ref = "maven-publish" } +owasp-dependencycheck = { id = "org.owasp.dependencycheck", version.ref = "owasp-dependencycheck" }