ci: add reusable Pysa workflow#87
Conversation
PR SummaryLow Risk Overview Introduces a workflow-picker template ( Reviewed by Cursor Bugbot for commit de1e065. Bugbot is set up for automated code reviews on this repo. Configure here. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Config skip on .pyre_configuration.local alone causes failure
- The workflow now only skips config generation when the base
.pyre_configurationexists, so repos with only.pyre_configuration.localstill get a generated base config.
- The workflow now only skips config generation when the base
Preview (c3b79ce308)
diff --git a/.github/workflow-templates/pysa.properties.json b/.github/workflow-templates/pysa.properties.json
new file mode 100644
--- /dev/null
+++ b/.github/workflow-templates/pysa.properties.json
@@ -1,0 +1,9 @@
+{
+ "name": "Pysa static analysis",
+ "description": "Run Pyre/Pysa taint analysis for Python repositories.",
+ "iconName": "octicon shield-check",
+ "categories": [
+ "Security",
+ "Continuous integration"
+ ]
+}
diff --git a/.github/workflow-templates/pysa.yml b/.github/workflow-templates/pysa.yml
new file mode 100644
--- /dev/null
+++ b/.github/workflow-templates/pysa.yml
@@ -1,0 +1,19 @@
+name: Pysa static analysis
+
+on:
+ pull_request:
+ paths:
+ - "**/*.py"
+ - "pyproject.toml"
+ - "requirements*.txt"
+ - ".pyre_configuration*"
+ - ".pysa/**"
+ - ".github/workflows/pysa.yml"
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+jobs:
+ pysa:
+ uses: evalops/.github/.github/workflows/pysa.yml@main
diff --git a/.github/workflows/pysa.yml b/.github/workflows/pysa.yml
new file mode 100644
--- /dev/null
+++ b/.github/workflows/pysa.yml
@@ -1,0 +1,130 @@
+name: pysa
+
+on:
+ workflow_call:
+ inputs:
+ python_version:
+ description: "Python version used to install and run Pyre/Pysa"
+ required: false
+ type: string
+ default: "3.12"
+ runner_label:
+ description: "Runner label used for Pysa"
+ required: false
+ type: string
+ default: blacksmith-4vcpu-ubuntu-2404
+ working_directory:
+ description: "Repository-relative directory to analyze"
+ required: false
+ type: string
+ default: "."
+ source_directories:
+ description: "Comma-separated Python source directories for generated Pyre config"
+ required: false
+ type: string
+ default: "."
+ taint_models_path:
+ description: "Optional Pysa taint-model directory"
+ required: false
+ type: string
+ default: ".pysa"
+ requirements_file:
+ description: "Optional requirements file to install before analysis"
+ required: false
+ type: string
+ default: ""
+ setup_command:
+ description: "Optional shell command to install repo-specific dependencies"
+ required: false
+ type: string
+ default: ""
+ pyre_package:
+ description: "pip package spec for Pyre/Pysa"
+ required: false
+ type: string
+ default: "pyre-check"
+
+permissions:
+ contents: read
+
+jobs:
+ analyze:
+ runs-on: ${{ inputs.runner_label }}
+ timeout-minutes: 25
+ defaults:
+ run:
+ working-directory: ${{ inputs.working_directory }}
+ env:
+ PYRE_PACKAGE: ${{ inputs.pyre_package }}
+ REQUIREMENTS_FILE: ${{ inputs.requirements_file }}
+ SETUP_COMMAND: ${{ inputs.setup_command }}
+ SOURCE_DIRECTORIES: ${{ inputs.source_directories }}
+ TAINT_MODELS_PATH: ${{ inputs.taint_models_path }}
+ steps:
+ - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
+
+ - uses: actions/setup-python@v5
+ with:
+ python-version: ${{ inputs.python_version }}
+
+ - name: Install dependencies
+ shell: bash
+ run: |
+ set -euo pipefail
+ python -m pip install --upgrade pip
+ if [ -n "${REQUIREMENTS_FILE}" ]; then
+ python -m pip install -r "${REQUIREMENTS_FILE}"
+ fi
+ if [ -n "${SETUP_COMMAND}" ]; then
+ bash -lc "${SETUP_COMMAND}"
+ fi
+ python -m pip install "${PYRE_PACKAGE}"
+
+ - name: Ensure Pyre configuration
+ shell: bash
+ run: |
+ set -euo pipefail
+ if [ -f ".pyre_configuration" ]; then
+ echo "using repository Pyre configuration"
+ exit 0
+ fi
+ python - <<'PY'
+ import json
+ import os
+
+ sources = [
+ item.strip()
+ for item in os.environ["SOURCE_DIRECTORIES"].split(",")
+ if item.strip()
+ ]
+ if not sources:
+ sources = ["."]
+ with open(".pyre_configuration", "w", encoding="utf-8") as fh:
+ json.dump({"source_directories": sources}, fh, indent=2)
+ fh.write("\n")
+ PY
+
+ - name: Run Pysa
+ shell: bash
+ run: |
+ set -euo pipefail
+ mkdir -p pysa-results
+ args=(analyze --save-results-to pysa-results)
+ if [ -n "${TAINT_MODELS_PATH}" ]; then
+ if [ -d "${TAINT_MODELS_PATH}" ]; then
+ args+=(--taint-models-path "${TAINT_MODELS_PATH}")
+ else
+ echo "::warning::Pysa taint model path '${TAINT_MODELS_PATH}' does not exist; running with bundled models only."
+ fi
+ fi
+ pyre "${args[@]}" | tee pysa-output.txt
+
+ - name: Upload Pysa output
+ if: ${{ always() }}
+ uses: actions/upload-artifact@v4
+ with:
+ name: pysa-output
+ path: |
+ ${{ inputs.working_directory }}/pysa-output.txt
+ ${{ inputs.working_directory }}/pysa-results/**
+ if-no-files-found: ignore
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -54,6 +54,7 @@
- `codex-ci-triage.yml` triages a specific failed Actions run.
- `codex-post-merge-verify.yml` checks default-branch health after merges.
- `codex-label-churn-audit.yml` audits PR label mutation loops.
+- `pysa.yml` runs Pyre/Pysa taint analysis for Python repos.
Each template expects an `OPENAI_API_KEY` repository secret. Repositories that
need stronger, repo-specific behavior should copy the matching prompt from
@@ -147,6 +148,40 @@
require_agents: true+### Pysa Static Analysis
+
+Use .github/workflows/pysa.yml to add Pyre/Pysa taint analysis to Python
+repositories. Downstream repos can adopt it from the workflow template picker or
+with:
+
+```yaml
+name: Pysa static analysis
+
+on:
- pull_request:
- paths:
-
- "**/*.py" -
- "pyproject.toml" -
- "requirements*.txt" -
- ".pyre_configuration*" -
- ".pysa/**" - workflow_dispatch:
+permissions:
- contents: read
+jobs:
- pysa:
- uses: evalops/.github/.github/workflows/pysa.yml@main
- with:
-
source_directories: "." -
taint_models_path: ".pysa"
+```
+
+Repos with custom dependency bootstrapping can pass requirements_file or
+`setup_command`. Repos without committed Pyre configuration get a minimal
+generated `.pyre_configuration` from `source_directories`.
+
Service Catalog
services.yaml is intentionally lightweight. It should answer:
</details>
<sub>You can send follow-ups to the cloud agent <a href="https://cursor.com/agents/bc-20c898b8-375e-474c-bbf5-e196ad91820a">here</a>.</sub>
<!-- BUGBOT_AUTOFIX_REVIEW_FOOTNOTE_END -->
<sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit de1e065aa9d9bb35a88f0dfb2fe8a7c793dbc31e. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup>

Summary
Test plan
ruby -e 'require "yaml"; ARGV.each { |f| YAML.load_file(f); puts "ok #{f}" }' .github/workflows/pysa.yml .github/workflow-templates/pysa.yml\n-ruby -e 'require "json"; ARGV.each { |f| JSON.parse(File.read(f)); puts "ok #{f}" }' .github/workflow-templates/pysa.properties.json\n-ruby -Itest -e 'ARGV.each { |path| require "./#{path}" }' test/*_test.rb\n-git diff --check