summaryrefslogtreecommitdiff
AgeCommit message (Collapse)Author
2026-05-11ci(ai-validation): fail-fast on traversal paths (consistency w/ non-regular ↵Yuriy Andamasov
guard) Copilot finding on PR #1959 (line 114): the path-traversal hardening block used `continue` (skip-with-warning) on detection of absolute / traversal paths, but the non-regular-tree-entry check below uses `exit 1`. The asymmetry meant a fork PR that smuggles in a path like `docs/../../outside.md` would silently bypass Pass 1 (no file copied into _changed_md/) and Pass 2 (LLM Read/Glob/Grep tools see no content) — reducing validation coverage on exactly the inputs that warrant the closest look. Change `continue` to `exit 1` so both unsafe-input checks have consistent visible-failure semantics. Maintainers must explicitly address an offending path rather than have it skipped. Mirrored byte-identically across all three open workflow PRs.
2026-05-11ci(ai-validation): rebase onto rolling@HEAD (#1968) — keep SHA pins/DB ↵Yuriy Andamasov
pin/no-id-token PR #1968 merged on rolling while this PR was in CR review. It adds: * has_md_changes output on prepare + job-level if-gate on validate (fixes the empty-_changed_md/ crash on infrastructure-only PRs) * fetch base by refs/heads/<ref> + use FETCH_HEAD instead of origin/<ref> (fixes the tag-vs-branch ambiguity on vyos/vyos- documentation where 'rolling' exists as both a branch and a tag) * skip validate on deletion-only Markdown PRs This commit pulls in rolling@HEAD's ai-validation.yml verbatim, then re-applies the 4 still-needed fixes raised by CR/Copilot: 1. SHA-pin actions/checkout@v6 (x4) + actions/upload-artifact@v4 + actions/download-artifact@v4 + anthropics/claude-code-action@v1 2. Pin reference-DB download to tag: ${{ env.REVIEWER_REF }} (was latest: true) — aligns DB version with pinned reviewer code 3. Drop id-token: write from validate job permissions (no OIDC use) Items already present in rolling@HEAD via PR #1968 (no further action here): * NUL/control-char rejection in changed-*.z (the corrected grep -zPq '[\x01-\x1F\x7F]' form, plus the explanatory comment block about why NUL is excluded from the rejection class) * Job-level if-gate on validate so infrastructure-only PRs skip the entire expensive validate chain * mkdir _changed_md defensive step in validate Result: 3 PRs (this one + the circinus/sagitta companions) now share a single byte-identical workflow file that is also a strict superset of rolling@HEAD's current file (3 fixes layered on top).
2026-05-11ci(ai-validation): reword NUL-guard comment for accuracyYuriy Andamasov
Two factual issues in prior wording (flagged by Copilot on #1969): - "NUL inside the name" implied embedded NUL is something to reject; NUL cannot appear in a git pathname (it is the tree-entry terminator). - "Filesystems … typically reject these" was wrong for LF/CR — POSIX filesystems allow them and git stores them fine. The actual hazard is in our line-delimited downstream tooling. Consolidated the two adjacent comment blocks into one accurate explanation. No behavior change. 🤖 Generated by [robots](https://vyos.io)
2026-05-11Merge pull request #1968 from vyos/yuriy/ai-validation-skip-no-md-prsYuriy Andamasov
ci(ai-validation): skip Pass 1/2 when PR has no .md changes
2026-05-11ci(ai-validation): cover deletion-only Markdown PRsYuriy Andamasov
CodeRabbit caught a real Major issue on this PR's first version: has_md_changes was derived from changed-md.txt, which is built with --diff-filter=ACMRT (excludes Deletes). A PR that only deletes docs/**/*.md files would set has_md_changes=false and skip validate entirely — but Pass 1 reviews the diff (via --pr-diff ../diff-md.patch), not just the post-image files in _changed_md/, so deletes are legitimate review targets. Two fixes that go together: 1. Derive has_md_changes from diff-md.patch (the unfiltered git diff already computed) instead of changed-md.txt. diff-md.patch includes D entries; changed-md.txt does not. 2. Add a defensive `mkdir -p _changed_md` after artifact download. actions/upload-artifact silently omits empty directories — on a deletion-only PR the artifact carries no _changed_md/ at all, and Pass 1's `working-directory: _changed_md` would fail. The directory is recreated empty; Pass 1 still operates on the diff. 🤖 Generated by [robots](https://vyos.io)
2026-05-11ci(ai-validation): fix control-char guard — use grep -z, exclude NULYuriy Andamasov
Previous form `tr -d '\0\n\r' | grep -Pq [\x00-\x1F\x7F]` stripped the very chars (LF, CR) it was meant to catch before the grep ran, so a path containing newlines or carriage returns slipped through. `grep -z` keeps NUL as the record delimiter (legitimate separator from git diff -z) and the pattern excludes 0x00 while catching every other control byte 0x01-0x1F + 0x7F. LF/CR inside any path now correctly fail the guard. Surfaced by Copilot on #1969; applied to all three branch copies (rolling/circinus/sagitta) so the workflow stays in sync. 🤖 Generated by [robots](https://vyos.io)
2026-05-11ci(ai-validation): scope GitHub App token to permission-contents: readYuriy Andamasov
The token is used only for read-only repo operations (sparse-checkout of reviewer branches.json, full checkout of vyos-1x, download of the reference-DB release asset). Without an explicit permission-* input the token inherits all installation permissions. Scope it down so a compromise cannot mutate either repo. Surfaced by CodeRabbit on #1960; applied to all three branch copies (rolling via #1969 follow-up + circinus #1959 + sagitta #1960) so the workflow stays in sync across the version-train branches. 🤖 Generated by [robots](https://vyos.io)
2026-05-11ci(ai-validation): hoist has_md_changes gate to validate job levelYuriy Andamasov
Per Copilot review on PR #1968: with the per-step gates, the validate job still ran the expensive setup chain (artifact download, GitHub App token, reviewer checkout/install, reference-DB download/extract, uv setup) on infrastructure-only PRs even though both Pass 1 and Pass 2 were guaranteed to skip. CI minutes wasted, no value produced. Move the gate to the job level — `if: needs.prepare.outputs.has_md_changes == 'true'` on validate. The whole job (including setup) skips cleanly when no .md files changed, and the per-step gates become redundant (removed in the same commit). 🤖 Generated by [robots](https://vyos.io)
2026-05-10ci(ai-validation): fetch base branch ref unambiguouslycopilot-swe-agent[bot]
Agent-Logs-Url: https://github.com/vyos/vyos-documentation/sessions/b505c475-0c95-470c-bb8e-3391741f8e4e Co-authored-by: andamasov <12631358+andamasov@users.noreply.github.com>
2026-05-11ci(ai-validation): address 4 CR findings — SHA pins, NUL guard, DB pin, ↵Yuriy Andamasov
drop id-token Follow-up on the now-merged #1957 (ubuntu-latest switch). CodeRabbit raised these findings on the paired add-to-circinus PR #1959 (where the same file was being added to the circinus branch); since the file contents are byte-identical across rolling/circinus/sagitta, applying the same fixes here. 1. SHA-pin actions/checkout@v6 (x4), actions/upload-artifact@v4, actions/download-artifact@v4, anthropics/claude-code-action@v1. pull_request_target has secrets + repo write — GitHub security guidance recommends full commit SHAs as the only immutable release form. 2. Reject paths containing control characters (NUL/CR/LF) in changed-md.z and changed-rst.z before `tr '\0' '\n'` converts them to newline-delimited manifests. A fork PR committing `docs/foo<LF>bar.md` would otherwise split into two logical lines, masking the real file from line-based consumers. 3. Pin reference-DB download to `tag: ${{ env.REVIEWER_REF }}` (was `latest: true`). Aligns DB version with the pinned reviewer code; a future reviewer-v1.x.x release with a DB schema change can't be silently picked up. 4. Drop `id-token: write` from validate job permissions. No OIDC usage; copy-paste leftover. Paired PRs on release branches (byte-identical file contents): * circinus: #1959 (commit 025319ea) * sagitta: #1960 (commit e5506317)
2026-05-11ci(ai-validation): skip Pass 1/2 when PR has no .md changesYuriy Andamasov
actions/upload-artifact silently omits empty directories. On a PR that changes no docs/**/*.md (workflow tweaks, README edits, config), the prepare job's _changed_md/ ends up empty and is dropped from the artifact. The validate job downloads the artifact, then "Pass 1 — deterministic checks" tries to start with `working-directory: _changed_md`, which doesn't exist, and bash fails before any in-step short-circuit can run. Surface a `has_md_changes` output from prepare based on whether changed-md.txt is non-empty, and gate Pass 1 + Pass 2 on it. When the flag is false, both review steps skip cleanly with no failure noise. Affects all infrastructure-only PRs (CI changes, workflow updates, config tweaks). Same failure was visible on the in-flight backports of update-version-tags hardening (#1965, #1966) which are pure workflow changes. 🤖 Generated by [robots](https://vyos.io)
2026-05-11ci: validate workflow_dispatch variant input against allowlistYuriy Andamasov
Copilot review on #1962 flagged that the type:choice UI constraint on workflow_dispatch.inputs.variant is bypassable when invoked via API (gh workflow run -f variant=…). An empty or arbitrary value would: - Generate a malformed concurrency key (context7-refresh- with empty suffix, since inputs.variant || head_branch || '' both go falsy) - Pass garbage to Context7's API (404s gracefully, but still a wasted runner minute and noisy) Add an explicit allowlist case after VARIANT is computed. Fails loud with a clear message before any downstream call. 🤖 Generated by [robots](https://vyos.io)
2026-05-11Merge pull request #1958 from vyos/yuriy/fix-update-version-tags-raceYuriy Andamasov
ci: serialize update-version-tags runs to close back-to-back-push race
2026-05-11Merge pull request #1957 from vyos/yuriy/ai-validation-ubuntu-runnersYuriy Andamasov
ci(ai-validation): switch to GitHub-hosted ubuntu-latest runners
2026-05-11ci: address Context7 LTS variants via tag field, not branch fieldYuriy Andamasov
Post-#1961, the rolling auto-refresh (default variant, no field) succeeds, but workflow_dispatch for branch=circinus/sagitta still 404s. Further empirical curls against the live API revealed: POST /api/v1/refresh {"libraryName":"/vyos/vyos-documentation","branch":"circinus"} → HTTP 404 {"error":"branch_not_found","message":"Branch 'circinus' not found"} POST /api/v1/refresh {"libraryName":"/vyos/vyos-documentation","tag":"1.5"} → HTTP 200 {"message":"Refresh started successfully"} So Context7's API addresses variants by their REGISTRATION TYPE on the dashboard: - default variant (rolling) → omit both 'branch' and 'tag' - tag-backed variants (1.5/1.4) → 'tag' field - branch-backed non-default → 'branch' field The dashboard shows 'rolling' with a branch icon and '1.5'/'1.4' with tag icons. The 'branch' field only addresses entries registered as branches; 'tag' addresses entries registered as tags. This is undocumented in the public GitHub Actions integration page but works against the live API. Changes: - Restore the variant mapping (circinus → 1.5, sagitta → 1.4) — that matches the actual dashboard variant names. #1961 had dropped this in favor of branch-name passthrough, which only worked for the default. - Switch the non-default payload from 'branch: <name>' to 'tag: <name>'. - workflow_dispatch input renamed back from 'branch' to 'variant'; choices back to [rolling, '1.5', '1.4']. - Restore the variant-keyed concurrency expression (with rewrite chain). - Update the documentation comment to record the empirical API semantics. Spec: ~/.claude/specs/2026-05-10-context7-github-actions-integration-design.md 🤖 Generated by [robots](https://vyos.io)
2026-05-11ci: re-validate HEAD inside retag jobYuriy Andamasov
Both Copilot and CodeRabbit flagged the same hole on PR #1958: GitHub's "Re-run failed jobs" can execute retag in isolation, skipping check_head. If the branch HEAD advanced since the original run, the isolated retag would PATCH the tag to a stale github.sha. Add the same HEAD-equivalence guard inside retag, immediately before the PATCH/POST. Defense-in-depth — both jobs check, so neither full re-runs nor selective retag re-runs can move the tag backward. 🤖 Generated by [robots](https://vyos.io)
2026-05-11Merge pull request #1961 from vyos/yuriy/context7-branchname-fixYuriy Andamasov
ci: fix Context7 refresh — branch-name addressing + default-variant handling
2026-05-11ci(ai-validation): refresh two stale comments after ubuntu-latest switchYuriy Andamasov
Copilot review on the paired add-to-circinus PR (#1959) flagged two documentation drifts from the ubuntu-latest switch in this PR: 1. Line 65: comment referenced a 'cp loop' but the implementation has used 'git show HEAD:<path>' as the bundling mechanism since 1ea164ff. Reworded to describe the bundling loop accurately. 2. Line 270: comment explained why setup-uv was used 'on Debian 12' — stale now that the workflow runs on ubuntu-latest. Reworded to describe the actual reason setup-uv is preferred (fast interpreter provisioning + portable to self-hosted Debian if this workflow ever moves back). Documentation-only change. No behavioral effect.
2026-05-11ci: fix Context7 refresh — branch-name addressing + default-variant handlingYuriy Andamasov
The post-merge auto-fired runs from #1948/#1949/#1950 all returned 4xx errors. Diagnostic curls against the live Context7 API revealed two issues: 1. The 'branch' parameter addresses variants by their underlying Git branch name (rolling/circinus/sagitta), not by their tag display name (rolling/1.5/1.4). Sending 'branch: "1.5"' returns: HTTP 404 {"error":"branch_not_found","message":"Branch '1.5' not found"} 2. The default variant refreshes when the 'branch' field is omitted entirely. Sending 'branch: "rolling"' returns: HTTP 400 {"error":"branch-not-found","message":"Failed to refresh library"} But omitting the field returns: HTTP 200 {"message":"Refresh started successfully"} 3. (Bonus, validating #1951 was wrong direction.) The libraryName must include the leading slash: 'libraryName: "/vyos/vyos-documentation"' per Context7's docs. Without the slash returns: HTTP 404 {"error":"library_not_found"} This commit restores the leading slash that #1951 incorrectly removed. Changes: - Restore leading slash on libraryName ('/' + github.repository). - Drop the tag-name mapping in the case statement; pass head_branch directly as the branch value. - Omit the 'branch' field when head_branch is 'rolling' (default variant). - workflow_dispatch input renamed from 'version' to 'branch'; choice options changed from [rolling, '1.5', '1.4'] to [rolling, circinus, sagitta]. - Simplified concurrency expression (no longer needs the rewrite chain). - Documentation comment updated to explain the branch-name addressing. Spec: ~/.claude/specs/2026-05-10-context7-github-actions-integration-design.md This is the 'fallback mapping' path that the spec's variant table already documented as a contingency — pre-flight confirmed it's the correct path. 🤖 Generated by [robots](https://vyos.io)
2026-05-10ci: keep stale reruns out of version-tag queuecopilot-swe-agent[bot]
Agent-Logs-Url: https://github.com/vyos/vyos-documentation/sessions/74be7b98-780e-4cbf-8177-11534c4ec2d7 Co-authored-by: andamasov <12631358+andamasov@users.noreply.github.com>
2026-05-11ci: serialize update-version-tags runs to close back-to-back-push raceYuriy Andamasov
PR #1953 added github.sha to the concurrency group key to prevent stale "Re-run jobs" replays from cancelling the in-progress current-HEAD run. Copilot review on the sagitta backport (#1955) caught the regression that introduced: per-SHA groups mean back-to-back pushes A then B run in parallel rather than serializing, and if run-A's force-PATCH lands after run-B's, the tag rewinds to A. Fix: per-branch group + cancel-in-progress: false. - Concurrent runs serialize, so commit order is preserved on the tag. - Stale "Re-run jobs" replays queue behind the current run, then hit the HEAD-equivalence guard in the job body and exit 0 — the guard (added in PR #1953) is the safeguard for that case, not the concurrency group. - Tag-move work is fast (~5s); serial execution under back-to-back push bursts is acceptable. 🤖 Generated by [robots](https://vyos.io)
2026-05-11ci(ai-validation): switch to GitHub-hosted ubuntu-latest runnersYuriy Andamasov
The `vyos` org does not have self-hosted runners labeled `web` (those live in the VyOS-Networks org pool and only serve repos there). Every AI Validation run queued since #1947 merged sat in `queued` state indefinitely with no runner picking it up — observed across all recent PRs (#1955 mergify backports, #1956, plus several yuriy/* branches). Switching both `prepare` and `validate` jobs to `runs-on: ubuntu-latest`: * Removes the host-isolation half of the prepare-job rationale comment and replaces it with the ephemeral-VM rationale (cross-run state leakage is impossible on a fresh GitHub-hosted VM). * Removes both `atos-actions/clean-self-hosted-runner` cleanup steps — GitHub-hosted runners are ephemeral, the action is a no-op there at best and a failure mode at worst (it expects self-hosted workspace patterns that don't exist on hosted runners). * Tweaks one comment that mentioned `/proc/<pid>/cmdline on the self- hosted runner` to be runner-agnostic. Also removes `.github/actionlint.yaml`. It was added in #1947 to silence actionlint's "label 'web' is unknown" false positive — with no workflow in this repo now using `[self-hosted, web]`, the file is dead code. The canonical reference at `VyOS-Networks/vyos-docs-opus-reviewer/scripts/ ai-validation.yml` intentionally diverges: that repo IS in VyOS-Networks and has access to the `web` self-hosted pool, so its canonical keeps `runs-on: [self-hosted, web]` and the cleanup steps. The deployed file's REFERENCE COPY header comment block in the reviewer repo will be updated in a follow-up to note that the deployed file may use different runners per host repo's pool availability. No security regression — the trust boundary on prepare is enforced by no-fork-code-execution, no-secrets-referenced, persist-credentials:false, and the split-job artifact, all of which are unchanged. Adds the implicit host-ephemerality guarantee of GitHub-hosted runners.
2026-05-11Merge pull request #1956 from vyos/yuriy/ai-validation-skip-notice-gateYuriy Andamasov
ci(ai-validation): gate skip-notice comment on opened/reopened only
2026-05-10ci: gate skip-notice on opened/reopened onlyYuriy Andamasov
CodeRabbit minor finding on the paired canonical PR (VyOS-Networks/vyos-docs-opus-reviewer#14): the `Notify on PR (when skipping)` step posts a fresh `gh pr comment` on every `synchronize` event. On a fork PR to a repo where the AI-validation secrets are not configured, every push during PR iteration would duplicate the skip notice, flooding the conversation thread. Gate the step to fire only on `opened`/`reopened` — those are the moments where the PR author benefits from being told once that validation is skipped. Further pushes add no new information; the workflow-run-page `::notice::` annotation is still emitted on every run for maintainers. `concurrency.cancel-in-progress: true` alone is not sufficient — most synchronize events would be cancelled before the notify step ran, but any run that completed the notify step before the next push still posts the comment. Paired canonical commit: VyOS-Networks/vyos-docs-opus-reviewer@ea88567
2026-05-10Merge pull request #1953 from vyos/yuriy/harden-update-version-tagsYuriy Andamasov
ci: harden update-version-tags against stale re-runs and silent failures
2026-05-10Merge pull request #1951 from vyos/yuriy/context7-libname-fixYuriy Andamasov
ci: drop leading slash in Context7 libraryName
2026-05-10ci: scope update-version-tags concurrency group by SHAYuriy Andamasov
Copilot review on PR #1953 surfaced an edge case the HEAD guard alone doesn't fully cover: with cancel-in-progress: true and a per-branch concurrency group, a stale "Re-run jobs" replay can cancel the in-progress run for the current branch HEAD. The stale re-run then hits the HEAD guard and exits 0, leaving the tag un-advanced until the next push. Including github.sha in the concurrency group means different commits land in different groups and never cancel each other. Same-SHA re-runs still deduplicate (they share the group), and the HEAD guard handles the case where a stale re-run beats the current-HEAD run to start. 🤖 Generated by [robots](https://vyos.io)
2026-05-10ci: harden update-version-tags against stale re-runs and silent failuresYuriy Andamasov
Two improvements to .github/workflows/update-version-tags.yml, bundled because they touch the same code block: 1. HEAD-equivalence guard. GitHub's "Re-run jobs" replays the original event SHA, which for this workflow would move tag rolling/1.5/1.4 backward to a stale commit. Compare github.sha against the live branch HEAD via the API and exit 0 with a log line if they differ. 2. PATCH-first with 404-only fallback to POST. The previous "GET probe then PATCH or POST" pattern silently fell through to POST on any gh-api error (auth, rate-limit, 5xx), which would attempt to create a tag that already exists and mask the real failure. Now the fallback to POST fires only on HTTP 404; every other error is re-emitted to stderr and fails the job. Backport to circinus and sagitta after merge. 🤖 Generated by [robots](https://vyos.io)
2026-05-10ci: drop leading slash in Context7 libraryNameYuriy Andamasov
Context7's API expects the bare repo identifier 'vyos/vyos-documentation' (verified at https://context7.com/vyos/vyos-documentation), not the leading-slash form. The auto-fired workflow_run cycle after #1950 / #1948 / #1949 merged returned HTTP 404 on all three variants because of the extra slash. Spec: ~/.claude/specs/2026-05-10-context7-github-actions-integration-design.md (spec narrative referenced Codex's round-1 advice to use leading slash; that advice was empirically wrong against the live Context7 API). 🤖 Generated by [robots](https://vyos.io)
2026-05-10Merge pull request #1947 from vyos/yuriy/ai-validation-myst-rebuildYuriy Andamasov
ci: AI Validation rewrite — MyST + split-job + branch map
2026-05-10Merge pull request #1950 from vyos/yuriy/context7-refreshYuriy Andamasov
ci: add Context7 refresh workflow
2026-05-10ci: declare custom self-hosted runner label `web` for actionlintYuriy Andamasov
CodeRabbit nitpick on PR #1947: actionlint flags `runs-on: [self-hosted, web]` with `label "web" is unknown` because `web` is a custom label for the VyOS-Networks org-managed self-hosted Debian 12 runner pool, not a known GitHub-hosted label. Adding `.github/actionlint.yaml` with the `self-hosted-runner.labels` list is the upstream-documented way to suppress the false positive without disabling the linter. No behavioral change.
2026-05-10ci: add connect-timeout and max-time to Context7 curl callYuriy Andamasov
Without explicit timeouts, a stalled TCP handshake or slow server response blocks the workflow indefinitely. --connect-timeout 10 bounds the TCP/connect phase; --max-time 60 caps total request duration. Both are within reasonable limits for a refresh POST that normally completes in well under a second. 🤖 Generated by [robots](https://vyos.io)
2026-05-10Revert "ci: prepare on GitHub-hosted ubuntu" — Debian self-hosted onlyYuriy Andamasov
User direction: GitHub-hosted ubuntu-latest is not available in this environment. The runner pool is Debian 12 self-hosted (web-runner-01, web-runner-02). prepare must run there too. Pushing back on Copilot's defense-in-depth finding (line 35) with explicit threat-model reasoning documented in the workflow comment: - prepare does not execute fork code. Only git fetch / git diff / git show / file reads. No pip install, no npm install, no build, no test. Adding any of these would require a deliberate code change in this file that a reviewer must approve. - No secrets are referenced in prepare. Even a presence-check would leak the value into the runner environment. - persist-credentials: false on the merge-ref checkout keeps the default GITHUB_TOKEN out of fork-readable .git/config. - The atos-actions/clean-self-hosted-runner step (`if: always()`) wipes the workspace after every job regardless of exit state. The split-job artifact still bridges the trust boundary to validate. validate remains the only place where secrets are referenced. The skip-notice `gh pr comment` step from a22df7d is preserved — that's an independent discoverability improvement. 🤖 Generated by [robots](https://vyos.io)
2026-05-10ci: bump REVIEWER_REF to reviewer-v1.0.1Yuriy Andamasov
Tag VyOS-Networks/vyos-docs-opus-reviewer/reviewer-v1.0.1 was published after PR #13 merged on the reviewer side. v1.0.1 brings: - Self-hosted runner workspace cleanup - setup-uv (Debian 12 + Python 3.12 compat) The branches.json map and Python source consumed via REVIEWER_REF are unchanged from v1.0.0, so this bump is a no-op functionally for the validate job — but it ensures the deployed workflow pulls from a stable, post-cleanup-merge state of main rather than the older v1.0.0 tag. 🤖 Generated by [robots](https://vyos.io)
2026-05-10ci: add explicit API key presence check before curlYuriy Andamasov
If CONTEXT7_API_KEY is unset or empty (e.g. secret not yet configured), emit a clear error message rather than letting curl fail with a generic auth error. The `:-` guard is needed because `set -u` would otherwise abort before the `-z` test when the variable is truly unset. 🤖 Generated by [robots](https://vyos.io)
2026-05-10fix(ci): prepare on GitHub-hosted ubuntu + PR comment on skipYuriy Andamasov
Two more Copilot findings on b0fdd07: 1. line 35 — defense in depth: prepare on self-hosted is risky. Even though the prepare job doesn't execute fork code (it only does git diff / git show / file reads — never pip install, npm install, build, or tests), Copilot's right that running fork content on a self-hosted runner with internal-network access is the wrong default. A future maintainer who innocently adds a "run linter" step to prepare could turn it into an attack vector against the VyOS internal network or a persistence mechanism on the host. Moved prepare to runs-on: ubuntu-latest. GitHub-hosted runners are ephemeral, isolated, and have no path to internal services. The trusted validate job stays on [self-hosted, web]; the split-job artifact bridges the trust boundary as before. Side benefit: removes the runner-side dependency on jq/gh/git for the prepare job (those are pre-installed on ubuntu-latest). 2. line 149 — skip-notice discoverability. When secrets are missing the workflow only emits ::notice:: in the run logs. Contributors checking the PR timeline have no reason to click through to the run page. Added a new step that posts an actual PR comment via gh pr comment when skip=true, running with GH_TOKEN: ${{ github.token }} (the validate job already has pull-requests: write). The ::notice:: annotation is preserved alongside. 🤖 Generated by [robots](https://vyos.io)
2026-05-10fix(ci): address 3 CodeRabbit findings on AI Validation workflowYuriy Andamasov
CR finally got credits and posted three substantive findings on b0fdd07. 1. CRITICAL — line 281, model: input is silently ignored. anthropics/claude-code-action@v1 removed the top-level `model` input; the migration guide says model selection now travels via `claude_args: --model <name>`. With the old form, the action used its DEFAULT model on every run instead of the pinned claude-opus-4-7, defeating the version pin entirely. CR even ran a web query and actionlint to verify (actionlint output: "input 'model' is not defined in action 'anthropics/claude-code-action@v1'"). Moved --model claude-opus-4-7 into claude_args. 2. MAJOR — line 135, secrets template-expanded into shell text. `[ -z "${{ secrets.VYOS_APP_ID }}" ]` lets GH Actions do ${{ ... }} expansion BEFORE bash parses the script. A secret containing a single quote, backtick, or $ would either break the test syntactically or be evaluated by the shell. The same hygiene that justifies the prepare/validate split applies here. Moved the three secrets to an env: mapping; the script now reads "$VYOS_APP_ID" etc., handed to bash as already-quoted env vars. 3. NIT — line 7, concurrency group brittle outside PR events. `github.event.pull_request.number` is empty on workflow_dispatch or schedule; the group would collapse to "ai-validation-" and unrelated runs cancel each other. Defensive fix: fallback to `github.ref`. Today the workflow only fires on pull_request_target so this is purely future-proofing. Same changes being synced to canonical scripts/ai-validation.yml in vyos-docs-opus-reviewer PR #13. 🤖 Generated by [robots](https://vyos.io)
2026-05-10ci: add Context7 refresh workflowYuriy Andamasov
Refreshes Context7's index of the VyOS documentation library on completion of 'Update version tags', mapping rolling/circinus/sagitta to Context7 variants rolling/1.5/1.4 respectively. Triggered via workflow_run because GITHUB_TOKEN-driven tag pushes from update-version-tags.yml do not fan out to downstream workflows. workflow_dispatch added for ad-hoc and bootstrap refreshes. Spec: ~/.claude/specs/2026-05-10-context7-github-actions-integration-design.md 🤖 Generated by [robots](https://vyos.io)
2026-05-10fix(ci): fail-fast on non-regular docs entries in PR diffYuriy Andamasov
Copilot finding on .github/workflows/ai-validation.yml:86: The bundling loop currently *skips* non-regular tree entries (symlinks mode 120000, submodules 160000) with a `::warning::` and continues. A PR that converts a regular docs/**/*.md into a symlink effectively bypasses both Pass 1 (no file copied into _changed_md/) and Pass 2 (LLM Read/Glob/Grep sees no content) — reducing validation coverage on exactly the kind of change that warrants closer review. Replaced the `continue` with `exit 1` on non-regular mode. The error message instructs the PR author to convert the file back to a regular .md (or get explicit maintainer waiver). Maintainers can still land non-regular doc entries by adjusting the workflow, but the decision becomes visible rather than silent. Combined with the symlink-exfil mitigation (commit 1ea164ff): we no longer copy symlink target content into the artifact AND we no longer silently skip the change. Either it's a regular file we can validate, or the workflow fails loudly. Same change being applied to the canonical reference copy in VyOS-Networks/vyos-docs-opus-reviewer PR #13. Suppressed-by-Copilot finding (line 110, id-token: write): Same pushback as previous rounds. claude-code-action@v1 uses OIDC internally (verified via this repo's commit b18a399c). Keeping. 🤖 Generated by [robots](https://vyos.io)
2026-05-10fix(ci): idempotent _changed_md + remove App token from process argvYuriy Andamasov
Two new findings on PR #1947: 1. Copilot — line 59, mkdir collision after cancellation: `mkdir _changed_md` fails with EEXIST if the directory was left behind by a previous run that concurrency.cancel-in-progress killed before the post-job cleanup step could execute. On a busy PR with rapid synchronize events this is a real non-determinism. Replaced with `rm -rf _changed_md && mkdir -p` so the bundling step is idempotent. 2. CodeRabbit — Major, scripts/ai-validation.yml:244 (mirrored here on the deployed copy): `uv pip install "git+https://x-access-token:${TOKEN}@..."` puts the App token in process argv. On a self-hosted runner anyone able to read /proc/<pid>/cmdline (any user with the same UID, any root tool, any LSM audit log) sees the secret while uv/git is running. This undercuts the persist-credentials:false hardening on the surrounding checkouts. Replaced the install with a two-step checkout + local-path install: - actions/checkout@v6 with persist-credentials:false fetches the reviewer source into ./reviewer-src using the App token as a transient http extraheader (not argv). - `uv pip install ./reviewer-src` then installs from the local path — no token anywhere on the command line. Net trust boundary: same security posture as the existing sparse-checkout of branches.json (line 100-110); no new attack surface. 🤖 Generated by [robots](https://vyos.io)
2026-05-10ci: force Node 24 + SHA-pin create-github-app-token v2.2.2Yuriy Andamasov
Mirrors VyOS-Networks/vyos-docs-opus-reviewer PR #13 commit 116a4bc. Adds workflow-level env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" GitHub's documented escape hatch from the Node 20 deprecation notice; forces every JavaScript action in the workflow to run on Node 24 without per-action version churn (covers upload-artifact@v4, download-artifact@v4, etc.). Bumps actions/create-github-app-token to SHA-pinned v2.2.2 (fee1f7d63c2ff003460e3d139729b119787bc349). 🤖 Generated by [robots](https://vyos.io)
2026-05-10ci: use setup-uv to install Python 3.12 on Debian 12Yuriy Andamasov
actions/setup-python's prebuilt manifest at actions/python-versions ships 3.12 for Ubuntu but not for Debian 12. The vyos web- runner pool runs Debian 12, so the previous setup-python@v6 step failed with: "##[error]The version '3.12' with architecture 'x64' was not found for Debian 12." Replacing the step with astral-sh/setup-uv@v8.1.0 (SHA-pinned). uv provisions Python from Astral's cross-platform standalone builds, which work on Debian. Setting `python-version: '3.12'` triggers `uv python install`; `activate-environment: true` creates a venv at $GITHUB_WORKSPACE/.venv and prepends its bin/ to PATH so the `vyos-doc-review` CLI installed by uv pip install is callable without further configuration. Replaces `pip install` with `uv pip install` to use the venv that setup-uv activated. Same change being applied to .github/workflows/rebuild-reference.yml and .github/workflows/full-scan.yml in vyos-docs-opus-reviewer PR #13 (canonical scripts/ai-validation.yml will re-sync from this file). 🤖 Generated by [robots](https://vyos.io)
2026-05-10Merge pull request #1944 from vyos/yuriy/drop-stray-rstYuriy Andamasov
docs: drop stray terraformvyos.rst and remove .rst from source_suffix
2026-05-10Merge pull request #1946 from vyos/yuriy/context7-rules-expandYuriy Andamasov
docs: expand context7 rules — VyOS-specific LLM guardrails
2026-05-10Merge pull request #1945 from vyos/yuriy/doc-linter-mystYuriy Andamasov
ci: extend doc-linter to MyST Markdown
2026-05-10fix(ci): reject path traversal in fork-controlled diff entriesYuriy Andamasov
Copilot finding on .github/workflows/ai-validation.yml:64 — defense in depth: Even though git's tree machinery generally rejects `..` segments and absolute paths at commit time, treat fork-controlled diff input as untrusted. A path like `docs/../../outside.md` would let `git show HEAD:<path>` write to `_changed_md/../../outside.md`, which resolves to a sibling of `_changed_md/` — escaping the artifact directory and writing into the runner workspace. Mitigation: validate each path before any mkdir/redirect. Reject: - absolute paths (`/foo`) - interior `..` segments (`foo/../bar`) - leading `..` (`../foo`) - trailing `..` (`foo/..`) - bare `..` Combined with the existing symlink/submodule mode check, the prepare job now refuses to bundle any tree entry whose path or type could escape the artifact boundary. Suppressed-by-Copilot finding (line 68, id-token: write): Same pushback as previous rounds. claude-code-action@v1 uses OIDC internally (verified via vyos/vyos-documentation commit b18a399c). Keeping as-is. 🤖 Generated by [robots](https://vyos.io)
2026-05-10ci: clean self-hosted runner workspace after each jobYuriy Andamasov
Self-hosted runners don't auto-clean their workspace between jobs, so PR fork content + cloned vyos-1x source + downloaded reference DB + the App-token-bearing pip install cache all linger on the runner host until the next job overwrites them. That's a leak surface for both secret material and disk space. Adds atos-actions/clean-self-hosted-runner@v1.4.34 (SHA-pinned) as the LAST step in both prepare and validate jobs, gated with `if: always()` so it runs after success, failure, or cancellation. The action is from atos-actions, a verified GitHub Marketplace partner. Composite action — no Node 20/24 deprecation. Logic is auditable (rm -rf ./* ./.[!.]*). Has a DISABLE_RUNNER_CLEANUP env var kill-switch for debugging if a job needs to leave residual state for inspection. 🤖 Generated by [robots](https://vyos.io)
2026-05-10fix(ci): bundle .md via git-show blobs, not filesystem cp (symlink exfil)Yuriy Andamasov
Copilot finding on .github/workflows/ai-validation.yml:50 — high severity: A fork PR can commit a symlink at docs/x.md (mode 120000) pointing to an absolute path on the self-hosted runner. The previous `xargs cp` would follow the symlink and copy the *target's* content (/etc/passwd, runner secrets, ssh keys, the App's PEM if accessible) into the pr-input artifact. The validate job downloads that artifact and exposes it to the Pass 2 claude-code-action step (which has Read,Glob,Grep tools), so a prompt-injection attempt could exfiltrate runner state via inline review comments. Mitigation: replace the cp loop with `git show HEAD:<path>` extraction. This pulls bytes directly from the merge commit's tree blob. For a symlink entry, git show returns the textual target path (a string like "/etc/passwd"), NOT the target's filesystem content. The artifact's worst-case is a text file containing a path string, which has no exfiltration value. Implementation: - Read NUL-delimited paths from changed-md.z (preserves filename safety). - For each path, check ls-tree mode: 100644/100755 = normal file, accept; 120000 = symlink, skip with ::warning::; 160000 = submodule, skip; anything else, skip. - Use `git show HEAD:<path>` to write blob content into _changed_md/. - All variable expansions are within double-quotes (no word-splitting, no glob, no recursive parse of $() in the substituted value). The xargs cp option-injection defense (`-- ` end-of-options) is no longer needed since cp is gone. The --diff-filter=ACMRT and persist-credentials fixes from edd903d remain. Suppressed-by-Copilot finding (line 68, id-token: write): Same as previous round — pushback. claude-code-action@v1 uses OIDC internally (verified via vyos/vyos-documentation commit b18a399c, where the permission was removed and immediately restored after the action broke). Keeping as-is. 🤖 Generated by [robots](https://vyos.io)
2026-05-10fix(ci): address Copilot review on PR #1947Yuriy Andamasov
Three Copilot findings on the AI Validation workflow: 1. line 46 (cp loop on deleted files): `git diff --name-only` with no filter includes Deleted entries, so the subsequent `xargs -0 ... cp --parents` would fail when a PR deletes (or renames) a `.md`/`.rst` file. Added `--diff-filter=ACMRT` to both name-only diffs so deletions are excluded from the cp source list. Deletions still appear in diff-md.patch (which uses the unfiltered full diff) so Pass 1's --pr-diff input still sees them via the patch hunk. 2. line 103 (reviewer sparse-checkout): The App token was being persisted into reviewer/.git/config as an http extraheader by default. The Pass 2 claude-code-action step has Read/Glob/Grep allowed, so a prompt-injection attempt could exfiltrate the token from the workspace. Added `persist-credentials: false`. The sparse-checkout fetched only branches.json which is a one-shot read, no further git ops needed in this job. 3. line 127 (vyos-1x checkout): Same persist-credentials concern. The vyos-1x clone is read by claude-code-action for Pass 2 source inspection — exactly the step where filesystem read tools are exposed to LLM-driven shell behavior. Added `persist-credentials: false`. The vyos-1x tree is only read after this checkout (`Read,Glob,Grep` over `.vyos-1x/`); we don't run any git commands against it that would need the token. Suppressed-by-Copilot finding (line 64, id-token: write): Pushback. claude-code-action@v1 uses OIDC internally (verified by commit b18a399c on the previously-deployed workflow). Removing this permission breaks the action. Keeping as-is. 🤖 Generated by [robots](https://vyos.io)