diff options
| author | Yuriy Andamasov <yuriy@vyos.io> | 2026-05-10 22:19:25 +0300 |
|---|---|---|
| committer | Yuriy Andamasov <yuriy@vyos.io> | 2026-05-10 22:19:25 +0300 |
| commit | 1ea164ffadf124e9d0d0d34880e2ff729155b750 (patch) | |
| tree | bab4c4b488ab3e42714e36b203dfbe3786afabf5 /.github/workflows | |
| parent | edd903dcb59ab2669f7a96d74c118c897849dd92 (diff) | |
| download | vyos-documentation-1ea164ffadf124e9d0d0d34880e2ff729155b750.tar.gz vyos-documentation-1ea164ffadf124e9d0d0d34880e2ff729155b750.zip | |
fix(ci): bundle .md via git-show blobs, not filesystem cp (symlink exfil)
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)
Diffstat (limited to '.github/workflows')
| -rw-r--r-- | .github/workflows/ai-validation.yml | 25 |
1 files changed, 21 insertions, 4 deletions
diff --git a/.github/workflows/ai-validation.yml b/.github/workflows/ai-validation.yml index f4a0997a..57cd12a1 100644 --- a/.github/workflows/ai-validation.yml +++ b/.github/workflows/ai-validation.yml @@ -43,11 +43,28 @@ jobs: tr '\0' '\n' < changed-md.z > changed-md.txt tr '\0' '\n' < changed-rst.z > changed-rst.txt 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. mkdir _changed_md - # `--` after `-t _changed_md/` ends cp's option parsing, so a - # filename starting with `-` (which git won't emit from a tree, but - # defense in depth) cannot be misinterpreted as a cp option. - xargs -0 -a changed-md.z -r cp --parents -t _changed_md/ -- + while IFS= read -r -d '' path; do + mode=$(git ls-tree HEAD -- "$path" | awk '{print $1}') + case "$mode" in + 100644|100755) ;; + *) + echo "::warning::Skipping non-regular file in PR diff: $path (mode=$mode)" + continue + ;; + esac + mkdir -p "_changed_md/$(dirname -- "$path")" + git show "HEAD:$path" > "_changed_md/$path" + done < changed-md.z - name: Upload PR input artifact uses: actions/upload-artifact@v4 |
