Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
5d0a7cc
use uv and githubkit
JacobCoffee Nov 5, 2025
f1d21a8
missed file removal
JacobCoffee Nov 5, 2025
096f40a
add playwright basic
JacobCoffee Nov 5, 2025
b4881e6
remove stringent patch version
JacobCoffee Nov 5, 2025
6cf47e4
add ci for tests/linting checks
JacobCoffee Nov 5, 2025
023d143
move cron CI to uv
JacobCoffee Nov 5, 2025
c8b9c8e
playwright initial base + otp
JacobCoffee Nov 6, 2025
6807202
playwright comment grab
JacobCoffee Nov 6, 2025
efdfa64
add post comments
JacobCoffee Nov 7, 2025
236daef
organize things into module, merge comments tests
JacobCoffee Nov 7, 2025
818f156
fix missing f
JacobCoffee Nov 19, 2025
cd31843
add helpers
JacobCoffee Nov 19, 2025
9cba6eb
add monitoring ci
JacobCoffee Nov 19, 2025
8bbbb61
placeholder for playright things
JacobCoffee Nov 19, 2025
6ef021a
set username inside clien
JacobCoffee Nov 19, 2025
7df217f
parse comments as commands
JacobCoffee Nov 19, 2025
aee0db6
idea file
JacobCoffee Nov 19, 2025
1e08066
run linting with rules actually enabled
JacobCoffee Nov 19, 2025
6f87c48
add some quick targets for local dev
JacobCoffee Nov 19, 2025
9e5e465
lint
JacobCoffee Nov 19, 2025
c010bec
add gha
JacobCoffee Nov 19, 2025
7b427e3
source state for exampl
JacobCoffee Nov 19, 2025
42558b5
lint
JacobCoffee Nov 19, 2025
ed7393f
placeholder file
JacobCoffee Nov 19, 2025
dff3b1a
dont process bot comments
JacobCoffee Nov 19, 2025
93350ef
dont print but log
JacobCoffee Nov 20, 2025
f6ab771
swap to log instead of print, run lint/fmt
JacobCoffee Nov 20, 2025
2ebdb8a
update tests pasing, source tests for new files,
JacobCoffee Nov 20, 2025
628fd7a
add some info to help people onboard
JacobCoffee Nov 20, 2025
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
17 changes: 17 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# GitHub App credentials (for API-capable operations)
GH_CLIENT_ID="123456"
GH_CLIENT_PRIVATE_KEY="base64...your...pem...keyfile"
GH_AUTH_TOKEN="ghp_123456"

# Playwright bot credentials (for browser automation)
GH_BOT_USERNAME=PSRT-GHSA-Automation
GH_BOT_PASSWORD=<secure-password>
GH_BOT_OTP_SECRET=<optional-2fa-secret> # Key used to generate OTP codes

# CVE API credentials
CVE_USERNAME="user@example.org"
CVE_API_KEY="123456"
CVE_ENV="testproddev"

# Sentry
SENTRY_DSN=
63 changes: 63 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]


jobs:
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5

- name: Run ruff lint
uses: astral-sh/ruff-action@v3

- name: Run ruff format check
uses: astral-sh/ruff-action@v3
with:
args: "format --check --diff"

test:
name: Test on Python ${{ matrix.python-version }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.14"]

steps:
- uses: actions/checkout@v5

- name: Set up uv
uses: astral-sh/setup-uv@v6
with:
enable-cache: true

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
run: uv sync --locked --dev --no-editable

- name: Install Playwright browsers
run: uv run playwright install --with-deps chromium

- name: Run tests
run: uv run pytest tests/ -v --tb=short

- name: Upload coverage reports
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results-${{ matrix.python-version }}
path: |
.coverage
htmlcov/
retention-days: 30
32 changes: 20 additions & 12 deletions .github/workflows/cron.yml
Original file line number Diff line number Diff line change
@@ -1,24 +1,32 @@
name: "PSRT GHSA Bot"
name: "PSRT GHSA Cron Bot"

on:
workflow_dispatch:
schedule:
- cron: "0 * * * *"

jobs:
cron:
runs-on: ubuntu-latest
name: "Cron"
name: "Run PSRT Advisory Bot"
steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
- uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # v4
- uses: actions/checkout@v5

- name: Set up uv
uses: astral-sh/setup-uv@v6
with:
python-version: 3.12
cache: pip
cache-dependency-path: |
requirements.txt
- run: |
python -m pip install -r requirements.txt
- run: |
python app.py
enable-cache: true

- name: Set up Python
uses: actions/setup-python@v6
with:
python-version-file: "pyproject.toml"

- name: Install dependencies
run: uv sync --locked --no-editable --no-dev

- name: Run bot
run: uv run python src/psrt_ghsa_bot/app.py
env:
GH_CLIENT_ID: ${{ vars.GH_CLIENT_ID }}
GH_CLIENT_SECRET: ${{ secrets.GH_CLIENT_SECRET }}
Expand Down
33 changes: 33 additions & 0 deletions .github/workflows/health-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: "Health Check"

on:
workflow_dispatch:
schedule:
- cron: "15 * * * *"

jobs:
monitor:
runs-on: ubuntu-latest
name: "Monitor Workflow Health"
steps:
- uses: actions/checkout@v5

- name: Set up uv
uses: astral-sh/setup-uv@v6
with:
enable-cache: true

- name: Set up Python
uses: actions/setup-python@v6
with:
python-version-file: "pyproject.toml"

- name: Install dependencies
run: uv sync --locked --no-editable --no-dev

- name: Check workflow status and report to Sentry
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
GITHUB_REPOSITORY: ${{ github.repository }}
run: uv run python src/psrt_ghsa_bot/health_check.py
69 changes: 69 additions & 0 deletions .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
name: "PSRT GHSA Playwright Bot"

on:
workflow_dispatch:
schedule:
- cron: "*/5 * * * *"

jobs:
process-comments:
runs-on: ubuntu-latest
name: "Process GHSA Comments"
steps:
- uses: actions/checkout@v5
with:
token: ${{ secrets.GITHUB_TOKEN }}

- name: Set up uv
uses: astral-sh/setup-uv@v6
with:
enable-cache: true

- name: Set up Python
uses: actions/setup-python@v6
with:
python-version-file: "pyproject.toml"

- name: Install dependencies
run: uv sync --locked --no-editable --no-dev

- name: Install Playwright browsers
run: uv run playwright install --with-deps chromium

- name: Restore state from cache
id: cache-state
uses: actions/cache/restore@v4
with:
path: state.json
key: playwright-state-${{ github.run_id }}
restore-keys: |
playwright-state-

- name: Process comments
run: uv run python -m psrt_ghsa_bot.comment_processor
env:
GH_CLIENT_ID: ${{ vars.GH_CLIENT_ID }}
GH_CLIENT_SECRET: ${{ secrets.GH_CLIENT_SECRET }}
GH_CLIENT_PRIVATE_KEY: ${{ secrets.GH_CLIENT_PRIVATE_KEY }}
CVE_USERNAME: ${{ vars.CVE_USERNAME }}
CVE_API_KEY: ${{ secrets.CVE_API_KEY }}
CVE_ENV: ${{ vars.CVE_ENV }}
GH_BOT_USERNAME: ${{ vars.GH_BOT_USERNAME }}
GH_BOT_PASSWORD: ${{ secrets.GH_BOT_PASSWORD }}
GH_BOT_OTP_SECRET: ${{ secrets.GH_BOT_OTP_SECRET }}

- name: Save state to cache
if: always()
uses: actions/cache/save@v4
with:
path: state.json
key: playwright-state-${{ github.run_id }}

- name: Commit state file
if: always()
run: |
git config user.name "PSRT-GHSA-Automation[bot]"
git config user.email "bot@python.org"
git add state.json
git diff --quiet || git commit -m "Update comment processing state [skip ci]"
git push || true
13 changes: 13 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -160,3 +160,16 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

# Playwright authentication state
playwright/.auth/
playwright-state/
**/playwright-state/

# Playwright videos and traces
playwright-videos/
playwright-traces/
*.webm
trace.zip
debug_*.png
tests/PLAYWRIGHT_FULL.test
1 change: 1 addition & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.14
45 changes: 45 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
.DEFAULT_GOAL:=help
.ONESHELL:

help: ## Display this help text for Makefile
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z0-9_-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)

upgrade: ## Upgrade all dependencies to the latest stable versions
@uv lock --upgrade
@echo "=> Dependencies Updated"

lint: ## Lint the code
@uv run ruff check --fix --unsafe-fixes .

fmt: ## Format the code
@uv run ruff format .

fmt-check: ## Runs Ruff format in check mode (no changes)
@uv run --no-sync ruff format --check .

type-check: ## Run type-checking
@uv run ty check

ty: type-check ## Alias for type-check

check: lint fmt type-check ## Run all checks except tests

test: ## Run tests
@test -f tests/PLAYWRIGHT_FULL.test && uv run playwright install --with-deps chromium 2>/dev/null || true
@uv run pytest

ci: lint fmt type-check test ## Run everything

app: ## Run the app
@uv run python app.py

### --- Bot Things
### These all reequire .env file with the vars set based on .env.example!
cron-run: ## Run the cron bot (app.py)
@uv run python -m psrt_ghsa_bot.app

playwright-run: ## Run playwright bot
@uv run python -m psrt_ghsa_bot.comment_processor

health-check: ## Run health check
@uv run python -m psrt_ghsa_bot.health_check
67 changes: 67 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,70 @@
# PSRT GHSA Bot

Bot which adds the PSRT GitHub team (`python/psrt`) and CVE IDs to GitHub Security Advisories.

## Moving Pieces

### GitHub Actions

- The cron bot runs off of [`.github/workflows/cron.yml`](.github/workflows/cron.yml) and runs at the top of each hour.
- Calls [src/psrt_ghsa_bot/app.py](src/psrt_ghsa_bot/app.py)
- Fetches open GHSAs from all installed orgs/repos
- Adds PSRT team to GHSAs without it
- Assigns CVE IDs to draft GHSAs without one
- The Playwright bot runs off of [`.github/workflows/playwright.yml`](.github/workflows/playwright.yml) and runs every 5 minutes.
- Calls [src/psrt_ghsa_bot/comment_processor.py](src/psrt_ghsa_bot/comment_processor.py)
- Reads GHSA comments via Playwright (no API available)
- Parses `@<bot-username>` commands, executes if authorized
- Posts responses, tracks state in `state.json`
- Health checks are done via [`.github/workflows/health-check.yml`](.github/workflows/health-check.yml) and runs every 15 minutes.
- It checks the status using the `gh` CLI and reports to Sentry if the bot is not healthy via Sentry cron monitors.

### Why Playwright?

The GHSA API is limited and has not really been developed in awhile. As such, it is missing a lot of features
like:
- Commenting on GHSAs
- Adding teams to GHSAs
- Assigning CVE IDs to GHSAs
- Removing temporary forks generated inside a GHSA that collaboraters use fro remediation
- No webhooks to respond to things.. so we do the GHA polling thing...

That's why there is this weird split between the cron.yml and playwright.yml. As API things
are added, we can move more into app.py/cron.yml and remove the playwright stuff (gladly!)

## Development

Uses `uv` for dependency management and `pytest` for testing.

### Setup

Make sure you have `uv` installed athttps://docs.astral.sh/uv/getting-started/installation/
Quickly, for Linux/macOS:
```shell
curl -LsSf https://astral.sh/uv/install.sh | sh
```
or (not recommended):
```shell
pipx install uv
```

Afterwards, you can use `make` to run the commands in the [`Makefile`](Makefile).
- `make upgrade` - Upgrade all dependencies to the latest stable versions.

Every time you run `uv run` or anything it automatically installs/syncs the dependencies
and it's near-instant so there is no `make install` or anything.

### Tests

Only unique things here are `PLAYWRIGHT_FULL.test`, which can can see more about in [`PLAYWRIGHT_FULL.test.example`](tests/PLAYWRIGHT_FULL.test.example).
This just tells the [`Makefile`](Makefile) target `make test` to run `playwright install` before running the tests
and then enables some of the skipped tests. These tests assume a test organization and all the setup behind that
because it is an integration test and will comment on and read a GHSA advisory.

### Scripts

There is a [`scripts/`](scripts/) directory with some local dev scripts, namely one that will
set up an organization with all the things needed to develop (TODO: it doesn't actually do anything yet.)

The idea behind the bootstrap_org.py is that it will:
- Take your org
Loading