docs(security): stop overstating release signing (#567) #832
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: CI | |
| on: | |
| push: | |
| branches: [main, develop] | |
| pull_request: | |
| # Least privilege (#282): every job here only reads the repo — none push commits, comment, or | |
| # publish packages. Narrowing the default GITHUB_TOKEN limits the blast radius of a compromised step. | |
| permissions: | |
| contents: read | |
| # Cancel superseded runs of the same PR; never cancel push runs on main/develop (they set the | |
| # diff-cover baseline and are required checks on the protected branch). #415. | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| jobs: | |
| dashboard: | |
| name: Dashboard tests (pytest + coverage) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| env: | |
| UV_PYTHON_DOWNLOADS: never # use the setup-python 3.11; don't fetch another interpreter | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| persist-credentials: false | |
| fetch-depth: 0 # full history so diff-cover can diff the PR against origin/develop (#286) | |
| - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 | |
| with: | |
| python-version: "3.11" | |
| - name: "Install uv (pinned — reproducible, hash-locked installs, #283)" | |
| # Curl the pinned installer (same posture as hadolint below); avoids a mutable-tag action. | |
| run: | | |
| export UV_INSTALL_DIR="$HOME/.local/bin" # deterministic install dir (runners may set CARGO_HOME) | |
| curl -LsSf https://astral.sh/uv/0.10.10/install.sh | sh | |
| echo "$UV_INSTALL_DIR" >> "$GITHUB_PATH" | |
| - name: Dashboard tests + coverage gate (emits coverage.xml) | |
| run: make test-dashboard | |
| - name: "Patch-coverage gate — changed lines >=90% (diff-cover vs origin/develop, #286)" | |
| run: | | |
| git fetch --no-tags origin develop | |
| make test-patch-coverage | |
| - name: Fake-daemon contract test (real clients vs controllable fakes) | |
| # Points the real Monero/Tari clients at the integration fakes and asserts they parse | |
| # every state (synced/syncing/down). Docker-free, so it runs on every PR (issue #54). | |
| run: make test-fakes | |
| frontend: | |
| name: Frontend logic tests (node --test) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| persist-credentials: false | |
| - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | |
| with: | |
| node-version: "20" | |
| # Pure client logic (worker sort, tooltip formatting) lives in static/logic.mjs and is | |
| # tested with Node's built-in runner — no package.json/node_modules/build step. Component | |
| # rendering is covered by a manual browser smoke test, not here. | |
| - name: Run frontend logic tests | |
| run: node --test build/dashboard/tests/frontend/*.test.mjs | |
| dashboard-image: | |
| name: Dashboard image (Docker test stage) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| persist-credentials: false | |
| - name: Build the dashboard test stage (installs package + runs the suite in-container) | |
| run: docker build --target test ./build/dashboard | |
| build-images: | |
| # Build every build/* image so a broken Dockerfile / COPY path / entrypoint / pinned-digest is | |
| # caught in CI instead of at deploy time (#124). Previously only the dashboard test stage was | |
| # built, so e.g. the Tor image's entrypoint restructure (#180) shipped with no CI safety net. | |
| # Tari is an upstream image (no Dockerfile here), so it's excluded. fail-fast: false so one | |
| # broken image doesn't hide the others; empty build-args keep the dashboard build working bare. | |
| name: Build image (${{ matrix.service }}) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| service: [monero, p2pool, tor, xmrig-proxy, dashboard] | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| persist-credentials: false | |
| - name: docker build ./build/${{ matrix.service }} | |
| run: docker build -t "pithead-${{ matrix.service }}:ci" "./build/${{ matrix.service }}" | |
| - name: Scan image for CVEs (Trivy) | |
| # Gate on actionable (fixable) HIGH/CRITICAL only; accepted findings live in .trivyignore. | |
| # Must stay green before v1.1 images publish (#282). | |
| uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 | |
| with: | |
| image-ref: pithead-${{ matrix.service }}:ci | |
| scanners: vuln | |
| severity: HIGH,CRITICAL | |
| ignore-unfixed: true | |
| exit-code: "1" | |
| trivyignores: .trivyignore | |
| hadolint: | |
| name: Dockerfile lint (hadolint) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| persist-credentials: false | |
| - name: "Install hadolint (pinned + sha256-verified, #286)" | |
| run: | | |
| curl -fsSL -o /tmp/hadolint \ | |
| https://github.com/hadolint/hadolint/releases/download/v2.12.0/hadolint-Linux-x86_64 | |
| echo "56de6d5e5ec427e17b74fa48d51271c7fc0d61244bf5c90e828aab8362d55010 /tmp/hadolint" | sha256sum -c - | |
| sudo install -m 0755 /tmp/hadolint /usr/local/bin/hadolint | |
| - name: Lint all build/* Dockerfiles (config in .hadolint.yaml) | |
| run: hadolint build/*/Dockerfile | |
| python-lint: | |
| name: Python lint + format (ruff) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| env: | |
| UV_PYTHON_DOWNLOADS: never | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| persist-credentials: false | |
| - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 | |
| with: | |
| python-version: "3.11" | |
| - name: Install uv (pinned) | |
| run: | | |
| export UV_INSTALL_DIR="$HOME/.local/bin" # deterministic install dir (runners may set CARGO_HOME) | |
| curl -LsSf https://astral.sh/uv/0.10.10/install.sh | sh | |
| echo "$UV_INSTALL_DIR" >> "$GITHUB_PATH" | |
| - name: ruff check + format --check (config in build/dashboard/pyproject.toml + root ruff.toml) | |
| # Single source of truth: the Makefile `lint-py` target, which runs ruff via uv from the | |
| # locked `dev` extra — one pinned ruff for CI, pre-commit, and local devs. | |
| run: make lint-py | |
| gitleaks: | |
| name: Secret scan (gitleaks) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| persist-credentials: false | |
| fetch-depth: 0 # full history so the scan covers every commit, not just the tip | |
| - name: Scan git history for secrets (gitleaks, pinned by digest) | |
| # Run the binary via its pinned image (the gitleaks-action requires a license for org repos). | |
| # Accepted false positives live in .gitleaks.toml. | |
| run: | | |
| docker run --rm -v "$PWD":/repo \ | |
| ghcr.io/gitleaks/gitleaks:v8.30.1@sha256:c00b6bd0aeb3071cbcb79009cb16a60dd9e0a7c60e2be9ab65d25e6bc8abbb7f \ | |
| git /repo --no-banner --redact --config /repo/.gitleaks.toml | |
| zizmor: | |
| name: Workflow audit (zizmor) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| env: | |
| UV_PYTHON_DOWNLOADS: never | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| persist-credentials: false | |
| - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 | |
| with: | |
| python-version: "3.11" | |
| - name: Install uv (pinned) | |
| run: | | |
| export UV_INSTALL_DIR="$HOME/.local/bin" | |
| curl -LsSf https://astral.sh/uv/0.10.10/install.sh | sh | |
| echo "$UV_INSTALL_DIR" >> "$GITHUB_PATH" | |
| - name: Audit workflows (injection, token scope, self-hosted-runner risks) | |
| run: uvx zizmor@1.25.2 --offline .github/workflows/ | |
| shell: | |
| name: Shell tests (shellcheck + pithead suite) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| persist-credentials: false | |
| - name: "Install shellcheck + shfmt (pinned + sha256-verified, #286)" | |
| # Direct upstream-release downloads with a sha256 check (RigForge convention) instead of the | |
| # runner's preinstalled shellcheck / apt — reproducible, and immune to the apt-mirror | |
| # flakiness of #64. Keep these pins in sync with the local-dev tooling. | |
| run: | | |
| curl -fsSL -o /tmp/shfmt \ | |
| https://github.com/mvdan/sh/releases/download/v3.13.1/shfmt_v3.13.1_linux_amd64 | |
| echo "fb096c5d1ac6beabbdbaa2874d025badb03ee07929f0c9ff67563ce8c75398b1 /tmp/shfmt" | sha256sum -c - | |
| sudo install -m 0755 /tmp/shfmt /usr/local/bin/shfmt | |
| curl -fsSL -o /tmp/sc.tar.xz \ | |
| https://github.com/koalaman/shellcheck/releases/download/v0.11.0/shellcheck-v0.11.0.linux.x86_64.tar.xz | |
| echo "8c3be12b05d5c177a04c29e3c78ce89ac86f1595681cab149b65b97c4e227198 /tmp/sc.tar.xz" | sha256sum -c - | |
| tar -xJf /tmp/sc.tar.xz -C /tmp | |
| sudo install -m 0755 /tmp/shellcheck-v0.11.0/shellcheck /usr/local/bin/shellcheck | |
| - name: Lint pithead, build/* container scripts, and test scripts (shellcheck + shfmt) | |
| # Single source of truth: the Makefile `lint-sh` target (so the file list can't drift | |
| # between here and the Makefile). Covers build/*/*.sh — the entrypoints + healthchecks that | |
| # run in every container (#124). Gate on warnings+errors; info-level style nits vary by | |
| # shellcheck version. Python lint runs in its own `python-lint` job (ruff isn't on this | |
| # runner; `make lint` runs both for local devs). | |
| run: make lint-sh | |
| - name: Run pithead test suite | |
| run: bash tests/stack/run.sh | |
| - name: Run integration harness self-test | |
| # Pure-logic checks for the tests/integration/ harness (config rendering, matrix | |
| # coverage, redaction). The LIVE matrix (tests/integration/run.sh) needs a real test | |
| # server and runs as a gated/manual release gate (#54), not on every PR. | |
| run: bash tests/integration/selftest.sh | |
| compose: | |
| name: Compose config + security hardening | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| persist-credentials: false | |
| - name: Validate docker-compose.yml interpolation + hardening invariants (#90) | |
| run: bash tests/stack/test_compose.sh | |
| lint-surfaces: | |
| name: Lint per-surface (biome, yaml, markdown, proto, toml) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| env: | |
| UV_PYTHON_DOWNLOADS: never | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| persist-credentials: false | |
| - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | |
| with: | |
| node-version: "20" | |
| - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 | |
| with: | |
| python-version: "3.11" | |
| - name: Install uv (pinned) | |
| run: | | |
| export UV_INSTALL_DIR="$HOME/.local/bin" | |
| curl -LsSf https://astral.sh/uv/0.10.10/install.sh | sh | |
| echo "$UV_INSTALL_DIR" >> "$GITHUB_PATH" | |
| - name: Lint JS/CSS (Biome), YAML, Markdown, proto (buf), TOML (taplo), docs voice | |
| # Single source of truth: the Makefile targets. Tools run via npx/uvx/docker (preinstalled). | |
| run: make lint-js lint-yaml lint-md lint-proto lint-toml lint-docs-voice |