Skip to content

Security audit · pull_request · fix/1004-sim-avg-r-empty-side #797

Security audit · pull_request · fix/1004-sim-avg-r-empty-side

Security audit · pull_request · fix/1004-sim-avg-r-empty-side #797

Workflow file for this run

# The security scans -- configured for a public repo, which means: no tokens (#291).
#
# Why this workflow exists: the pre-launch gate names a gap that has been true since the
# repo opened -- `SONAR_TOKEN` and `SNYK_TOKEN` were names for secrets nobody created, so
# keel had no static analysis or dependency scanning that actually runs. This workflow is
# the tokenless BASELINE: it runs on GITHUB_TOKEN alone, on every pull request and weekly.
# `code-quality.yml` remains the optional enhanced tier (SonarQube + Snyk) for IF those
# tokens are ever created -- its preflight skips cleanly while they are not, so the two
# workflows disagree about nothing: baseline here, optional depth there.
#
# Why these jobs are NOT the `test` job in ci.yml: the `main` ruleset requires the status
# context `test`, which ci.yml produces. A vulnerable dependency or a CodeQL finding is
# information about the state of the world, not a verdict on a proposed change -- a new
# CVE published against an already-pinned version must surface without waiting for a
# merge to blame. So these jobs run alongside (on PRs, and weekly on the calendar, because
# CVEs are published against pinned versions whether or not anyone pushes) without gating
# merges.
name: security
run-name: "Security audit · ${{ github.event_name }} · ${{ github.head_ref || github.ref_name }}"
# ── WHY THIS SKIPS DOCUMENTATION, AND WHY `ci.yml` DELIBERATELY DOES NOT (#644) ──────────────
# This workflow reads no documentation, so a docs-only change can produce no signal from it and
# the run is pure cost. Seven docs-only pull requests merged on 2026-08-30/31 fired it for
# nothing.
#
# ⚠️ DO NOT COPY THIS BLOCK INTO `ci.yml`. keel's documentation is UNDER TEST, on purpose, and
# `ci.yml` is what runs those tests:
#
# * `tests/test_python_floor.py` reads README.md, README.ar.md and docs/desktop-install.md and
# fails when they disagree with the RELEASE's `requires-python`. It exists because five
# places said 3.11 while the wheels required 3.14 (#595/#607).
# * `tests/test_doc_links.py` pins the documented `SHA256SUMS-*` filenames against what
# `release.yml` actually emits -- the docs promised one file, the workflow shipped three
# (#605/#618).
#
# A docs-only pull request is the ONLY kind those pins are for. Filtering docs out of `ci.yml`
# would disable them on exactly the changes they were built to catch.
#
# SAFE HERE for two independent reasons: `main` carries no branch protection, so no check is
# "required" and a skipped run cannot leave a pull request permanently unmergeable; and
# `paths-ignore` applies only to `pull_request` here -- `schedule` fires regardless, so the
# weekly cron below re-scans everything within seven days even on a pathological miss.
#
# `docs/**` sweeps up the experiment drivers and ledgers under `docs/experiments/`. Correct
# here, since this workflow reads none of it; NOT correct for `ci.yml`, where
# `docs/experiments/trials-ledger.jsonl` is a hash-chained artifact the research tests reason
# about.
# ── WHY THERE IS NO `push` TRIGGER ───────────────────────────────────────────────────────────
# Both jobs ran a second time on the merge commit, and the second run cost ~2.5 minutes to
# re-answer a question the pull-request run had answered four minutes earlier: `dependencies`
# audits `uv.lock`, which the merge does not change, and neither job reads anything a merge
# could combine differently. Five merges on 2026-09-01 spent ~12 minutes that way.
#
# ⚠️ THE COST, STATED RATHER THAN DISCOVERED. Code scanning reports alerts against the DEFAULT
# BRANCH, and that baseline is refreshed by analyses OF the default branch -- which now means the
# weekly cron alone. A finding introduced by a merge, or an alert a merge fixes, can therefore
# take up to seven days to appear or clear in the Security tab. Pull-request analysis is
# unaffected and still runs on every change, so nothing reaches `main` unscanned; what is slower
# is the SUMMARY of what is on `main`, not the scanning itself. If that lag ever matters, put
# `push` back rather than shortening the cron -- a daily scan pays most of the cost for a
# fraction of the freshness.
on:
workflow_dispatch:
pull_request:
paths-ignore:
- "docs/**"
- "**/*.md"
- "LICENSE"
schedule:
- cron: "23 4 * * 1"
# Not cancel-in-progress: a cancelled audit run leaves the last completed scan older than
# it needs to be, and these jobs are short enough that waiting beats losing a result.
concurrency:
group: security-${{ github.ref }}
cancel-in-progress: false
permissions:
contents: read
jobs:
dependencies:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v7
- name: Install uv
uses: astral-sh/setup-uv@v7
# Audit the LOCK, not a fresh resolve: `uv export --frozen` emits exactly the pinned
# versions every deployment gets from `uv.lock` as committed (`--frozen` is the same
# never-re-resolve-in-CI stance `code-quality.yml` documents), so those are the
# versions scanned. Unlike the Snyk export there, dev dependencies are INCLUDED
# deliberately: this job gates nothing, so the wider advisory net costs nothing, and
# a CVE in the dev toolchain is still something a contributor's machine runs. Extras
# are included for the same reason; the repo's own eight distributions are excluded
# because they are the code under scan, not third-party dependencies of it.
- name: Export the locked dependency set
run: >
uv export --frozen --all-extras --no-hashes
--no-emit-package keel-trader
--no-emit-package keel-core
--no-emit-package keel-broker-api
--no-emit-package keel-broker-coinbase
--no-emit-package keel-broker-alpaca
--no-emit-package keel-broker-fake
--no-emit-package keel-broker-robinhood
--no-emit-package keel-broker-kraken
> requirements.lock.txt
- name: Audit the locked dependency set
run: uvx pip-audit --requirement requirements.lock.txt
codeql:
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
# CodeQL uploads its findings to the repo's Security tab; without this permission
# the scan runs and its results go nowhere.
security-events: write
contents: read
steps:
- uses: actions/checkout@v7
- uses: github/codeql-action/init@v4
with:
languages: python
- uses: github/codeql-action/analyze@v4