Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/actionlint.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
self-hosted-runner:
labels:
- nvidia-h100-pt2-7
- nvidia-h100-1
- nvidia-h100-2
188 changes: 188 additions & 0 deletions .github/workflows/issue-pytest-command.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
name: Issue / PR pytest command

on:
issue_comment:
types: [created]

concurrency:
group: issue-pytest-${{ github.event.issue.number }}
cancel-in-progress: false

permissions:
issues: write
pull-requests: write
contents: read

jobs:
parse:
if: |
contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association) &&
startsWith(github.event.comment.body, 'pytest ')
runs-on: ubuntu-latest
outputs:
pytest_args: ${{ steps.parse.outputs.pytest_args }}
issue_number: ${{ github.event.issue.number }}
is_pr: ${{ github.event.issue.pull_request != null }}
comment_id: ${{ github.event.comment.id }}
steps:
- id: parse
env:
COMMENT_BODY: ${{ github.event.comment.body }}
run: |
FIRST_LINE="$(printf '%s\n' "$COMMENT_BODY" | head -n1)"
ARGS="${FIRST_LINE#pytest }"
EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64)
echo "pytest_args<<$EOF" >> $GITHUB_OUTPUT
echo "$ARGS" >> $GITHUB_OUTPUT
echo "$EOF" >> $GITHUB_OUTPUT
Comment thread
coderabbitai[bot] marked this conversation as resolved.

react:
needs: parse
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v7
with:
script: |
github.rest.reactions.createForIssueComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: ${{ needs.parse.outputs.comment_id }},
content: 'eyes',
});

run-pytest:
needs: parse
runs-on: nvidia-h100-pt2-7
Comment thread
coderabbitai[bot] marked this conversation as resolved.
timeout-minutes: 60
outputs:
status: ${{ steps.pytest.outputs.status }}
exit_code: ${{ steps.pytest.outputs.exit_code }}
env:
FLA_CI_ENV: 1
steps:
- name: Determine checkout ref
id: ref
run: |
if [ "${{ needs.parse.outputs.is_pr }}" = "true" ]; then
echo "ref=refs/pull/${{ needs.parse.outputs.issue_number }}/head" >> $GITHUB_OUTPUT
else
echo "ref=${{ github.event.repository.default_branch }}" >> $GITHUB_OUTPUT
fi

- name: Check out repo
uses: actions/checkout@v4
with:
ref: ${{ steps.ref.outputs.ref }}

- name: Discover Conda Path and Set Env Vars
id: find_conda
shell: bash
run: |
set -e
TARGET_CONDA_ENV="pytorch_2_7"
echo "Determining conda environment based on runner: ${{ runner.name }}"
case "${{ runner.name }}" in
nvidia-h100-pt2-7|nvidia-h100-1) TARGET_CONDA_ENV="pytorch_2_7" ;;
nvidia-h100-2) TARGET_CONDA_ENV="pytorch_2_7_1" ;;
esac
echo "--> Runner is '${{ runner.name }}', selected environment is '${TARGET_CONDA_ENV}'"
Comment thread
coderabbitai[bot] marked this conversation as resolved.

echo "Searching for Conda installation in home directory ($HOME)..."
POSSIBLE_NAMES=("miniforge3" "miniconda3" "anaconda3")
FOUND_PATH=""
for name in "${POSSIBLE_NAMES[@]}"; do
CANDIDATE_PATH="$HOME/$name"
echo "--> Checking for path: ${CANDIDATE_PATH}"
if [ -d "${CANDIDATE_PATH}" ] && [ -x "${CANDIDATE_PATH}/bin/conda" ]; then
echo " Found valid Conda installation: ${CANDIDATE_PATH}"
FOUND_PATH="${CANDIDATE_PATH}"
break
fi
done

if [ -n "${FOUND_PATH}" ]; then
echo "Setting CONDA environment variable to: ${FOUND_PATH}"
echo "CONDA=${FOUND_PATH}" >> $GITHUB_ENV
echo "CONDA_BIN_PATH=${FOUND_PATH}/envs/${TARGET_CONDA_ENV}/bin" >> $GITHUB_ENV
echo "CONDA_ENV_NAME=${TARGET_CONDA_ENV}" >> $GITHUB_ENV
else
echo "::error::Could not automatically find a Conda installation."
exit 1
fi

- name: Install/Update Dependencies
shell: bash
run: |
$CONDA_BIN_PATH/pip uninstall -y flash-linear-attention
$CONDA_BIN_PATH/pip install -U pytest setuptools wheel ninja
$CONDA_BIN_PATH/pip install .
Comment on lines +63 to +118

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: arbitrary code execution on self-hosted GPU runner from untrusted PR heads.

When is_pr is true, this job checks out refs/pull/N/head (attacker-controlled code from the PR author) and then runs pip install . on a persistent self-hosted H100 runner. pip install . executes setup.py / pyproject.toml build backends, and pytest collection runs conftest.py — both are arbitrary code execution vectors. The trust gate on line 19 only validates the commenter's author_association, not the PR author's. Any external contributor can open a malicious PR, wait for any OWNER/MEMBER/COLLABORATOR to type pytest ..., and then compromise the runner (secrets exfiltration, persistent backdoor, crypto-mining on the H100, etc.). This is the well-known GitHub "pwn-request" pattern, and it's especially dangerous on self-hosted runners where filesystem/caches persist across jobs.

At minimum, also require the PR author to be trusted, or require a maintainer-applied label as an additional gate. Ideally the GPU job should also run in an ephemeral container.

🔒 Suggested hardening (PR-author trust gate in parse)
   parse:
     if: |
       contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association) &&
       startsWith(github.event.comment.body, 'pytest ')
     runs-on: ubuntu-latest
     outputs:
       pytest_args:  ${{ steps.parse.outputs.pytest_args }}
       issue_number: ${{ github.event.issue.number }}
       is_pr:        ${{ github.event.issue.pull_request != null }}
       comment_id:   ${{ github.event.comment.id }}
+      pr_trusted:   ${{ steps.trust.outputs.trusted }}
     steps:
       - id: parse
         ...
+      - id: trust
+        if: ${{ github.event.issue.pull_request != null }}
+        env:
+          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        run: |
+          ASSOC=$(gh api "repos/${{ github.repository }}/pulls/${{ github.event.issue.number }}" --jq '.author_association')
+          case "$ASSOC" in
+            OWNER|MEMBER|COLLABORATOR) echo "trusted=true"  >> "$GITHUB_OUTPUT" ;;
+            *)                         echo "trusted=false" >> "$GITHUB_OUTPUT" ;;
+          esac

And gate run-pytest on it:

   run-pytest:
     needs: parse
+    if: needs.parse.outputs.is_pr != 'true' || needs.parse.outputs.pr_trusted == 'true'
     runs-on: nvidia-h100-pt2-7
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/issue-pytest-command.yml around lines 63 - 118, The
workflow currently checks out refs/pull/.../head and runs $CONDA_BIN_PATH/pip
install . and pytest in the "Install/Update Dependencies" step after the "Check
out repo" step (steps/ref and find_conda), which allows arbitrary code execution
from untrusted PR authors; update the gating logic in the parse step so it also
validates the PR author's trust (author_association) or requires a
maintainer-applied label before allowing is_pr=true, and then change the
run-pytest job to use a safe ref (merge/ref or the default branch) or run inside
an ephemeral sandbox/container and skip $CONDA_BIN_PATH/pip install . for
untrusted PRs (or only install from a pinned build artifact), and ensure the
job's conditional uses the new trusted PR gate instead of the current
commenter-only gate so "Install/Update Dependencies", pytest collection, and any
build backend execution are never performed on attacker-controlled heads.


- name: Run pytest
id: pytest
env:
PYTEST_ARGS: ${{ needs.parse.outputs.pytest_args }}
shell: bash
run: |
set +e
$CONDA_BIN_PATH/pytest $PYTEST_ARGS > pytest_output.txt 2>&1
EXIT_CODE=$?
set -e
echo "exit_code=$EXIT_CODE" >> $GITHUB_OUTPUT
if [ $EXIT_CODE -eq 0 ]; then
echo "status=✅ PASSED" >> $GITHUB_OUTPUT
else
echo "status=❌ FAILED" >> $GITHUB_OUTPUT
fi

- name: Upload pytest output
if: always()
uses: actions/upload-artifact@v4
with:
name: pytest-output
path: pytest_output.txt
retention-days: 1
if-no-files-found: warn

post-result:
needs: [parse, run-pytest]
if: always() && needs.parse.result == 'success'
runs-on: ubuntu-latest
steps:
- name: Download pytest output
uses: actions/download-artifact@v4
continue-on-error: true
with:
name: pytest-output

- name: Post comment
uses: actions/github-script@v7
env:
PYTEST_ARGS: ${{ needs.parse.outputs.pytest_args }}
PYTEST_STATUS: ${{ needs.run-pytest.outputs.status }}
PYTEST_EXIT_CODE: ${{ needs.run-pytest.outputs.exit_code }}
ISSUE_NUMBER: ${{ needs.parse.outputs.issue_number }}
with:
script: |
const fs = require('fs');
let output;
if (fs.existsSync('pytest_output.txt')) {
output = fs.readFileSync('pytest_output.txt', 'utf8');
// GitHub comment body limit is 65536 characters. Leave margin for markdown wrapper.
const MAX_LEN = 60000;
if (output.length > MAX_LEN) {
output = output.slice(0, MAX_LEN) + '\n\n... (output truncated due to GitHub comment length limit)';
}
} else {
output = '(No pytest output available. The test job may have failed before running pytest.)';
}
const status = process.env.PYTEST_STATUS || 'Unknown';
const exitCode = process.env.PYTEST_EXIT_CODE || '';
const args = (process.env.PYTEST_ARGS || '').trim();
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
const body = `## ${status} — Pytest Results (H100 PyTorch 2.7)\n\n**Command:** \\`pytest ${args}\\`\n**Exit code:** ${exitCode}\n\n[View workflow run logs](${runUrl})\n\n<details><summary>Click to expand pytest output</summary>\n\n\\`\\`\\`\n${output}\n\\`\\`\\`\n\n</details>`;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: Number(process.env.ISSUE_NUMBER),
body: body,
Comment thread
coderabbitai[bot] marked this conversation as resolved.
});
Comment thread
zhiyuan1i marked this conversation as resolved.
Loading