diff --git a/.github/workflows/container-scan.yml b/.github/workflows/container-scan.yml index ee48c382..13bbbfdd 100644 --- a/.github/workflows/container-scan.yml +++ b/.github/workflows/container-scan.yml @@ -26,7 +26,7 @@ jobs: GATE_FAIL_THRESHOLD: "8.0" GATE_WARN_THRESHOLD: "5.0" - # SAST /Linting + # SAST / Linting SEMGREP_CONFIG_RULESETS: >- semgrep-rules/dockerfile OPENGREP_SAST_SARIF_OUTPUT: sast-opengrep-dockerfile.sarif @@ -37,7 +37,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Set up Python - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 #v6.2.0 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: # renovate: datasource=python-version depName=python python-version: '3.14.4' @@ -54,52 +54,52 @@ jobs: run: python ci/container_scan.py --scan-type sast - name: Run SCA scanning - if: always() + if: ${{ !cancelled() }} run: python ci/container_scan.py --scan-type sca --image ${{ env.IMAGE_NAME }} - - name: Upload Trivy SARIF to GitHub Security tab - id: upload_trivy - if: always() - uses: github/codeql-action/upload-sarif@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v4.31.2 - with: - sarif_file: ${{ env.TRIVY_SCA_SARIF_OUTPUT }} - category: trivy-container-scanning - - - name: Upload OSV Scanner SARIF to GitHub Security tab - id: upload_osv - if: always() - uses: github/codeql-action/upload-sarif@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v4.31.2 - with: - sarif_file: ${{ env.OSV_SCA_SARIF_OUTPUT }} - category: osv-scanner-container-scanning - - - name: Upload OpenGrep SARIF to GitHub Security tab - id: upload_opengrep - if: always() - uses: github/codeql-action/upload-sarif@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v4.31.2 - with: - sarif_file: ${{ env.OPENGREP_SAST_SARIF_OUTPUT }} - category: opengrep-sast - - - name: Upload Hadolint SARIF to GitHub Security tab - id: upload_hadolint - if: always() - uses: github/codeql-action/upload-sarif@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v4.31.2 - with: - sarif_file: ${{ env.HADOLINT_SAST_SARIF_OUTPUT }} - category: hadolint-sast - - name: Merge all SARIF reports - if: always() + if: ${{ !cancelled() }} run: | python ci/container_scan.py \ --merge-sarif "${{ env.TRIVY_SCA_SARIF_OUTPUT }}" "${{ env.OSV_SCA_SARIF_OUTPUT }}" "${{ env.OPENGREP_SAST_SARIF_OUTPUT }}" "${{ env.HADOLINT_SAST_SARIF_OUTPUT }}" \ --merge-output "${{ env.CONTAINER_SCAN_MERGED_SARIF_OUTPUT }}" - - name: Upload SARIF artifacts - if: always() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a + - name: Upload merged SARIF to GitHub Security tab + if: ${{ !cancelled() && hashFiles(env.CONTAINER_SCAN_MERGED_SARIF_OUTPUT) != '' }} + uses: github/codeql-action/upload-sarif@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v4.31.2 + with: + sarif_file: ${{ env.CONTAINER_SCAN_MERGED_SARIF_OUTPUT }} + category: container-scan + + # Artifact downloads are gated only by repo read access, and GitHub has no + # per-artifact permissions. Encrypt before upload, and delete the plaintext + # so no later step can publish it. + - name: Encrypt SARIF report + id: encrypt + if: ${{ !cancelled() && hashFiles(env.CONTAINER_SCAN_MERGED_SARIF_OUTPUT) != '' }} + env: + PW: ${{ secrets.ARTIFACT_PASSWORD }} + OUT: ${{ env.CONTAINER_SCAN_MERGED_SARIF_OUTPUT }}.gpg + run: | + if [ -z "$PW" ]; then + echo "::warning title=SARIF report not uploaded::ARTIFACT_PASSWORD is not available in this run (Dependabot, Renovate or fork PR). Skipping encryption and upload." + rm -f -- "$CONTAINER_SCAN_MERGED_SARIF_OUTPUT" + exit 0 + fi + printf '%s' "$PW" | gpg --symmetric --batch --yes --quiet \ + --pinentry-mode loopback --passphrase-fd 0 \ + --no-symkey-cache \ + --cipher-algo AES256 \ + --s2k-mode 3 --s2k-digest-algo SHA512 --s2k-count 65011712 \ + --output "$OUT" -- "$CONTAINER_SCAN_MERGED_SARIF_OUTPUT" + rm -f -- "$CONTAINER_SCAN_MERGED_SARIF_OUTPUT" + echo "encrypted=$OUT" >> "$GITHUB_OUTPUT" + + - name: Upload encrypted SARIF artifact + if: ${{ !cancelled() && steps.encrypt.outputs.encrypted != '' }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: container-scan-sarif-report - path: ${{ env.CONTAINER_SCAN_MERGED_SARIF_OUTPUT }} + path: ${{ steps.encrypt.outputs.encrypted }} retention-days: 30 + if-no-files-found: error diff --git a/.github/workflows/sast.yml b/.github/workflows/sast.yml index d086bb2b..ff3f5623 100644 --- a/.github/workflows/sast.yml +++ b/.github/workflows/sast.yml @@ -27,7 +27,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Set up Python - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 #v6.2.0 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: # renovate: datasource=python-version depName=python python-version: '3.14.4' @@ -38,18 +38,39 @@ jobs: - name: Run SAST scanning run: python ci/sast_scan.py - - name: Upload Semgrep SARIF to GitHub Security tab - id: upload_semgrep - if: always() + - name: Upload SARIF to GitHub Security tab + if: ${{ !cancelled() && hashFiles(env.OPENGREP_SARIF_OUTPUT) != '' }} uses: github/codeql-action/upload-sarif@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v4.31.2 with: sarif_file: ${{ env.OPENGREP_SARIF_OUTPUT }} category: semgrep-app - - name: Upload SARIF artifact - if: always() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a #v7.0.1 + - name: Encrypt SARIF report + id: encrypt + if: ${{ !cancelled() && hashFiles(env.OPENGREP_SARIF_OUTPUT) != '' }} + env: + PW: ${{ secrets.ARTIFACT_PASSWORD }} + OUT: ${{ env.OPENGREP_SARIF_OUTPUT }}.gpg + run: | + if [ -z "$PW" ]; then + echo "::warning title=SARIF report not uploaded::ARTIFACT_PASSWORD is not available in this run (Dependabot, Renovate or fork PR). Skipping encryption and upload." + rm -f -- "$OPENGREP_SARIF_OUTPUT" + exit 0 + fi + printf '%s' "$PW" | gpg --symmetric --batch --yes --quiet \ + --pinentry-mode loopback --passphrase-fd 0 \ + --no-symkey-cache \ + --cipher-algo AES256 \ + --s2k-mode 3 --s2k-digest-algo SHA512 --s2k-count 65011712 \ + --output "$OUT" -- "$OPENGREP_SARIF_OUTPUT" + rm -f -- "$OPENGREP_SARIF_OUTPUT" + echo "encrypted=$OUT" >> "$GITHUB_OUTPUT" + + - name: Upload encrypted SARIF artifact + if: ${{ !cancelled() && steps.encrypt.outputs.encrypted != '' }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: sast-scan-sarif-report - path: ${{ env.OPENGREP_SARIF_OUTPUT }} + path: ${{ steps.encrypt.outputs.encrypted }} retention-days: 30 + if-no-files-found: error diff --git a/.github/workflows/sca.yml b/.github/workflows/sca.yml index 96dd3740..050fcf1c 100644 --- a/.github/workflows/sca.yml +++ b/.github/workflows/sca.yml @@ -29,13 +29,13 @@ jobs: uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Set up Python - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 #v6.3.0 + uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 with: # renovate: datasource=python-version depName=python python-version: '3.14.4' - name: Cache npm packages - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 #v6.1.0 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: ~/.npm key: ${{ runner.os }}-npm-v1-${{ hashFiles('**/package-lock.json') }} @@ -44,29 +44,43 @@ jobs: - name: Setup tools and generate SBOM run: bash ci/setup-tools.sh --install-tool trivy,osv-scanner --sbom-ecosystem npm + - name: Run SCA tools run: python ci/sca_scan.py - - name: Upload Trivy SARIF to GitHub Security tab - id: upload_trivy - if: always() + - name: Upload merged SARIF to GitHub Security tab + if: ${{ !cancelled() && hashFiles(env.SCA_MERGED_SARIF_OUTPUT) != '' }} uses: github/codeql-action/upload-sarif@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v4.31.2 with: - sarif_file: ${{ env.TRIVY_SARIF_OUTPUT }} - category: trivy-app + sarif_file: ${{ env.SCA_MERGED_SARIF_OUTPUT }} + category: sca-app - - name: Upload OSV Scanner SARIF to GitHub Security tab - id: upload_osv - if: always() - uses: github/codeql-action/upload-sarif@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v4.31.2 - with: - sarif_file: ${{ env.OSV_SARIF_OUTPUT }} - category: osv-scanner-app + - name: Encrypt SARIF report + id: encrypt + if: ${{ !cancelled() && hashFiles(env.SCA_MERGED_SARIF_OUTPUT) != '' }} + env: + PW: ${{ secrets.ARTIFACT_PASSWORD }} + OUT: ${{ env.SCA_MERGED_SARIF_OUTPUT }}.gpg + run: | + if [ -z "$PW" ]; then + echo "::warning title=SARIF report not uploaded::ARTIFACT_PASSWORD is not available in this run (Dependabot, Renovate or fork PR). Skipping encryption and upload." + rm -f -- "$SCA_MERGED_SARIF_OUTPUT" + exit 0 + fi + printf '%s' "$PW" | gpg --symmetric --batch --yes --quiet \ + --pinentry-mode loopback --passphrase-fd 0 \ + --no-symkey-cache \ + --cipher-algo AES256 \ + --s2k-mode 3 --s2k-digest-algo SHA512 --s2k-count 65011712 \ + --output "$OUT" -- "$SCA_MERGED_SARIF_OUTPUT" + rm -f -- "$SCA_MERGED_SARIF_OUTPUT" + echo "encrypted=$OUT" >> "$GITHUB_OUTPUT" - - name: Upload SARIF artifacts - if: always() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a #v7.0.1 + - name: Upload encrypted SARIF artifact + if: ${{ !cancelled() && steps.encrypt.outputs.encrypted != '' }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: sca-scan-sarif-report - path: ${{ env.SCA_MERGED_SARIF_OUTPUT }} + path: ${{ steps.encrypt.outputs.encrypted }} retention-days: 30 + if-no-files-found: error diff --git a/package-lock.json b/package-lock.json index d38abaa0..21448526 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9080,20 +9080,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/image-size": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", - "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", - "dev": true, - "license": "MIT", - "optional": true, - "bin": { - "image-size": "bin/image-size.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/immutable": { "version": "5.1.9", "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.9.tgz", @@ -10052,11 +10038,10 @@ } }, "node_modules/less": { - "version": "4.6.4", - "resolved": "https://registry.npmjs.org/less/-/less-4.6.4.tgz", - "integrity": "sha512-OJmO5+HxZLLw0RLzkqaNHzcgEAQG7C0y3aMbwtCzIUFZsLMNNq/1IdAdHEycQ58CwUO3jPTHmoN+tE5I7FQxNg==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/less/-/less-4.9.1.tgz", + "integrity": "sha512-orp15PfJvvNDIqJdVWzMI9Sjpjp3VTiw3sfvbB+67LlISTEn8uVT2EdYSuyl02BLvaftv6sdk9Umnxmm5rckmg==", "dev": true, - "license": "Apache-2.0", "dependencies": { "copy-anything": "^3.0.5", "parse-node-version": "^1.0.1" @@ -10070,10 +10055,10 @@ "optionalDependencies": { "errno": "^0.1.1", "graceful-fs": "^4.1.2", - "image-size": "~0.5.0", - "make-dir": "^2.1.0", + "make-dir": "^5.1.0", "mime": "^1.4.1", "needle": "^3.1.0", + "probe-image-size": "^7.2.3", "source-map": "~0.6.0" } }, @@ -10105,18 +10090,16 @@ } }, "node_modules/less/node_modules/make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-5.1.0.tgz", + "integrity": "sha512-IfpFq6UM39dUNiphpA6uDezNx/AvWyhwfICWPR3t1VspkgkMZrL+Rk1RbN1bx+aeNYwOrqGJgEgV3yotk+ZUVw==", "dev": true, - "license": "MIT", "optional": true, - "dependencies": { - "pify": "^4.0.1", - "semver": "^5.6.0" - }, "engines": { - "node": ">=6" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/less/node_modules/mime": { @@ -10133,17 +10116,6 @@ "node": ">=4" } }, - "node_modules/less/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "license": "ISC", - "optional": true, - "bin": { - "semver": "bin/semver" - } - }, "node_modules/less/node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -10287,6 +10259,13 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "optional": true + }, "node_modules/log-symbols": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-7.0.1.tgz", @@ -11709,17 +11688,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=6" - } - }, "node_modules/piscina": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/piscina/-/piscina-5.2.0.tgz", @@ -11952,6 +11920,69 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/probe-image-size": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/probe-image-size/-/probe-image-size-7.4.0.tgz", + "integrity": "sha512-cdEprVtZxV+awMde9X+4jILBFYh4CARxVrQaMl4wY4YcPWbul9jntXrIW95NInBDyJwcVUP3U0T6yukN8rMBaQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "optional": true, + "dependencies": { + "lodash.merge": "^4.6.2", + "needle": "^2.5.2", + "stream-parser": "~0.3.1" + } + }, + "node_modules/probe-image-size/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "optional": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/probe-image-size/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/probe-image-size/node_modules/needle": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.9.1.tgz", + "integrity": "sha512-6R9fqJ5Zcmf+uYaFgdIHmLwNldn5HbK8L5ybn7Uz+ylX/rnOsSp1AHcvQSrCaFN+qNM1wpymHqD7mVasEOlHGQ==", + "dev": true, + "optional": true, + "dependencies": { + "debug": "^3.2.6", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 4.4.x" + } + }, "node_modules/proc-log": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", @@ -13346,6 +13377,33 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/stream-parser": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/stream-parser/-/stream-parser-0.3.1.tgz", + "integrity": "sha512-bJ/HgKq41nlKvlhccD5kaCr/P+Hu0wPNKPJOH7en+YrJu/9EgqUF+88w5Jb6KNcjOFMhfX4B2asfeAtIGuHObQ==", + "dev": true, + "optional": true, + "dependencies": { + "debug": "2" + } + }, + "node_modules/stream-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "optional": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/stream-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "optional": true + }, "node_modules/streamroller": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz", diff --git a/package.json b/package.json index 2a34dd66..5b712693 100644 --- a/package.json +++ b/package.json @@ -87,6 +87,7 @@ "ws": "8.21.0", "zrender": "6.1.0", "ip-address": "10.3.1", - "socket.io-parser": "4.2.7" + "socket.io-parser": "4.2.7", + "less": "^4.9.0" } }