streamline some github actions (#7430)

Co-authored-by: Douwe Osinga <douwe@squareup.com>
This commit is contained in:
Douwe Osinga
2026-02-23 08:54:18 -05:00
committed by GitHub
parent b58144632b
commit ef3f5fa6c2
5 changed files with 147 additions and 20 deletions
+8 -4
View File
@@ -181,9 +181,11 @@ jobs:
run: | run: |
echo "number=$(jq -r '.number' /tmp/issue.json)" >> $GITHUB_OUTPUT echo "number=$(jq -r '.number' /tmp/issue.json)" >> $GITHUB_OUTPUT
echo "title<<TITLE_EOF" >> $GITHUB_OUTPUT # SECURITY: Use random delimiter to prevent injection if title contains our delimiter
DELIMITER="EOF_$(openssl rand -hex 8)"
echo "title<<$DELIMITER" >> $GITHUB_OUTPUT
jq -r '.title' /tmp/issue.json >> $GITHUB_OUTPUT jq -r '.title' /tmp/issue.json >> $GITHUB_OUTPUT
echo "TITLE_EOF" >> $GITHUB_OUTPUT echo "$DELIMITER" >> $GITHUB_OUTPUT
- name: Run goose - name: Run goose
id: goose id: goose
@@ -202,9 +204,11 @@ jobs:
if [ -n "$(git status --porcelain)" ] && [ -f /tmp/issue_summary.txt ]; then if [ -n "$(git status --porcelain)" ] && [ -f /tmp/issue_summary.txt ]; then
echo "has_changes=true" >> $GITHUB_OUTPUT echo "has_changes=true" >> $GITHUB_OUTPUT
echo "summary<<SUMMARY_EOF" >> $GITHUB_OUTPUT # SECURITY: Use random delimiter to prevent injection if summary contains our delimiter
SUMMARY_DELIMITER="EOF_$(openssl rand -hex 8)"
echo "summary<<$SUMMARY_DELIMITER" >> $GITHUB_OUTPUT
cat /tmp/issue_summary.txt >> $GITHUB_OUTPUT cat /tmp/issue_summary.txt >> $GITHUB_OUTPUT
echo "SUMMARY_EOF" >> $GITHUB_OUTPUT echo "$SUMMARY_DELIMITER" >> $GITHUB_OUTPUT
else else
echo "has_changes=false" >> $GITHUB_OUTPUT echo "has_changes=false" >> $GITHUB_OUTPUT
fi fi
+9 -5
View File
@@ -274,9 +274,11 @@ jobs:
INSTRUCTIONS="No specific instructions - perform a general code review." INSTRUCTIONS="No specific instructions - perform a general code review."
fi fi
echo "instructions<<INSTRUCTIONS_EOF" >> $GITHUB_OUTPUT # SECURITY: Use random delimiter to prevent injection if comment contains our delimiter
DELIMITER="EOF_$(openssl rand -hex 8)"
echo "instructions<<$DELIMITER" >> $GITHUB_OUTPUT
echo "$INSTRUCTIONS" >> $GITHUB_OUTPUT echo "$INSTRUCTIONS" >> $GITHUB_OUTPUT
echo "INSTRUCTIONS_EOF" >> $GITHUB_OUTPUT echo "$DELIMITER" >> $GITHUB_OUTPUT
- name: Run goose review - name: Run goose review
id: goose id: goose
@@ -285,14 +287,16 @@ jobs:
PR_TITLE: ${{ github.event.issue.title }} PR_TITLE: ${{ github.event.issue.title }}
PR_BODY: ${{ github.event.issue.body }} PR_BODY: ${{ github.event.issue.body }}
REVIEW_INSTRUCTIONS: ${{ steps.instructions.outputs.instructions }} REVIEW_INSTRUCTIONS: ${{ steps.instructions.outputs.instructions }}
# SECURITY: Pass issue JSON via environment variable to avoid heredoc injection
# (GHSA-mm8p-57gq-3xj6) - user-controlled content could terminate heredoc early
ISSUE_JSON: ${{ toJson(github.event.issue) }}
run: | run: |
mkdir -p $HOME/.local/share/goose/sessions mkdir -p $HOME/.local/share/goose/sessions
mkdir -p $HOME/.config/goose mkdir -p $HOME/.config/goose
git config --global --add safe.directory "$GITHUB_WORKSPACE" git config --global --add safe.directory "$GITHUB_WORKSPACE"
cat > /tmp/pr.json << 'PRJSON' # SECURITY: Use printf with env var instead of heredoc to prevent injection
${{ toJson(github.event.issue) }} printf '%s' "$ISSUE_JSON" > /tmp/pr.json
PRJSON
echo "$GOOSE_RECIPE" | envsubst '$PR_NUMBER $PR_TITLE $PR_BODY $REVIEW_INSTRUCTIONS' > /tmp/recipe.yaml echo "$GOOSE_RECIPE" | envsubst '$PR_NUMBER $PR_TITLE $PR_BODY $REVIEW_INSTRUCTIONS' > /tmp/recipe.yaml
+53 -2
View File
@@ -1,4 +1,8 @@
# This workflow is triggered by a comment on PR with the text ".build-cli" # This workflow is triggered by a comment on PR with the text ".build-cli"
#
# SECURITY: This workflow checks out and builds code from PRs. To prevent
# malicious code execution (GHSA-4h72-4h3w-4587, GHSA-mqm8-hhf6-wvjq),
# we verify the commenter has write access before proceeding.
on: on:
issue_comment: issue_comment:
types: [created] types: [created]
@@ -28,11 +32,56 @@ jobs:
name: Trigger on ".build-cli" PR comment name: Trigger on ".build-cli" PR comment
runs-on: ubuntu-latest runs-on: ubuntu-latest
outputs: outputs:
continue: 'true' continue: ${{ steps.security_check.outputs.authorized }}
pr_number: ${{ steps.command.outputs.issue_number || github.event.inputs.pr_number }} pr_number: ${{ steps.command.outputs.issue_number || github.event.inputs.pr_number }}
head_sha: ${{ steps.set_head_sha.outputs.head_sha || github.sha }} head_sha: ${{ steps.set_head_sha.outputs.head_sha || github.sha }}
steps: steps:
# SECURITY: Verify commenter has write access BEFORE any checkout
# This prevents attackers from triggering builds on their own malicious PRs
- name: Verify commenter permissions
id: security_check
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
script: |
// workflow_dispatch requires repo write access, so it's inherently safe
if (context.eventName === 'workflow_dispatch') {
core.setOutput('authorized', 'true');
console.log('✅ workflow_dispatch - authorized');
return;
}
const commenter = context.payload.comment.user.login;
console.log(`Checking permissions for: ${commenter}`);
try {
const { data: permission } = await github.rest.repos.getCollaboratorPermissionLevel({
owner: context.repo.owner,
repo: context.repo.repo,
username: commenter
});
const allowed = ['admin', 'maintain', 'write'].includes(permission.permission);
console.log(`Permission level: ${permission.permission}, Authorized: ${allowed}`);
if (!allowed) {
// Post a comment explaining the rejection
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.issue.number,
body: `⚠️ @${commenter} Only repository collaborators with write access can trigger builds.`
});
core.setOutput('authorized', 'false');
} else {
core.setOutput('authorized', 'true');
}
} catch (error) {
console.log(`Permission check failed: ${error.message}`);
core.setOutput('authorized', 'false');
}
- name: Run command action - name: Run command action
if: steps.security_check.outputs.authorized == 'true'
uses: github/command@v2.0.3 uses: github/command@v2.0.3
id: command id: command
with: with:
@@ -42,10 +91,12 @@ jobs:
allowed_contexts: pull_request allowed_contexts: pull_request
- name: Checkout code - name: Checkout code
if: steps.security_check.outputs.authorized == 'true'
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Get PR head SHA with gh - name: Get PR head SHA with gh
id: set_head_sha id: set_head_sha
if: steps.security_check.outputs.authorized == 'true'
run: | run: |
echo "Get PR head SHA with gh" echo "Get PR head SHA with gh"
HEAD_SHA=$(gh pr view "$ISSUE_NUMBER" --json headRefOid -q .headRefOid) HEAD_SHA=$(gh pr view "$ISSUE_NUMBER" --json headRefOid -q .headRefOid)
@@ -92,4 +143,4 @@ jobs:
- [📦 Windows (x86_64)](https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/goose-x86_64-pc-windows-gnu.zip) - [📦 Windows (x86_64)](https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/goose-x86_64-pc-windows-gnu.zip)
These links are provided by nightly.link and will work even if you're not logged into GitHub. These links are provided by nightly.link and will work even if you're not logged into GitHub.
+55 -4
View File
@@ -1,7 +1,12 @@
# This workflow is triggered by a comment on PR with the text ".bundle" # This workflow is triggered by a comment on PR with the text ".bundle"
# It bundles the ARM64 Desktop App, then creates a PR comment with a link to download the app. # It bundles the ARM64 Desktop App, then creates a PR comment with a link to download the app.
#
# SECURITY: This workflow checks out and builds code from PRs. To prevent
# malicious code execution (GHSA-4h72-4h3w-4587), we verify the commenter
# has write access before proceeding.
on: on:
issue_comment:
types: [created]
workflow_dispatch: workflow_dispatch:
inputs: inputs:
pr_number: pr_number:
@@ -28,11 +33,56 @@ jobs:
name: Trigger on ".bundle" PR comment name: Trigger on ".bundle" PR comment
runs-on: ubuntu-latest runs-on: ubuntu-latest
outputs: outputs:
continue: 'true' continue: ${{ steps.security_check.outputs.authorized }}
pr_number: ${{ steps.command.outputs.issue_number || github.event.inputs.pr_number }} pr_number: ${{ steps.command.outputs.issue_number || github.event.inputs.pr_number }}
pr_sha: ${{ steps.get_pr_info.outputs.sha }} pr_sha: ${{ steps.get_pr_info.outputs.sha }}
steps: steps:
# SECURITY: Verify commenter has write access BEFORE any checkout
# This prevents attackers from triggering builds on their own malicious PRs
- name: Verify commenter permissions
id: security_check
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
script: |
// workflow_dispatch requires repo write access, so it's inherently safe
if (context.eventName === 'workflow_dispatch') {
core.setOutput('authorized', 'true');
console.log('✅ workflow_dispatch - authorized');
return;
}
const commenter = context.payload.comment.user.login;
console.log(`Checking permissions for: ${commenter}`);
try {
const { data: permission } = await github.rest.repos.getCollaboratorPermissionLevel({
owner: context.repo.owner,
repo: context.repo.repo,
username: commenter
});
const allowed = ['admin', 'maintain', 'write'].includes(permission.permission);
console.log(`Permission level: ${permission.permission}, Authorized: ${allowed}`);
if (!allowed) {
// Post a comment explaining the rejection
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.issue.number,
body: `⚠️ @${commenter} Only repository collaborators with write access can trigger builds.`
});
core.setOutput('authorized', 'false');
} else {
core.setOutput('authorized', 'true');
}
} catch (error) {
console.log(`Permission check failed: ${error.message}`);
core.setOutput('authorized', 'false');
}
- name: Debug workflow trigger - name: Debug workflow trigger
if: steps.security_check.outputs.authorized == 'true'
env: env:
WORKFLOW_NAME: ${{ github.workflow }} WORKFLOW_NAME: ${{ github.workflow }}
WORKFLOW_REF: ${{ github.ref }} WORKFLOW_REF: ${{ github.ref }}
@@ -50,6 +100,7 @@ jobs:
echo "Repository: ${REPOSITORY}" echo "Repository: ${REPOSITORY}"
- name: Run command action - name: Run command action
if: steps.security_check.outputs.authorized == 'true'
uses: github/command@3442f3fa1efe01bdb024b157083c337902d17372 # v2.0.3 uses: github/command@3442f3fa1efe01bdb024b157083c337902d17372 # v2.0.3
id: command id: command
with: with:
@@ -61,7 +112,7 @@ jobs:
# Get the PR's SHA # Get the PR's SHA
- name: Get PR info - name: Get PR info
id: get_pr_info id: get_pr_info
if: ${{ steps.command.outputs.continue == 'true' || github.event_name == 'workflow_dispatch' }} if: steps.security_check.outputs.authorized == 'true'
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with: with:
script: | script: |
@@ -152,4 +203,4 @@ jobs:
* optionally run `codesign --force --deep --sign - --entitlements ui/desktop/entitlements.plist '/path/to/Goose.app'` * optionally run `codesign --force --deep --sign - --entitlements ui/desktop/entitlements.plist '/path/to/Goose.app'`
* start the app * start the app
The signing step is only needed if you do something that uses mac entitlements like speech to text The signing step is only needed if you do something that uses mac entitlements like speech to text
+22 -5
View File
@@ -25,14 +25,29 @@ jobs:
with: with:
egress-policy: audit egress-policy: audit
- name: Checkout PR # SECURITY FIX (GHSA-7qhh-cph9-6ppm): Checkout base branch for trusted Dockerfile
# The PR could contain a malicious Dockerfile that exfiltrates secrets.
# We checkout the base branch (trusted) for building the scanner image,
# and only fetch recipe files from the PR for scanning.
- name: Checkout base branch (trusted code)
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
ref: ${{ github.event.pull_request.base.sha }}
fetch-depth: 0
path: trusted
- name: Fetch PR recipe files only
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with: with:
ref: ${{ github.event.pull_request.head.sha }} ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0 fetch-depth: 0
path: pr-content
sparse-checkout: |
documentation/src/pages/recipes/data/recipes/
- name: Check if recipe files changed in this push - name: Check if recipe files changed in this push
id: recipe_changes id: recipe_changes
working-directory: pr-content
run: | run: |
set -e set -e
echo "🔍 Checking if recipe files were modified in this push..." echo "🔍 Checking if recipe files were modified in this push..."
@@ -68,6 +83,7 @@ jobs:
- name: Find recipe files in PR (new or modified) - name: Find recipe files in PR (new or modified)
id: find_recipes id: find_recipes
if: steps.recipe_changes.outputs.recipe_files_changed == 'true' if: steps.recipe_changes.outputs.recipe_files_changed == 'true'
working-directory: pr-content
run: | run: |
set -e set -e
echo "Looking for recipe files in PR (new or modified)..." echo "Looking for recipe files in PR (new or modified)..."
@@ -97,8 +113,8 @@ jobs:
echo "has_recipes=true" >> "$GITHUB_OUTPUT" echo "has_recipes=true" >> "$GITHUB_OUTPUT"
echo "recipe_count=$RECIPE_COUNT" >> "$GITHUB_OUTPUT" echo "recipe_count=$RECIPE_COUNT" >> "$GITHUB_OUTPUT"
# Save recipe file paths for later steps # Save recipe file paths for later steps (with pr-content prefix for mounting)
echo "$RECIPE_FILES" > "$RUNNER_TEMP/recipe_files.txt" echo "$RECIPE_FILES" | sed 's|^|pr-content/|' > "$RUNNER_TEMP/recipe_files.txt"
fi fi
- name: Set up Docker Buildx - name: Set up Docker Buildx
@@ -117,14 +133,15 @@ jobs:
DOCKER_BUILDKIT: 1 DOCKER_BUILDKIT: 1
IMAGE_TAG: ${{ github.sha }} IMAGE_TAG: ${{ github.sha }}
run: | run: |
# SECURITY: Build from trusted/ directory (base branch) to prevent malicious Dockerfile
docker buildx build \ docker buildx build \
--pull \ --pull \
--no-cache \ --no-cache \
--load \ --load \
--platform linux/amd64 \ --platform linux/amd64 \
-t "recipe-scanner:${IMAGE_TAG}" \ -t "recipe-scanner:${IMAGE_TAG}" \
-f recipe-scanner/Dockerfile \ -f trusted/recipe-scanner/Dockerfile \
recipe-scanner/ trusted/recipe-scanner/
- name: Scan all recipe files - name: Scan all recipe files
if: steps.find_recipes.outputs.has_recipes == 'true' && steps.recipe_changes.outputs.recipe_files_changed == 'true' if: steps.find_recipes.outputs.has_recipes == 'true' && steps.recipe_changes.outputs.recipe_files_changed == 'true'