diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..6ce36fc --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,28 @@ +version: 2 +# Dependabot keeps dependencies and GitHub Actions current. +# cooldown.default-days: 7 => only propose versions that have been public +# for at least 7 days, mitigating malicious just-published releases. +# Note: cooldowns delay version updates, but they do not delay security updates +# (vulnerability fixes still open immediately). +updates: + - package-ecosystem: npm + directory: "/" + schedule: + interval: weekly + timezone: Europe/Berlin + cooldown: + default-days: 7 + - package-ecosystem: github-actions + directory: "/" + schedule: + interval: weekly + timezone: Europe/Berlin + cooldown: + default-days: 7 + - package-ecosystem: docker + directory: "/" + schedule: + interval: weekly + timezone: Europe/Berlin + cooldown: + default-days: 7 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a6d55f1..2f66c79 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,12 +17,14 @@ jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - uses: actions/checkout@v7 + with: + persist-credentials: false + - uses: actions/setup-node@v7 with: node-version: '24.19.0' - name: Cache node_modules - uses: actions/cache@v3 + uses: actions/cache@v6 env: cache-name: cache-node-modules with: @@ -35,7 +37,9 @@ jobs: dockertest: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 + with: + persist-credentials: false - run: docker build -t 'ocean-node-bootstrap:mybuild' . build: @@ -48,12 +52,14 @@ jobs: node: ['24.19.0'] steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - uses: actions/checkout@v7 + with: + persist-credentials: false + - uses: actions/setup-node@v7 with: node-version: ${{ matrix.node }} - name: Cache node_modules - uses: actions/cache@v3 + uses: actions/cache@v6 env: cache-name: cache-node-modules with: diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index a26f8b3..e8720b0 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -14,6 +14,9 @@ env: DOCKERHUB_IMAGE: ${{ 'oceanprotocol/ocean-node-bootstrap' }} GHCR_IMAGE: ${{ 'ghcr.io/oceanprotocol/ocean-node-bootstrap' }} +permissions: + contents: read + jobs: build: runs-on: ubuntu-latest @@ -29,15 +32,17 @@ jobs: platform=${{ matrix.platform }} echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 + with: + persist-credentials: false - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@v4 with: platforms: ${{ matrix.platform }} #image: tonistiigi/binfmt:qemu-v8.0.4 - name: Set up Docker Buildx id: buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v4 with: platforms: ${{ matrix.platform }} - name: Login to Docker Hub @@ -46,7 +51,7 @@ jobs: DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_PUSH_USERNAME }} DOCKERHUB_TOKEN: ${{ secrets.DOCKER_PUSH_TOKEN }} if: env.DOCKERHUB_USERNAME != '' && env.DOCKERHUB_TOKEN != '' - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: username: ${{ secrets.DOCKERHUB_PUSH_USERNAME }} password: ${{ secrets.DOCKER_PUSH_TOKEN }} @@ -55,14 +60,14 @@ jobs: env: GHCR_PUSH_TOKEN: ${{ secrets.GHCR_PUSH_TOKEN }} if: env.GHCR_PUSH_TOKEN != '' - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GHCR_PUSH_TOKEN }} - name: Set Docker metadata id: ocean_node_meta - uses: docker/metadata-action@v5 + uses: docker/metadata-action@v6 with: images: | ${{ env.DOCKERHUB_IMAGE }} @@ -78,7 +83,7 @@ jobs: - name: Build and push to Docker Hub if: steps.dockerhub_login.outcome == 'success' id: build_dockerhub - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v7 with: builder: ${{ steps.buildx.outputs.name }} context: . @@ -90,7 +95,7 @@ jobs: - name: Build and push to GHCR if: steps.ghcr_login.outcome == 'success' id: build_ghcr - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v7 with: builder: ${{ steps.buildx.outputs.name }} context: . @@ -110,7 +115,7 @@ jobs: touch "/tmp/digests/ghcr-${ghcr_digest#sha256:}" fi - name: Upload digest - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: digests-${{ env.PLATFORM_PAIR }} path: /tmp/digests/* @@ -131,15 +136,17 @@ jobs: platform=${{ matrix.platform }} echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 + with: + persist-credentials: false - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@v4 with: platforms: ${{ matrix.platform }} #image: tonistiigi/binfmt:qemu-v8.0.4 - name: Set up Docker Buildx id: buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v4 with: platforms: ${{ matrix.platform }} - name: Login to Docker Hub @@ -148,7 +155,7 @@ jobs: DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_PUSH_USERNAME }} DOCKERHUB_TOKEN: ${{ secrets.DOCKER_PUSH_TOKEN }} if: env.DOCKERHUB_USERNAME != '' && env.DOCKERHUB_TOKEN != '' - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: username: ${{ secrets.DOCKERHUB_PUSH_USERNAME }} password: ${{ secrets.DOCKER_PUSH_TOKEN }} @@ -157,14 +164,14 @@ jobs: env: GHCR_PUSH_TOKEN: ${{ secrets.GHCR_PUSH_TOKEN }} if: env.GHCR_PUSH_TOKEN != '' - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GHCR_PUSH_TOKEN }} - name: Set Docker metadata id: ocean_node_meta - uses: docker/metadata-action@v5 + uses: docker/metadata-action@v6 with: images: | ${{ env.DOCKERHUB_IMAGE }} @@ -180,7 +187,7 @@ jobs: - name: Build and push to Docker Hub if: steps.dockerhub_login.outcome == 'success' id: build_dockerhub - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v7 with: builder: ${{ steps.buildx.outputs.name }} context: . @@ -192,7 +199,7 @@ jobs: - name: Build and push to GHCR if: steps.ghcr_login.outcome == 'success' id: build_ghcr - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v7 with: builder: ${{ steps.buildx.outputs.name }} context: . @@ -212,7 +219,7 @@ jobs: touch "/tmp/digests/ghcr-arm64-${ghcr_digest#sha256:}" fi - name: Upload digest - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: digests-${{ env.PLATFORM_PAIR }} path: /tmp/digests/* @@ -227,16 +234,16 @@ jobs: - build-arm steps: - name: Download digests - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: path: /tmp/digests pattern: digests-* merge-multiple: true - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v4 - name: Set Docker metadata id: ocean_node_meta - uses: docker/metadata-action@v5 + uses: docker/metadata-action@v6 with: images: | ${{ env.DOCKERHUB_IMAGE }} @@ -255,7 +262,7 @@ jobs: DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_PUSH_USERNAME }} DOCKERHUB_TOKEN: ${{ secrets.DOCKER_PUSH_TOKEN }} if: env.DOCKERHUB_USERNAME != '' && env.DOCKERHUB_TOKEN != '' - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: username: ${{ secrets.DOCKERHUB_PUSH_USERNAME }} password: ${{ secrets.DOCKER_PUSH_TOKEN }} @@ -264,7 +271,7 @@ jobs: env: GHCR_PUSH_TOKEN: ${{ secrets.GHCR_PUSH_TOKEN }} if: env.GHCR_PUSH_TOKEN != '' - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.repository_owner }} diff --git a/.github/workflows/ghcr_cleanup.yml b/.github/workflows/ghcr_cleanup.yml index 2151753..747ed6a 100644 --- a/.github/workflows/ghcr_cleanup.yml +++ b/.github/workflows/ghcr_cleanup.yml @@ -7,6 +7,9 @@ env: DOCKERHUB_IMAGE: ${{ 'oceanprotocol/ocean-node-bootstrap' }} GHCR_IMAGE: ${{ 'ghcr.io/oceanprotocol/ocean-node-bootstrap' }} +permissions: + contents: read + jobs: ghcr_cleanup: runs-on: ubuntu-latest @@ -16,14 +19,14 @@ jobs: env: GHCR_PUSH_TOKEN: ${{ secrets.GHCR_PUSH_TOKEN }} if: env.GHCR_PUSH_TOKEN != '' - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GHCR_PUSH_TOKEN }} - name: 'Clean up docker images' if: steps.ghcr_login.outcome == 'success' - uses: dataaxiom/ghcr-cleanup-action@v1 + uses: dataaxiom/ghcr-cleanup-action@d52806a0dc70b430571a37da1fde39733ffd640f # v1.2.2 with: token: ${{ secrets.GHCR_PUSH_TOKEN }} exclude-tags: latest,main,v* diff --git a/.github/workflows/n8n.yml b/.github/workflows/n8n.yml index b48d25f..50be856 100644 --- a/.github/workflows/n8n.yml +++ b/.github/workflows/n8n.yml @@ -1,57 +1,102 @@ name: Trigger N8N Security Scan +# Triggered by a PR comment containing "/run-security-scan" (NOT by pull_request +# events). Hardened: untrusted context values are passed via env and assembled +# with jq (no shell/JSON injection), the trigger is gated to trusted commenters, +# the job runs with no GITHUB_TOKEN scope, and the webhook call is authenticated +# with a bearer token. on: issue_comment: types: [created] +permissions: {} + jobs: trigger-n8n: - if: github.event.issue.pull_request && contains(github.event.comment.body, '/run-security-scan') + # Only react to comments on PRs, only when they contain the command, and only + # from trusted members/collaborators/owners — never arbitrary commenters. + if: > + github.event.issue.pull_request && + contains(github.event.comment.body, '/run-security-scan') && + contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association) runs-on: ubuntu-latest steps: - - name: Prepare hybrid payload + - name: Prepare payload id: payload + # Every value below is untrusted context: it is injected into the + # environment (never inlined into the script with ${{ }}) and then + # JSON-encoded by jq, so it cannot break out of the shell or the JSON. + env: + HTML_URL: ${{ github.event.issue.html_url }} + REPO: ${{ github.repository }} + OWNER: ${{ github.repository_owner }} + BRANCH: ${{ github.ref_name }} + COMMIT: ${{ github.sha }} + ACTOR: ${{ github.actor }} + BEFORE: ${{ github.event.before || '' }} + AFTER: ${{ github.event.after || '' }} + PR: ${{ github.event.pull_request.number || 'null' }} + PR_ISSUE: ${{ github.event.issue.pull_request.number || 'null' }} + HEAD_SHA: ${{ github.event.pull_request.head.sha || '' }} + HEAD_REF: ${{ github.event.pull_request.head.ref || '' }} + BASE_SHA: ${{ github.event.pull_request.base.sha || '' }} + BASE_REF: ${{ github.event.pull_request.base.ref || '' }} + CLONE_URL: ${{ github.event.repository.clone_url || '' }} + OVERRIDES: ${{ github.event.inputs.overrides || '' }} run: | - echo "Building payload.json" - # Ensure overrides is always valid JSON - OVERRIDES='${{ github.event.inputs.overrides }}' - if [ -z "$OVERRIDES" ]; then - OVERRIDES="{}" + set -euo pipefail + # Ensure overrides is always valid JSON (empty => {}). + OVERRIDES_JSON='{}' + if [ -n "${OVERRIDES}" ]; then + OVERRIDES_JSON="${OVERRIDES}" fi - cat < payload.json - { - "htmlUrl": "${{ github.event.issue.html_url }}", - "repo": "${{ github.repository }}", - "owner": "${{ github.repository_owner }}", - "branch": "${{ github.ref_name }}", - "commit": "${{ github.sha }}", - "actor": "${{ github.actor }}", - - "before": "${{ github.event.before || '' }}", - "after": "${{ github.event.after || '' }}", - - "pr": ${{ github.event.pull_request.number || 'null' }}, - "prIssue": ${{ github.event.issue.pull_request.number || 'null' }}, - - "headSha": "${{ github.event.pull_request.head.sha || '' }}", - "headRef": "${{ github.event.pull_request.head.ref || '' }}", - "baseSha": "${{ github.event.pull_request.base.sha || '' }}", - "baseRef": "${{ github.event.pull_request.base.ref || '' }}", - - "cloneUrl": "${{ github.event.repository.clone_url || '' }}", - - "overrides": $OVERRIDES - - } - EOF + jq -n \ + --arg htmlUrl "${HTML_URL}" \ + --arg repo "${REPO}" \ + --arg owner "${OWNER}" \ + --arg branch "${BRANCH}" \ + --arg commit "${COMMIT}" \ + --arg actor "${ACTOR}" \ + --arg before "${BEFORE}" \ + --arg after "${AFTER}" \ + --argjson pr "${PR:-null}" \ + --argjson prIssue "${PR_ISSUE:-null}" \ + --arg headSha "${HEAD_SHA}" \ + --arg headRef "${HEAD_REF}" \ + --arg baseSha "${BASE_SHA}" \ + --arg baseRef "${BASE_REF}" \ + --arg cloneUrl "${CLONE_URL}" \ + --argjson overrides "${OVERRIDES_JSON}" \ + '{ + htmlUrl: $htmlUrl, + repo: $repo, + owner: $owner, + branch: $branch, + commit: $commit, + actor: $actor, + before: $before, + after: $after, + pr: $pr, + prIssue: $prIssue, + headSha: $headSha, + headRef: $headRef, + baseSha: $baseSha, + baseRef: $baseRef, + cloneUrl: $cloneUrl, + overrides: $overrides + }' > payload.json - echo "Payload built:" + echo "Payload built (values shown are JSON-escaped):" cat payload.json - name: Send payload to n8n webhook + env: + N8N_BEARER_TOKEN: ${{ secrets.N8N_BEARER_TOKEN }} run: | - curl -X POST \ + set -euo pipefail + curl -fsS -X POST \ -H "Content-Type: application/json" \ + -H "Authorization: Bearer ${N8N_BEARER_TOKEN}" \ -d @payload.json \ https://pandoras-box.oceanprotocol.io/webhook/66d5c38a-7df7-4106-a8e3-f3070fcb6858