Investigate petemoore/taskcluster alert #303 (aiohttp) #77737
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| --- | |
| 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 }} |