From 34cf804d759bfa8ed6c5d5cafdfae679978f390b Mon Sep 17 00:00:00 2001 From: Jeppe B <2jepp9350@gmail.com> Date: Wed, 22 Jul 2026 19:05:55 +0200 Subject: [PATCH] Harden CI runner and release gate security (#320) ## Summary - run untrusted pull-request jobs on ephemeral `ubuntu-24.04` runners - reserve the local backend runner pool for trusted branch pushes - remove world-writable Docker-socket fallbacks - pin core GitHub Actions and disable checkout credential persistence - remove the release-manager PHP parse-error fail-open path ## Why Pull-request code previously ran on persistent self-hosted runners with Docker access, and CI contained permission weakening and a release-gate break-glass success path. Those behaviors were unsafe for autonomous intake. ## Validation - workflow YAML parsed - backend AI workflow outputs are in sync - pinned action SHAs match the current v4 tags - `git diff --check` ## Risk and activation This is an R4 CI/release-policy change. Keep the PR draft for human review and let required CI prove the hosted-runner path before merge. --- .github/workflows/tests.yml | 84 ++++++++++++++++--------------------- 1 file changed, 35 insertions(+), 49 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 789c9b03..04289144 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -19,7 +19,9 @@ concurrency: jobs: php: name: PHP ${{ matrix.suite }} (required) - runs-on: [self-hosted, Linux, X64, pleno, backend, docker] + # Pull requests are untrusted and must use an ephemeral GitHub-hosted runner. + # Trusted branch pushes may use the local backend pool for throughput. + runs-on: ${{ github.event_name == 'pull_request' && 'ubuntu-24.04' || 'backend' }} strategy: fail-fast: false matrix: @@ -29,25 +31,21 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + with: + persist-credentials: false - name: Ensure Docker access run: | set -euo pipefail - if docker ps >/dev/null 2>&1; then - exit 0 - fi - test -S /var/run/docker.sock || (echo "Docker socket is not available." >&2; exit 1) - if command -v sudo >/dev/null 2>&1; then - sudo -n chmod 666 /var/run/docker.sock - else - chmod 666 /var/run/docker.sock - fi - docker ps >/dev/null + docker ps >/dev/null 2>&1 || { + echo "Docker is unavailable to the runner identity. Fix the isolated runner configuration; the workflow will not weaken /var/run/docker.sock permissions." >&2 + exit 1 + } - name: Setup Node.js if: ${{ matrix.suite == 'unit' }} - uses: actions/setup-node@v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: node-version: 22 @@ -61,7 +59,7 @@ jobs: - name: Upload PHP suite logs if: ${{ failure() }} continue-on-error: true - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: php-${{ matrix.suite }}-logs path: .tmp/ci-logs/${{ matrix.suite }} @@ -70,14 +68,16 @@ jobs: edge-agent: name: Edge Agent (required) - runs-on: [self-hosted, Linux, X64, pleno, backend] + runs-on: ${{ github.event_name == 'pull_request' && 'ubuntu-24.04' || 'backend' }} steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + with: + persist-credentials: false - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: node-version: 22 @@ -115,25 +115,21 @@ jobs: edge-broker: name: Edge Broker (required) - runs-on: [self-hosted, Linux, X64, pleno, backend, docker] + runs-on: ${{ github.event_name == 'pull_request' && 'ubuntu-24.04' || 'backend' }} steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + with: + persist-credentials: false - name: Ensure Docker access run: | set -euo pipefail - if docker ps >/dev/null 2>&1; then - exit 0 - fi - test -S /var/run/docker.sock || (echo "Docker socket is not available." >&2; exit 1) - if command -v sudo >/dev/null 2>&1; then - sudo -n chmod 666 /var/run/docker.sock - else - chmod 666 /var/run/docker.sock - fi - docker ps >/dev/null + docker ps >/dev/null 2>&1 || { + echo "Docker is unavailable to the runner identity. Fix the isolated runner configuration; the workflow will not weaken /var/run/docker.sock permissions." >&2 + exit 1 + } - name: Materialize CI compose env files run: | @@ -147,7 +143,7 @@ jobs: docker compose -f docker-compose.example.yml config > /dev/null - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: node-version: 22 @@ -161,7 +157,7 @@ jobs: edge-gateway-backend: name: Edge Gateway Backend (required) - runs-on: [self-hosted, Linux, X64, pleno, backend, docker] + runs-on: ${{ github.event_name == 'pull_request' && 'ubuntu-24.04' || 'backend' }} env: COMPOSE_FILE: docker-compose.yml:.github/docker-compose.ci.yml COMPOSE_PROJECT_NAME: edge-gateway-backend-${{ github.run_id }}-${{ github.run_attempt }} @@ -175,21 +171,17 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + with: + persist-credentials: false - name: Ensure Docker access run: | set -euo pipefail - if docker ps >/dev/null 2>&1; then - exit 0 - fi - test -S /var/run/docker.sock || (echo "Docker socket is not available." >&2; exit 1) - if command -v sudo >/dev/null 2>&1; then - sudo -n chmod 666 /var/run/docker.sock - else - chmod 666 /var/run/docker.sock - fi - docker ps >/dev/null + docker ps >/dev/null 2>&1 || { + echo "Docker is unavailable to the runner identity. Fix the isolated runner configuration; the workflow will not weaken /var/run/docker.sock permissions." >&2 + exit 1 + } - name: Allocate CI ports run: | @@ -240,7 +232,7 @@ jobs: printf '\nEDGE_PUBLIC_BROKER_URL=http://edge-broker:4300/edge-broker\n' >> .env - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: node-version: 22 @@ -411,12 +403,6 @@ jobs: exit 0 fi - if printf '%s' "$response_body" | grep -qi 'Parse error'; then - echo "::warning::Release Manager API returned a PHP parse error while recording the gate. Treating this as a break-glass pass so a fix can be deployed." - printf '%s\n' "$response_body" - exit 0 - fi - printf '%s\n' "$response_body" echo "Release Manager gate failed with HTTP $http_code." >&2 exit 1