Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
68 changes: 68 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Dependabot, watching every manifest the workspace ships (#291).
#
# The `pip` ecosystem works per manifest directory, and this workspace is six
# distributions: the root plus five under packages/. A directory not listed here is a
# distribution whose dependencies update without review -- tests/test_security_scans.py
# pins the full set so adding a seventh distribution means adding it here or failing CI.
# Updates are grouped (one PR per week per ecosystem, not one per package) because this
# is a solo-maintained repo: review bandwidth is the scarce resource, and a wall of
# single-package PRs is how "ignore Dependabot" becomes the policy.
version: 2
updates:
- package-ecosystem: pip
directory: /
schedule:
interval: weekly
groups:
python-dependencies:
patterns: ["*"]

- package-ecosystem: pip
directory: /packages/keel-core
schedule:
interval: weekly
groups:
python-dependencies:
patterns: ["*"]

- package-ecosystem: pip
directory: /packages/keel-broker-api
schedule:
interval: weekly
groups:
python-dependencies:
patterns: ["*"]

- package-ecosystem: pip
directory: /packages/keel-broker-coinbase
schedule:
interval: weekly
groups:
python-dependencies:
patterns: ["*"]

- package-ecosystem: pip
directory: /packages/keel-broker-fake
schedule:
interval: weekly
groups:
python-dependencies:
patterns: ["*"]

- package-ecosystem: pip
directory: /packages/keel-broker-robinhood
schedule:
interval: weekly
groups:
python-dependencies:
patterns: ["*"]

# The scan and CI workflows pin their actions like any other dependency; unwatched,
# they age into exactly the advisory-visibility problem #291 exists to close.
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
groups:
github-actions:
patterns: ["*"]
5 changes: 5 additions & 0 deletions .github/workflows/code-quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ name: Code Quality

# SonarQube (static analysis + coverage) and Snyk (dependency vulnerabilities).
#
# NOTE (#291): the tokenless baseline now lives in `security.yml` -- Dependabot, a weekly
# `pip-audit` over the exported lock, and CodeQL, all on GITHUB_TOKEN alone, always on.
# This workflow is the OPTIONAL enhanced tier: SonarQube and Snyk run only if the two
# secrets below are ever created, and its `preflight` skips them cleanly (never red) while
# they are not. The two workflows disagree about nothing: baseline there, depth here.
# ┌─ BEFORE THIS CAN RUN ─────────────────────────────────────────────────────────────────────────
# │ Two repository secrets must exist. As of 2026-08-16, NEITHER DOES -- `gh secret list` is empty
# │ at both repo and org level:
Expand Down
84 changes: 84 additions & 0 deletions .github/workflows/security.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# 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, always, on every push 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 push, on PRs, and weekly on the
# calendar, because CVEs are published against pinned versions whether or not anyone
# pushes) without gating merges.
name: security

on:
workflow_dispatch:
push:
branches: [main]
pull_request:
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@v4

- name: Install uv
uses: astral-sh/setup-uv@v6

# 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 six 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-fake
--no-emit-package keel-broker-robinhood
> 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@v4

- uses: github/codeql-action/init@v3
with:
languages: python

- uses: github/codeql-action/analyze@v3
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ keel-broker-robinhood = { workspace = true }
dev = [
"mypy>=1.18.0",
"pytest>=9.1.1",
# tests/test_security_scans.py parses .github/dependabot.yml directly, so the test
# suite imports pyyaml itself rather than riding keel-core's transitive copy (the
# rule the [project] comment above states for `keel/`, applied where it now applies).
"pyyaml>=6.0.2",
# Only `code-quality.yml` needs this: SonarQube reports coverage as a first-class metric and
# has nothing to show without a coverage XML to import. It is deliberately NOT wired into the
# default `pytest` invocation -- `ci.yml` and `release.yml` still run a plain `pytest -q`,
Expand Down
Loading
Loading