From 0db4ec19b695c50f72a5f3f5b754d0d719c34518 Mon Sep 17 00:00:00 2001 From: Andrey Gruzdev Date: Fri, 10 Jul 2026 01:35:02 +0200 Subject: [PATCH 1/4] chore(infra): harden verifier runtime --- .github/dependabot.yml | 46 ++++++++++++++++ .github/scripts/runtime-smoke.sh | 52 +++++++++++++++++ .github/workflows/main.yml | 44 +++++++++++++-- docker-compose.yml | 24 +++++--- nest/.dockerignore | 7 ++- nest/Dockerfile | 95 +++++++++++++++++++------------- nest/docker-healthcheck.js | 44 +++++++++++++++ nginx/scripts/entrypoint.sh | 6 +- 8 files changed, 268 insertions(+), 50 deletions(-) create mode 100644 .github/dependabot.yml create mode 100644 .github/scripts/runtime-smoke.sh create mode 100644 nest/docker-healthcheck.js diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..99e6068 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,46 @@ +version: 2 + +updates: + - package-ecosystem: npm + directory: /nest + schedule: + interval: weekly + day: monday + time: "06:00" + timezone: Europe/Madrid + open-pull-requests-limit: 5 + commit-message: + prefix: chore(deps) + + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + day: monday + time: "06:00" + timezone: Europe/Madrid + open-pull-requests-limit: 2 + commit-message: + prefix: chore(ci) + + - package-ecosystem: docker + directory: / + schedule: + interval: weekly + day: monday + time: "06:00" + timezone: Europe/Madrid + open-pull-requests-limit: 2 + commit-message: + prefix: chore(docker) + + - package-ecosystem: docker + directory: /nest + schedule: + interval: weekly + day: monday + time: "06:00" + timezone: Europe/Madrid + open-pull-requests-limit: 3 + commit-message: + prefix: chore(docker) diff --git a/.github/scripts/runtime-smoke.sh b/.github/scripts/runtime-smoke.sh new file mode 100644 index 0000000..2fccefe --- /dev/null +++ b/.github/scripts/runtime-smoke.sh @@ -0,0 +1,52 @@ +#!/bin/sh + +set -eu + +image=${1:?Usage: runtime-smoke.sh IMAGE} +container="sourcescan-verifier-smoke-$$" + +cleanup() { + docker rm --force --volumes "$container" >/dev/null 2>&1 || true +} + +trap cleanup EXIT HUP INT TERM + +private_key=$(docker run --rm --entrypoint node "$image" -e \ + "const { utils } = require('near-api-js'); console.log(utils.KeyPair.fromRandom('ed25519').toString())") + +docker run \ + --detach \ + --env IPFS_HOST=127.0.0.1 \ + --env IPFS_PORT=5001 \ + --env JWT_EXPIRATION=1 \ + --env NEAR_MAINNET_ACCOUNT_ID=v2-verifier.sourcescan.near \ + --env "NEAR_MAINNET_PRIVATE_KEY=$private_key" \ + --env NEAR_TESTNET_ACCOUNT_ID=v2-verifier.sourcescan.testnet \ + --env "NEAR_TESTNET_PRIVATE_KEY=$private_key" \ + --env NEST_PORT=3033 \ + --env NEST_PREFIX=/api \ + --name "$container" \ + --privileged \ + "$image" >/dev/null + +attempt=0 +while [ "$attempt" -lt 75 ]; do + health=$(docker inspect --format '{{.State.Health.Status}}' "$container") + + case "$health" in + healthy) + docker exec "$container" docker builder prune --force >/dev/null 2>&1 + exit 0 + ;; + unhealthy) + break + ;; + esac + + attempt=$((attempt + 1)) + sleep 1 +done + +docker inspect --format '{{json .State.Health}}' "$container" >&2 +docker logs "$container" >&2 +exit 1 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2716d01..d6a756a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -6,15 +6,18 @@ on: pull_request: branches: [ "main", "api-v2" ] +permissions: + contents: read + jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - name: Use Node.js 24.x LTS - uses: actions/setup-node@v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: '24.x' cache: 'npm' @@ -31,10 +34,10 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - name: Use Node.js 24.x LTS - uses: actions/setup-node@v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: '24.x' cache: 'npm' @@ -45,6 +48,11 @@ jobs: cd nest npm ci + - name: Audit Production Dependencies + run: | + cd nest + npm audit --omit=dev --audit-level=critical + - name: Lint run: | cd nest @@ -54,3 +62,31 @@ jobs: run: | cd nest npm run test + + container: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 + + - name: Build production image + uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2 + with: + cache-from: type=gha + cache-to: type=gha,mode=max + context: ./nest + load: true + pull: true + tags: sourcescan-verifier-back:test + + - name: Smoke test runtime tools + run: | + docker run --rm --entrypoint node sourcescan-verifier-back:test --version + docker run --rm --entrypoint near sourcescan-verifier-back:test --version + docker run --rm --entrypoint docker sourcescan-verifier-back:test --version + + - name: Smoke test DIND and API startup + run: sh .github/scripts/runtime-smoke.sh sourcescan-verifier-back:test diff --git a/docker-compose.yml b/docker-compose.yml index 3224d6c..14308cb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,7 +16,7 @@ services: privileged: true sscan-ipfs: - image: ipfs/kubo:v0.39.0 + image: ipfs/kubo:v0.40.1@sha256:9c70a3dba0b5f362bf99317a02384a194f5c91cf5388abdb8fe7d64d83dd20bb container_name: ${BRANCH_NAME:-main}_sscan-ipfs restart: always environment: @@ -35,13 +35,13 @@ services: - ./docker-data/ipfs:/ipfsdata - ./ipfs/001-configure.sh:/container-init.d/001-configure.sh:ro healthcheck: - test: ["CMD-SHELL", "ipfs swarm peers | head -1"] + test: ["CMD", "ipfs", "id", "--format="] interval: 10s timeout: 5s retries: 5 sscan-nginx: - image: nginx:1.27 + image: nginx:1.30.3-alpine-slim@sha256:d5b51cfc7d55fc7a7bcf4d1d577b9c3738331df56d68f0b1d8ac9795b9470a5a container_name: ${BRANCH_NAME:-main}_sscan-nginx restart: always ports: @@ -56,13 +56,23 @@ services: environment: - NGINX_PORT=${NGINX_PORT} - NEST_PORT=${NEST_PORT} - - GATE_PORT=${GATE_PORT} - - IPFS_PORT=${IPFS_PORT} + - GATE_PORT=8080 + - IPFS_PORT=5001 - IPFS_ADMIN_USER=${IPFS_ADMIN_USER} - IPFS_ADMIN_PASS=${IPFS_ADMIN_PASS} + healthcheck: + test: ["CMD-SHELL", "wget --spider --quiet http://127.0.0.1:$${NGINX_PORT}/"] + interval: 15s + timeout: 5s + start_period: 10s + retries: 3 depends_on: - - sscan-nest - - sscan-ipfs + sscan-nest: + condition: service_healthy + restart: true + sscan-ipfs: + condition: service_healthy + restart: true networks: nwk: diff --git a/nest/.dockerignore b/nest/.dockerignore index b512c09..b6246d1 100644 --- a/nest/.dockerignore +++ b/nest/.dockerignore @@ -1 +1,6 @@ -node_modules \ No newline at end of file +.git +**/._* +coverage +dist +node_modules +npm-debug.log* diff --git a/nest/Dockerfile b/nest/Dockerfile index 258f1b4..4c2fdd7 100644 --- a/nest/Dockerfile +++ b/nest/Dockerfile @@ -1,17 +1,19 @@ -# Build stage -FROM node@sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14 AS build +# syntax=docker/dockerfile:1.7 -WORKDIR /app +ARG NEAR_CLI_RS_VERSION=0.27.0 + +# Keep exact tags for reviewability and digests for reproducibility. +FROM node:24.17.0-alpine3.24@sha256:156b55f92e98ccd5ef49578a8cea0df4679826564bad1c9d4ef04462b9f0ded6 AS node-build -RUN apk add --update python3 make g++\ - && rm -rf /var/cache/apk/* +WORKDIR /app # Copy configuration files +COPY eslint.config.mjs ./ COPY tsconfig*.json ./ COPY package*.json ./ -# Install dependencies from package-lock.json -RUN npm ci +# Install dependencies from package-lock.json. +RUN --mount=type=cache,target=/root/.npm,sharing=locked npm ci # Copy application sources (.ts, .tsx, js) COPY src/ src/ @@ -19,52 +21,71 @@ COPY src/ src/ # Build application (produces dist/ folder) RUN npm run build -# Runtime (production) layer -FROM docker@sha256:7d85d0eda291f1a7ab6df4a9d1802b5ad4cf9145a088bd11188c78dcb5c7392b AS production +# Keep only dependencies needed by the production application. +FROM node-build AS node-production + +RUN npm prune --omit=dev + +# Build near-cli-rs separately so Rust and compiler tooling never reach runtime. +FROM rust:1.96.0-alpine3.24@sha256:f87aa870663e2b57ec8c69de82c7eedf7383bee987eef7612c0359635eaadb41 AS near-cli-build + +ARG NEAR_CLI_RS_VERSION -# Install dependencies for Rust, build tools, and HIDAPI RUN apk add --no-cache \ - python3 \ - git \ - curl \ - perl \ - protobuf \ - eudev-dev \ build-base \ - pkgconfig \ + eudev-dev \ linux-headers \ - bash + perl \ + pkgconf \ + protobuf + +RUN --mount=type=cache,target=/usr/local/cargo/registry,sharing=locked \ + --mount=type=cache,target=/usr/local/cargo/git,sharing=locked \ + --mount=type=cache,target=/tmp/cargo-target,sharing=locked \ + CARGO_TARGET_DIR=/tmp/cargo-target \ + cargo install near-cli-rs --version "${NEAR_CLI_RS_VERSION}" --locked -# Install Rust -ENV RUSTUP_HOME=/usr/local/rustup -ENV CARGO_HOME=/usr/local/cargo +RUN test "$(near --version)" = "near-cli-rs ${NEAR_CLI_RS_VERSION}" + +# Runtime (production) layer +FROM docker:29.6.1-dind@sha256:66d292e5c26bd33a6f6f61cacb880de2186339a524ecba1ce098dbbaceed6515 AS production -RUN mkdir -p $RUSTUP_HOME $CARGO_HOME \ - && curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain stable -y \ - && ln -s $RUSTUP_HOME /root/.rustup \ - && ln -s $CARGO_HOME /root/.cargo -# Add Rust to the PATH environment variable for all subsequent commands -ENV PATH="/usr/local/cargo/bin:$PATH" +ARG NEAR_CLI_RS_VERSION -# Install near-cli-rs for contract verification -RUN cargo install near-cli-rs --locked +LABEL dev.sourcescan.near-cli-rs.version="${NEAR_CLI_RS_VERSION}" + +# Git requires libcurl, while Compose and Buildx are host-side tools here. +RUN apk upgrade --no-cache libcurl \ + && rm -f \ + /usr/local/libexec/docker/cli-plugins/docker-buildx \ + /usr/local/libexec/docker/cli-plugins/docker-compose # Set environment variable for near-cli-rs to run in non-interactive mode ENV NEAR_CI=1 WORKDIR /app -# Copy node and npm from build stage -COPY --from=build /usr/local /usr/local +# Copy Node and the pinned near CLI into the runtime image. +COPY --from=node-build /usr/lib/libgcc_s.so.1 /usr/lib/ +COPY --from=node-build /usr/lib/libstdc++.so.6* /usr/lib/ +COPY --from=node-build /usr/local/bin/node /usr/local/bin/node +COPY --from=near-cli-build /usr/local/cargo/bin/near /usr/local/bin/near -# Copy dependencies files +# Copy package metadata and production dependencies. COPY package*.json ./ - -# Install runtime dependencies (without dev/test dependencies) -RUN npm ci --omit=dev +COPY --from=node-production /app/node_modules/ ./node_modules/ +COPY docker-healthcheck.js ./ # Copy production build -COPY --from=build /app/dist/ ./dist/ +COPY --from=node-build /app/dist/ ./dist/ + +# Fail the image build if any required runtime binary is unusable. +RUN node --version \ + && near --version \ + && docker --version + +HEALTHCHECK --interval=15s --timeout=5s --start-period=10s --retries=3 \ + CMD ["node", "docker-healthcheck.js"] # Start the application when dockerd is ready -CMD ["sh", "-ec", "rm -f /var/run/docker.pid /var/run/docker.sock /var/run/docker/containerd/containerd.pid /var/run/docker/containerd/containerd.sock /var/run/docker/containerd/containerd.sock.ttrpc /var/run/docker/containerd/containerd-debug.sock; dockerd & until docker info >/dev/null 2>&1; do sleep 1; done; exec node dist/main.js"] +CMD ["sh", "-ec", "rm -f /var/run/docker.pid /var/run/docker.sock /var/run/docker/containerd/containerd.pid /var/run/docker/containerd/containerd.sock /var/run/docker/containerd/containerd.sock.ttrpc /var/run/docker/containerd/containerd-debug.sock; dockerd & until node docker-healthcheck.js --docker-only >/dev/null 2>&1; do sleep 1; done; exec node dist/main.js"] diff --git a/nest/docker-healthcheck.js b/nest/docker-healthcheck.js new file mode 100644 index 0000000..ab28a94 --- /dev/null +++ b/nest/docker-healthcheck.js @@ -0,0 +1,44 @@ +'use strict'; + +const http = require('node:http'); + +function request(options, label) { + return new Promise((resolve, reject) => { + const req = http.get(options, (response) => { + response.resume(); + + if (response.statusCode >= 200 && response.statusCode < 300) { + resolve(); + return; + } + + reject(new Error(`${label} returned HTTP ${response.statusCode}`)); + }); + + req.setTimeout(3000, () => req.destroy(new Error(`${label} timed out`))); + req.on('error', reject); + }); +} + +async function main() { + await request( + { socketPath: '/var/run/docker.sock', path: '/_ping' }, + 'Docker daemon', + ); + + if (process.argv.includes('--docker-only')) { + return; + } + + const port = Number(process.env.NEST_PORT); + if (!Number.isInteger(port) || port < 1 || port > 65535) { + throw new Error('NEST_PORT must be a valid TCP port'); + } + + await request({ host: '127.0.0.1', port, path: '/' }, 'Verifier API'); +} + +main().catch((error) => { + console.error(error.message); + process.exit(1); +}); diff --git a/nginx/scripts/entrypoint.sh b/nginx/scripts/entrypoint.sh index 2986256..cafe693 100755 --- a/nginx/scripts/entrypoint.sh +++ b/nginx/scripts/entrypoint.sh @@ -4,11 +4,15 @@ set -e # Generate htpasswd from environment variables if [ -n "$IPFS_ADMIN_USER" ] && [ -n "$IPFS_ADMIN_PASS" ]; then echo "Generating htpasswd for user: $IPFS_ADMIN_USER" - echo "$IPFS_ADMIN_USER:$(openssl passwd -apr1 $IPFS_ADMIN_PASS)" > /etc/nginx/.htpasswd + password_hash=$(printf '%s\n' "$IPFS_ADMIN_PASS" | mkpasswd -P 0 -m sha512) + printf '%s:%s\n' "$IPFS_ADMIN_USER" "$password_hash" > /etc/nginx/.htpasswd else echo "Warning: IPFS_ADMIN_USER or IPFS_ADMIN_PASS not set, creating empty htpasswd" touch /etc/nginx/.htpasswd fi +chown root:nginx /etc/nginx/.htpasswd +chmod 640 /etc/nginx/.htpasswd + # Start nginx exec /docker-entrypoint.sh "$@" From 24f61a4d114f5c885f7bfc1372e3cdbeea9daed3 Mon Sep 17 00:00:00 2001 From: Andrey Gruzdev Date: Sat, 11 Jul 2026 22:08:07 +0200 Subject: [PATCH 2/4] chore(infra): patch Rust toolchain to 1.96.1 --- nest/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nest/Dockerfile b/nest/Dockerfile index 4c2fdd7..a27d225 100644 --- a/nest/Dockerfile +++ b/nest/Dockerfile @@ -27,7 +27,7 @@ FROM node-build AS node-production RUN npm prune --omit=dev # Build near-cli-rs separately so Rust and compiler tooling never reach runtime. -FROM rust:1.96.0-alpine3.24@sha256:f87aa870663e2b57ec8c69de82c7eedf7383bee987eef7612c0359635eaadb41 AS near-cli-build +FROM rust:1.96.1-alpine3.24@sha256:a41f7740f8b45d45795624eec13a8b42263cc700f19f7e4e86e04d3dda08a479 AS near-cli-build ARG NEAR_CLI_RS_VERSION From 263156d8f2f5a02ac85c1fb0acb9c1eabfd0f6eb Mon Sep 17 00:00:00 2001 From: Andrey Gruzdev Date: Sat, 11 Jul 2026 22:27:00 +0200 Subject: [PATCH 3/4] fix(infra): pin Docker CLI to embedded daemon --- .github/scripts/runtime-smoke.sh | 3 +++ nest/Dockerfile | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/scripts/runtime-smoke.sh b/.github/scripts/runtime-smoke.sh index 2fccefe..f5e37b1 100644 --- a/.github/scripts/runtime-smoke.sh +++ b/.github/scripts/runtime-smoke.sh @@ -35,6 +35,9 @@ while [ "$attempt" -lt 75 ]; do case "$health" in healthy) + docker exec "$container" node -e \ + "if (process.env.DOCKER_HOST !== 'unix:///var/run/docker.sock') process.exit(1)" + docker exec "$container" docker info >/dev/null 2>&1 docker exec "$container" docker builder prune --force >/dev/null 2>&1 exit 0 ;; diff --git a/nest/Dockerfile b/nest/Dockerfile index a27d225..d1bdb91 100644 --- a/nest/Dockerfile +++ b/nest/Dockerfile @@ -60,8 +60,9 @@ RUN apk upgrade --no-cache libcurl \ /usr/local/libexec/docker/cli-plugins/docker-buildx \ /usr/local/libexec/docker/cli-plugins/docker-compose -# Set environment variable for near-cli-rs to run in non-interactive mode -ENV NEAR_CI=1 +# Keep near-cli-rs non-interactive and point child processes at the embedded daemon. +ENV NEAR_CI=1 \ + DOCKER_HOST=unix:///var/run/docker.sock WORKDIR /app From c33df0f30f2443b72f59bba3992af01a118b26e8 Mon Sep 17 00:00:00 2001 From: Andrey Gruzdev Date: Sat, 11 Jul 2026 22:40:09 +0200 Subject: [PATCH 4/4] chore(ci): simplify legacy maintenance checks --- .github/dependabot.yml | 46 -------------------------- .github/scripts/runtime-smoke.sh | 55 -------------------------------- .github/workflows/main.yml | 28 ---------------- 3 files changed, 129 deletions(-) delete mode 100644 .github/dependabot.yml delete mode 100644 .github/scripts/runtime-smoke.sh diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 99e6068..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,46 +0,0 @@ -version: 2 - -updates: - - package-ecosystem: npm - directory: /nest - schedule: - interval: weekly - day: monday - time: "06:00" - timezone: Europe/Madrid - open-pull-requests-limit: 5 - commit-message: - prefix: chore(deps) - - - package-ecosystem: github-actions - directory: / - schedule: - interval: weekly - day: monday - time: "06:00" - timezone: Europe/Madrid - open-pull-requests-limit: 2 - commit-message: - prefix: chore(ci) - - - package-ecosystem: docker - directory: / - schedule: - interval: weekly - day: monday - time: "06:00" - timezone: Europe/Madrid - open-pull-requests-limit: 2 - commit-message: - prefix: chore(docker) - - - package-ecosystem: docker - directory: /nest - schedule: - interval: weekly - day: monday - time: "06:00" - timezone: Europe/Madrid - open-pull-requests-limit: 3 - commit-message: - prefix: chore(docker) diff --git a/.github/scripts/runtime-smoke.sh b/.github/scripts/runtime-smoke.sh deleted file mode 100644 index f5e37b1..0000000 --- a/.github/scripts/runtime-smoke.sh +++ /dev/null @@ -1,55 +0,0 @@ -#!/bin/sh - -set -eu - -image=${1:?Usage: runtime-smoke.sh IMAGE} -container="sourcescan-verifier-smoke-$$" - -cleanup() { - docker rm --force --volumes "$container" >/dev/null 2>&1 || true -} - -trap cleanup EXIT HUP INT TERM - -private_key=$(docker run --rm --entrypoint node "$image" -e \ - "const { utils } = require('near-api-js'); console.log(utils.KeyPair.fromRandom('ed25519').toString())") - -docker run \ - --detach \ - --env IPFS_HOST=127.0.0.1 \ - --env IPFS_PORT=5001 \ - --env JWT_EXPIRATION=1 \ - --env NEAR_MAINNET_ACCOUNT_ID=v2-verifier.sourcescan.near \ - --env "NEAR_MAINNET_PRIVATE_KEY=$private_key" \ - --env NEAR_TESTNET_ACCOUNT_ID=v2-verifier.sourcescan.testnet \ - --env "NEAR_TESTNET_PRIVATE_KEY=$private_key" \ - --env NEST_PORT=3033 \ - --env NEST_PREFIX=/api \ - --name "$container" \ - --privileged \ - "$image" >/dev/null - -attempt=0 -while [ "$attempt" -lt 75 ]; do - health=$(docker inspect --format '{{.State.Health.Status}}' "$container") - - case "$health" in - healthy) - docker exec "$container" node -e \ - "if (process.env.DOCKER_HOST !== 'unix:///var/run/docker.sock') process.exit(1)" - docker exec "$container" docker info >/dev/null 2>&1 - docker exec "$container" docker builder prune --force >/dev/null 2>&1 - exit 0 - ;; - unhealthy) - break - ;; - esac - - attempt=$((attempt + 1)) - sleep 1 -done - -docker inspect --format '{{json .State.Health}}' "$container" >&2 -docker logs "$container" >&2 -exit 1 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d6a756a..0117d28 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -62,31 +62,3 @@ jobs: run: | cd nest npm run test - - container: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 - - - name: Build production image - uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2 - with: - cache-from: type=gha - cache-to: type=gha,mode=max - context: ./nest - load: true - pull: true - tags: sourcescan-verifier-back:test - - - name: Smoke test runtime tools - run: | - docker run --rm --entrypoint node sourcescan-verifier-back:test --version - docker run --rm --entrypoint near sourcescan-verifier-back:test --version - docker run --rm --entrypoint docker sourcescan-verifier-back:test --version - - - name: Smoke test DIND and API startup - run: sh .github/scripts/runtime-smoke.sh sourcescan-verifier-back:test