Merge pull request #117 from logos-co/ci/validate-without-fork-checkout

ci: fetch submission files instead of checking out the fork
This commit is contained in:
Sasha
2026-07-31 21:30:23 +02:00
committed by GitHub
2 changed files with 46 additions and 14 deletions
+17 -5
View File
@@ -4,8 +4,11 @@
#
# Runtime contract (from validate-submission.yml):
# - Runs on ubuntu-latest (GNU grep / sed / coreutils assumed).
# - `base/` contains the trusted base-branch checkout.
# - `pr/` contains the PR-head checkout, treated as untrusted data only.
# - `base/` contains the trusted base-branch checkout, and is the only
# source for repo-owned files such as the prize specs.
# - `pr/solutions/` contains just the submission markdown the PR changed,
# fetched by path from the API and treated as untrusted data only. The
# fork's working tree is never checked out.
# - Env: PR_TITLE, PR_REPO, BASE_REPO, CHANGED_FILES.
set -euo pipefail
@@ -230,7 +233,9 @@ if $IS_SOLUTION; then
# -------------------------------------------------------------------------
# 3h. Prize exists and is open
# -------------------------------------------------------------------------
PRIZE_FILE="pr/prizes/${PRIZE_ID}.md"
# Read from base/: the prize spec is repo-owned, so the authoritative copy
# is the base branch's, not whatever stale revision the fork branched from.
PRIZE_FILE="base/prizes/${PRIZE_ID}.md"
if [[ ! -f "$PRIZE_FILE" ]]; then
err "Prize \`${PRIZE_ID}\` not found in \`prizes/\`. Check the ID."
else
@@ -261,7 +266,12 @@ if $IS_SOLUTION; then
CLONE_DIR="/tmp/submission-repo"
rm -rf "$CLONE_DIR"
if git clone --depth=1 "$REPO_URL" "$CLONE_DIR" 2>/dev/null; then
# `REPO_URL` is submitter-controlled, so the clone is bounded: no auth
# prompts, no submodules, one shallow branch, and a wall-clock ceiling so
# an oversized or slow repo cannot consume the whole job.
if GIT_TERMINAL_PROMPT=0 timeout "${LP_CLONE_TIMEOUT:-300}" \
git clone --depth=1 --single-branch --no-tags \
--no-recurse-submodules "$REPO_URL" "$CLONE_DIR" 2>/dev/null; then
# 4a. AI workspace artifacts in the external repo
AI_ARTIFACTS=()
@@ -336,7 +346,9 @@ if $IS_SOLUTION; then
# Logos Messaging / Waku integration
if echo "$PRIZE_CONTENT" | grep -qi 'Logos Messaging\|Logos Chat\|Waku'; then
waku_ref=$(grep -ril 'waku\|logos.messaging\|logos.chat' "$CLONE_DIR" \
# Time-bounded: this is the one full-content scan of the clone.
waku_ref=$(timeout "${LP_SCAN_TIMEOUT:-60}" \
grep -ril 'waku\|logos.messaging\|logos.chat' "$CLONE_DIR" \
--include='*.rs' --include='*.go' --include='*.ts' --include='*.js' \
--include='*.toml' --include='*.json' 2>/dev/null | head -1 || true)
if [[ -z "$waku_ref" ]]; then
+29 -9
View File
@@ -2,8 +2,8 @@ name: Validate Submission
# `pull_request_target` is used so the workflow can comment on PRs from forks
# (which is how solutions are submitted). It runs on the base branch with
# write perms; the PR-head checkout is treated as untrusted data only and
# never executed.
# write perms, so the fork's tree is never checked out here: the only
# submission content the validator reads is fetched by path as data.
on:
pull_request_target:
types: [opened, synchronize, reopened, edited]
@@ -26,13 +26,8 @@ jobs:
with:
ref: ${{ github.event.pull_request.base.sha }}
path: base
- name: Checkout PR head (untrusted data, never executed)
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
path: pr
# Nothing in this job pushes, so the token has no reason to sit in
# base/.git/config for the life of the run.
persist-credentials: false
- name: Get changed files
@@ -52,6 +47,31 @@ jobs:
echo "__LP_EOF__"
} >> "$GITHUB_OUTPUT"
- name: Fetch changed solution files (untrusted data, never executed)
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
CHANGED_FILES: ${{ steps.changed.outputs.files }}
run: |
# The validator reads exactly one thing from the PR: the submission
# markdown. Fetching those blobs by path keeps the fork's working
# tree off the runner entirely, so there is nothing here to execute
# and no `allow-unsafe-pr-checkout` opt-in to carry. Paths come from
# the PR files API and are constrained to a single markdown file
# directly under `solutions/`.
mkdir -p pr/solutions
solution_files=$(printf '%s\n' "$CHANGED_FILES" \
| grep -E '^solutions/[A-Za-z0-9._-]+\.md$' || true)
# Unquoted on purpose: the pattern above admits no spaces or globs.
for f in $solution_files; do
if ! gh api -H "Accept: application/vnd.github.raw" \
"/repos/${REPO}/contents/${f}?ref=${HEAD_SHA}" > "pr/${f}"; then
rm -f "pr/${f}"
echo "::warning::Could not fetch ${f} at ${HEAD_SHA}."
fi
done
- name: Run validation
id: validate
env: