diff options
| author | Yuriy Andamasov <yuriy@vyos.io> | 2026-05-11 00:57:19 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-05-11 00:57:19 +0300 |
| commit | 92c978c48a17de5c37d0d32080e8cc9c4799a492 (patch) | |
| tree | 8accedae0055841e608ed22b31ac02fce07f36fc /.github/workflows | |
| parent | 62b91104ea361650d75c567b9642b03fec538bb9 (diff) | |
| parent | bf50e7d11bc6c3c4224a7c6ea039755fe83558eb (diff) | |
| download | vyos-documentation-92c978c48a17de5c37d0d32080e8cc9c4799a492.tar.gz vyos-documentation-92c978c48a17de5c37d0d32080e8cc9c4799a492.zip | |
Merge pull request #1957 from vyos/yuriy/ai-validation-ubuntu-runners
ci(ai-validation): switch to GitHub-hosted ubuntu-latest runners
Diffstat (limited to '.github/workflows')
| -rw-r--r-- | .github/workflows/ai-validation.yml | 90 |
1 files changed, 39 insertions, 51 deletions
diff --git a/.github/workflows/ai-validation.yml b/.github/workflows/ai-validation.yml index 4c1984e6..44952092 100644 --- a/.github/workflows/ai-validation.yml +++ b/.github/workflows/ai-validation.yml @@ -25,10 +25,13 @@ jobs: # expanding the attack surface to any future shell change in this job. # The validate job below performs the secrets-availability check and # skips with a notice if any are missing. - # Untrusted prepare runs on the same self-hosted Debian 12 pool as - # validate. GitHub-hosted ubuntu-latest is not available in this - # environment. The trust boundary on prepare is enforced WITHOUT host - # isolation: + # Untrusted prepare runs on GitHub-hosted ubuntu-latest. The `vyos` org + # does not have self-hosted runners labeled `web` (those live in the + # VyOS-Networks org and only serve repos there); `vyos/vyos-documentation` + # therefore uses GitHub-hosted runners for the AI Validation workflow. + # The split-job artifact still bridges the trust boundary to validate; + # validate is the only place where secrets are referenced. Defense in + # depth on prepare: # - No fork code is executed: prepare only does # git fetch / git diff / git show / file reads. # There is no `pip install` from the fork, no `npm install`, no @@ -40,13 +43,10 @@ jobs: # - persist-credentials: false on the merge-ref checkout means the # default GITHUB_TOKEN is not available to fork-controlled file # content. - # - atos-actions/clean-self-hosted-runner step (`if: always()`) at - # the end of the job wipes the workspace regardless of how prepare - # exits. - # The split-job artifact still bridges the trust boundary to validate; - # validate is the only place where secrets are referenced. + # - GitHub-hosted runners are ephemeral — every run starts on a fresh + # VM, so cross-run state leakage is not possible. prepare: - runs-on: [self-hosted, web] + runs-on: ubuntu-latest permissions: contents: read steps: @@ -62,10 +62,11 @@ jobs: set -euo pipefail git fetch --depth=1 origin "${{ github.event.pull_request.base.ref }}" BASE="origin/${{ github.event.pull_request.base.ref }}" - # --diff-filter=ACMRT excludes Deleted entries so the cp loop below - # doesn't try to copy files that no longer exist in the merge ref. - # Deletions still appear in diff-md.patch (full diff) but not in - # changed-md.txt (which drives the file-copy step). + # --diff-filter=ACMRT excludes Deleted entries so the bundling + # loop below (`git show HEAD:<path>`) doesn't try to extract + # blobs for files that no longer exist in the merge ref. + # Deletions still appear in diff-md.patch (full diff) but not + # in changed-md.txt (which drives the bundling step). git diff "$BASE...HEAD" --name-only --diff-filter=ACMRT -z -- 'docs/**/*.md' > changed-md.z git diff "$BASE...HEAD" --name-only --diff-filter=ACMRT -z -- 'docs/**/*.rst' > changed-rst.z tr '\0' '\n' < changed-md.z > changed-md.txt @@ -73,17 +74,18 @@ jobs: git diff "$BASE...HEAD" -- 'docs/**/*.md' > diff-md.patch # Bundle .md files via git's blob store (NOT the filesystem). # The fork's merge ref can contain symlinks (mode 120000) committed - # to docs/**/*.md that resolve to absolute paths on this self-hosted - # runner. `cp` would dereference and copy the target's content - # (/etc/passwd, runner secrets, ssh keys) into the artifact, - # exfiltrating runner state to the validate job's claude-code-action - # input. `git show HEAD:<path>` returns the blob directly from the - # object database; for a symlink-mode entry it returns the textual - # target path, never the target's content. + # to docs/**/*.md that resolve to absolute paths on the runner. + # `cp` would dereference and copy the target's content (/etc/passwd, + # any cached state, ssh keys etc) into the artifact, exfiltrating + # runner state to the validate job's claude-code-action input. + # `git show HEAD:<path>` returns the blob directly from the object + # database; for a symlink-mode entry it returns the textual target + # path, never the target's content. The runner being ephemeral + # (GitHub-hosted) limits the blast radius further, but the blob- + # extraction approach is the actual mitigation and is portable. # Idempotent: a previous run cancelled by concurrency.cancel-in-progress - # may have left _changed_md/ behind on the self-hosted runner if the - # job was killed before the post-job cleanup ran. rm -rf + mkdir -p - # guarantees a clean target regardless of prior state. + # may have left _changed_md/ behind. rm -rf + mkdir -p guarantees a + # clean target regardless of prior state. rm -rf _changed_md && mkdir -p _changed_md while IFS= read -r -d '' path; do # Path-traversal hardening: even though git's tree machinery @@ -130,17 +132,9 @@ jobs: diff-md.patch _changed_md/ - # Self-hosted-runner workspace cleanup. Composite action; the upstream - # already wraps its own logic in `if: ${{ always() && ... }}`, but the - # outer step also needs `if: always()` so it runs even after a prior - # step fails. - - name: Clean self-hosted runner workspace - if: always() - uses: atos-actions/clean-self-hosted-runner@c6ce136031329a4435508e02b3f97fd85353f744 # v1.4.34 - validate: needs: [prepare] - runs-on: [self-hosted, web] + runs-on: ubuntu-latest permissions: contents: read pull-requests: write @@ -274,10 +268,13 @@ jobs: exit 1 fi - # actions/setup-python prebuilt manifest does not ship Python 3.12 for - # Debian 12 (only Ubuntu). astral-sh/setup-uv installs uv (cross-platform) - # which then provisions Python 3.12 from Astral's standalone builds — - # works on Debian. activate-environment:true creates a .venv at + # astral-sh/setup-uv is used instead of actions/setup-python: uv + # provisions Python interpreters from Astral's standalone builds in a + # few seconds (no apt cache, no compile), and the same recipe works + # unchanged if this workflow ever moves back to a self-hosted Debian + # runner — actions/setup-python relies on a prebuilt manifest that + # only covers Ubuntu for some interpreter versions. + # activate-environment:true creates a .venv at # ${{ github.workspace }}/.venv and prepends its bin/ to PATH so the # `vyos-doc-review` CLI script is callable in subsequent steps. - name: Setup uv + Python 3.12 @@ -289,12 +286,11 @@ jobs: # Check out the reviewer source instead of installing via # `uv pip install git+https://x-access-token:<TOK>@...` — the URL form - # puts the App token in process argv (visible through - # /proc/<pid>/cmdline on the self-hosted runner while uv or git is - # running). actions/checkout writes the token as a transient http - # extraheader instead, and persist-credentials:false ensures it does - # not linger in reviewer-src/.git/config where the Pass 2 LLM step - # could read it. + # puts the App token in process argv (visible through /proc/<pid>/cmdline + # to any other process on the runner while uv or git is running). + # actions/checkout writes the token as a transient http extraheader + # instead, and persist-credentials:false ensures it does not linger in + # reviewer-src/.git/config where the Pass 2 LLM step could read it. - name: Checkout reviewer package source (pinned to REVIEWER_REF) if: steps.secrets-check.outputs.skip != 'true' uses: actions/checkout@v6 @@ -410,11 +406,3 @@ jobs: claude_args: | --model claude-opus-4-7 --allowedTools "mcp__github_inline_comment__create_inline_comment,Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Read,Glob,Grep" - - # Self-hosted-runner workspace cleanup. Composite action; the upstream - # already wraps its own logic in `if: ${{ always() && ... }}`, but the - # outer step also needs `if: always()` so it runs even after a prior - # step fails. - - name: Clean self-hosted runner workspace - if: always() - uses: atos-actions/clean-self-hosted-runner@c6ce136031329a4435508e02b3f97fd85353f744 # v1.4.34 |
