| Age | Commit message (Collapse) | Author |
|
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.
|
|
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).
|
|
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)
|
|
ci(ai-validation): skip Pass 1/2 when PR has no .md changes
|
|
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)
|
|
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)
|
|
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)
|
|
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)
|
|
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>
|
|
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)
|
|
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)
|
|
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)
|
|
ci: serialize update-version-tags runs to close back-to-back-push race
|
|
ci(ai-validation): switch to GitHub-hosted ubuntu-latest runners
|
|
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)
|
|
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)
|
|
ci: fix Context7 refresh — branch-name addressing + default-variant handling
|
|
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.
|
|
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)
|
|
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>
|
|
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)
|
|
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.
|
|
ci(ai-validation): gate skip-notice comment on opened/reopened only
|
|
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
|
|
ci: harden update-version-tags against stale re-runs and silent failures
|
|
ci: drop leading slash in Context7 libraryName
|
|
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)
|
|
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)
|
|
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)
|
|
ci: AI Validation rewrite — MyST + split-job + branch map
|
|
ci: add Context7 refresh workflow
|
|
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.
|
|
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)
|
|
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)
|
|
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)
|
|
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)
|
|
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)
|
|
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)
|
|
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)
|
|
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)
|
|
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)
|
|
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)
|
|
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)
|
|
docs: drop stray terraformvyos.rst and remove .rst from source_suffix
|
|
docs: expand context7 rules — VyOS-specific LLM guardrails
|
|
ci: extend doc-linter to MyST Markdown
|
|
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)
|
|
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)
|
|
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)
|
|
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)
|