Skip to content

Investigate petemoore/taskcluster alert #315 (brace-expansion) #77750

Investigate petemoore/taskcluster alert #315 (brace-expansion)

Investigate petemoore/taskcluster alert #315 (brace-expansion) #77750

---
name: BLEnder Investigate Security Alert
run-name: >-
Investigate ${{ inputs.target_repo }}
alert #${{ inputs.alert_number }} (${{ inputs.alert_package }})
on:
workflow_dispatch:
inputs:
target_repo:
description: 'Target repo (e.g. mozilla/fx-private-relay)'
required: true
alert_number:
description: 'Dependabot alert number'
required: true
alert_package:
description: 'Package name'
required: true
alert_ecosystem:
description: 'Ecosystem (npm, pip, etc.)'
required: true
alert_severity:
description: 'Alert severity'
default: ''
alert_patched_version:
description: 'Patched version'
default: ''
dry_run:
description: 'Dry run (true = no mutations)'
default: 'true'
verbose:
description: 'Print full Claude output'
default: 'false'
permissions:
contents: read
concurrency:
group: >-
blender-investigate-${{ inputs.target_repo }}-${{ inputs.alert_number }}
cancel-in-progress: false
jobs:
investigate:
runs-on: ubuntu-latest
permissions:
contents: read
actions: write
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v6
with:
persist-credentials: false
- name: Setup target repo
id: setup
uses: ./.github/actions/setup-target
with:
target-repo: ${{ inputs.target_repo }}
app-id: ${{ secrets.BLENDER_APP_ID }}
private-key: ${{ secrets.BLENDER_APP_PRIVATE_KEY }}
blender-workspace: ${{ github.workspace }}
# Omit explicit permissions — token inherits all installed
# permissions (contents, pull-requests, vulnerability-alerts,
# repository-advisories). We can't restrict because the token
# action has no input for repository-advisories.
install-sandbox: 'true'
install-claude: 'true'
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
with:
python-version: '3.11'
- uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v6
- name: Install Python dependencies
run: |
uv sync
echo "$GITHUB_WORKSPACE/.venv/bin" >> "$GITHUB_PATH"
- name: Check for existing BLEnder PR
id: existing-pr
run: |
PR=$(gh pr list --repo "$REPO" \
--search "$PACKAGE in:title" \
--json number,title,headRefName \
--jq '.[] | select(.headRefName | startswith("blender/")) | .number' \
| head -1)
if [ -n "$PR" ]; then
echo "skip=true" >> "$GITHUB_OUTPUT"
echo "::notice ::Skipping investigation — existing BLEnder PR #${PR} for ${PACKAGE}"
else
echo "skip=false" >> "$GITHUB_OUTPUT"
fi
env:
GH_TOKEN: ${{ steps.setup.outputs.token }}
REPO: ${{ inputs.target_repo }}
PACKAGE: ${{ inputs.alert_package }}
- name: Load merged config
id: blender-config
if: steps.existing-pr.outputs.skip != 'true'
run: python scripts/load-config.py --defaults config/defaults.yml --repo-config target/.blender/blender.yml
- name: Gather alert context
if: steps.existing-pr.outputs.skip != 'true'
working-directory: target
run: ${{ github.workspace }}/scripts/gather-alert-context.sh
env:
GH_TOKEN: ${{ steps.setup.outputs.token }}
ALERT_NUMBER: ${{ inputs.alert_number }}
REPO: ${{ inputs.target_repo }}
PROMPT_TEMPLATE: ${{ github.workspace }}/prompts/investigate-alert-prompt.md
ALERT_PACKAGE: ${{ inputs.alert_package }}
ALERT_ECOSYSTEM: ${{ inputs.alert_ecosystem }}
- name: Run Claude investigation
if: steps.existing-pr.outputs.skip != 'true'
working-directory: target
run: ${{ github.workspace }}/scripts/run-claude.sh
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
REPO: ${{ inputs.target_repo }}
REPO_NAME: ${{ steps.setup.outputs.repo_name }}
BLENDER_DIR: ${{ github.workspace }}
BLENDER_MODE: investigate
MAX_CLAUDE_TURNS: ${{ steps.blender-config.outputs.investigate_max_claude_turns || '20' }}
MAX_BUDGET_USD: ${{ steps.blender-config.outputs.investigate_max_budget_usd || '1.50' }}
CLAUDE_VERBOSE: ${{ inputs.verbose }}
- name: Upload verdict
if: steps.existing-pr.outputs.skip != 'true'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: alert-verdict
path: target/.blender-alert-verdict.json
if-no-files-found: ignore
include-hidden-files: true
retention-days: 1
remediate:
needs: investigate
runs-on: ubuntu-latest
permissions:
contents: write
outputs:
action: ${{ steps.post-action.outputs.action }}
fork_repo: ${{ steps.post-action.outputs.fork_repo }}
advisory_ghsa_id: ${{ steps.post-action.outputs.advisory_ghsa_id }}
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v6
with:
persist-credentials: false
- name: Setup target repo
id: setup
uses: ./.github/actions/setup-target
with:
target-repo: ${{ inputs.target_repo }}
app-id: ${{ secrets.BLENDER_APP_ID }}
private-key: ${{ secrets.BLENDER_APP_PRIVATE_KEY }}
blender-workspace: ${{ github.workspace }}
install-blender-deps: 'true'
- name: Download verdict
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: alert-verdict
path: target
continue-on-error: true
- name: Post-investigation action
id: post-action
working-directory: target
run: python ${{ github.workspace }}/scripts/post_alert_action.py
env:
GH_TOKEN: ${{ steps.setup.outputs.token }}
REPO: ${{ inputs.target_repo }}
ALERT_NUMBER: ${{ inputs.alert_number }}
ALERT_PACKAGE: ${{ inputs.alert_package }}
ALERT_ECOSYSTEM: ${{ inputs.alert_ecosystem }}
ALERT_SEVERITY: ${{ inputs.alert_severity }}
ALERT_PATCHED_VERSION: ${{ inputs.alert_patched_version }}
DRY_RUN: ${{ inputs.dry_run }}
DISMISS_UNAFFECTED: ${{ steps.setup.outputs.dismiss_unaffected }}
# --- npm transitive dependency bump (lock file only) ---
- name: Run npm audit fix
id: npm-fix
if: steps.post-action.outputs.action == 'npm_bump'
working-directory: target
run: |
echo "Before fix — vulnerable instances:"
npm ls "$NPM_PACKAGE" --all 2>/dev/null || true
echo "---"
npm audit fix --package-lock-only 2>&1 || true
echo "After fix — checking for remaining vulnerabilities:"
# Check if this specific package still has a vulnerability
REMAINING=$(npm audit --json 2>/dev/null \
| jq --arg pkg "$NPM_PACKAGE" \
'[.vulnerabilities[$pkg] // empty | select(.severity != null)] | length')
if [ "${REMAINING:-0}" -gt 0 ]; then
echo "::warning ::npm audit fix did not resolve all $NPM_PACKAGE vulnerabilities"
echo "Remaining vulnerable paths:"
npm audit --json 2>/dev/null \
| jq --arg pkg "$NPM_PACKAGE" '.vulnerabilities[$pkg]'
echo "fixed=false" >> "$GITHUB_OUTPUT"
else
echo "All $NPM_PACKAGE vulnerabilities resolved."
echo "fixed=true" >> "$GITHUB_OUTPUT"
fi
env:
NPM_PACKAGE: ${{ steps.post-action.outputs.npm_package }}
- name: Create npm bump PR
if: >-
steps.post-action.outputs.action == 'npm_bump'
&& steps.npm-fix.outputs.fixed == 'true'
&& inputs.dry_run != 'true'
working-directory: target
run: bash ${{ github.workspace }}/scripts/npm-bump.sh
env:
PACKAGE: ${{ steps.post-action.outputs.npm_package }}
PATCHED_VERSION: ${{ inputs.alert_patched_version }}
ALERT_NUMBER: ${{ inputs.alert_number }}
REPO: ${{ inputs.target_repo }}
GH_TOKEN: ${{ steps.setup.outputs.token }}
# --- pip transitive dependency bump (lock file only) ---
- name: Run pip lock upgrade
id: pip-fix
if: steps.post-action.outputs.action == 'pip_lock_bump'
working-directory: target
run: |
echo "Upgrading ${PIP_PACKAGE} via ${PIP_LOCK_TOOL}..."
case "$PIP_LOCK_TOOL" in
uv)
pip install uv
uv lock --upgrade-package "$PIP_PACKAGE"
;;
poetry)
pip install poetry
poetry update "$PIP_PACKAGE"
;;
pipenv)
pip install pipenv
pipenv update "$PIP_PACKAGE"
;;
*)
echo "Unknown lock tool: ${PIP_LOCK_TOOL}"
exit 1
;;
esac
echo "upgraded=true" >> "$GITHUB_OUTPUT"
env:
PIP_PACKAGE: ${{ steps.post-action.outputs.pip_package }}
PIP_LOCK_TOOL: ${{ steps.post-action.outputs.pip_lock_tool }}
- name: Create pip bump PR
if: >-
steps.post-action.outputs.action == 'pip_lock_bump'
&& steps.pip-fix.outputs.upgraded == 'true'
&& inputs.dry_run != 'true'
working-directory: target
run: bash ${{ github.workspace }}/scripts/pip-lock-bump.sh
env:
PACKAGE: ${{ steps.post-action.outputs.pip_package }}
PATCHED_VERSION: ${{ steps.post-action.outputs.pip_version }}
ALERT_NUMBER: ${{ inputs.alert_number }}
REPO: ${{ inputs.target_repo }}
GH_TOKEN: ${{ steps.setup.outputs.token }}
PIP_LOCK_TOOL: ${{ steps.post-action.outputs.pip_lock_tool }}
# --- Private fork remediation (only for affected alerts) ---
- name: Clone private fork
if: steps.post-action.outputs.action == 'private_fork'
run: |
git clone "https://x-access-token:${GH_TOKEN}@github.com/${FORK_REPO}.git" fork
env:
GH_TOKEN: ${{ steps.setup.outputs.token }}
FORK_REPO: ${{ steps.post-action.outputs.fork_repo }}
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
if: steps.post-action.outputs.action == 'private_fork' && steps.setup.outputs.node_version != ''
with:
node-version: ${{ steps.setup.outputs.node_version }}
- name: Install fork dependencies
if: steps.post-action.outputs.action == 'private_fork' && steps.setup.outputs.install_command != ''
continue-on-error: true
working-directory: fork
run: |
eval "$INSTALL_COMMAND" 2>&1 | tee /tmp/install-output.log
env:
INSTALL_COMMAND: ${{ steps.setup.outputs.install_command }}
- name: Install sandbox dependencies
if: steps.post-action.outputs.action == 'private_fork'
run: sudo apt-get install -y bubblewrap socat
- name: Install Claude Code
if: steps.post-action.outputs.action == 'private_fork'
run: |
npm ci
echo "$GITHUB_WORKSPACE/node_modules/.bin" >> "$GITHUB_PATH"
- name: Copy verdict to fork
if: steps.post-action.outputs.action == 'private_fork'
run: cp target/.blender-alert-verdict.json fork/.blender-alert-verdict.json 2>/dev/null || true
- name: Build fix prompt
if: steps.post-action.outputs.action == 'private_fork'
working-directory: fork
run: |
cat > .blender-prompt << PROMPT_EOF
Fix the security vulnerability in ${ALERT_PACKAGE}.
Bump ${ALERT_PACKAGE} to ${ALERT_PATCHED_VERSION} and fix any
code that uses the vulnerable API. Run the test suite to
verify your changes.
PROMPT_EOF
# Append investigation verdict if available
if [ -f .blender-alert-verdict.json ]; then
printf '\n## Investigation verdict\n\nThe BLEnder investigation produced the following verdict. Use the vulnerable_paths and reason to focus your fix:\n\n' >> .blender-prompt
cat .blender-alert-verdict.json >> .blender-prompt
fi
env:
ALERT_PACKAGE: ${{ inputs.alert_package }}
ALERT_PATCHED_VERSION: ${{ inputs.alert_patched_version }}
- name: Run Claude fix
if: steps.post-action.outputs.action == 'private_fork'
working-directory: fork
run: ${{ github.workspace }}/scripts/run-claude.sh
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
REPO: ${{ steps.post-action.outputs.fork_repo }}
REPO_NAME: ${{ steps.setup.outputs.repo_name }}
BLENDER_DIR: ${{ github.workspace }}
BLENDER_MODE: fix
CLAUDE_VERBOSE: ${{ inputs.verbose }}
- name: Commit fix and open fork PR
id: commit
if: steps.post-action.outputs.action == 'private_fork' && inputs.dry_run != 'true'
working-directory: fork
run: ${{ github.workspace }}/scripts/fork-fix-pr.sh
env:
GH_TOKEN: ${{ steps.setup.outputs.token }}
REPO: ${{ steps.post-action.outputs.fork_repo }}
ALERT_NUMBER: ${{ inputs.alert_number }}
ALERT_PACKAGE: ${{ inputs.alert_package }}
# Mark this alert as investigated so the sweep does not re-trigger it.
# Uses a lightweight tag on the blender repo.
- name: Tag alert as investigated
if: inputs.dry_run != 'true'
run: |
TAG_NAME="investigated/${TARGET_REPO}/${ALERT_NUMBER}"
git tag "$TAG_NAME"
git push "https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" "$TAG_NAME"
env:
TARGET_REPO: ${{ inputs.target_repo }}
ALERT_NUMBER: ${{ inputs.alert_number }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}