Skip to content

Security: spec-sync workflow is a supply-chain risk (writable + untrusted upstream) #92

Description

@TexasCoding

From Wave 5 security audit, finding F-O-01. Severity: high.

Threat model

The weekly `.github/workflows/spec-sync.yml` cron:

  • Has `permissions: contents: write, pull-requests: write` (spec-sync.yml:9-11).
  • Calls `scripts/sync_spec.py` which downloads YAML from `https://docs.kalshi.com/openapi.yaml\` and `/asyncapi.yaml` with no signature/hash pinning (`scripts/sync_spec.py:13-17`).
  • Feeds the YAML to `scripts/generate.py` (`datamodel-code-generator`), which writes Python that the test suite imports (`spec-sync.yml:135-137`).
  • Runs full `uv run pytest tests/ -v` against the generated code (`spec-sync.yml:147-149`).
  • Opens a PR via `peter-evans/create-pull-request`.

If a maintainer ever enables auto-merge on that PR, or hand-merges without auditing `_generated/models.py`, a compromise of upstream (MITM on `docs.kalshi.com`, or a malicious change merged into Kalshi's published spec) becomes a path to PyPI-published malicious code via the existing release workflow (release.yml triggers on tag push).

Mitigation options

  • (a) Pin a published checksum/signature for the spec files and verify before regen. Kalshi would need to publish a checksum or sigstore bundle alongside the spec; this is upstream-dependent.
  • (b) Keep auto-PR but require human approval plus an explicit "I read `_generated/models.py`" check (e.g., a checkbox in the PR template the workflow enforces with a status check).
  • (c) Drop the writable workflow entirely: gate the PR generation behind a workflow with only `contents: read` and post the diff as an issue comment, never as a writable branch. A maintainer manually opens a PR for genuine changes.

Option (c) is the safest. (b) is the smallest change. The existing SHA-pinning of third-party actions in this workflow is the right pattern — extend the principle to the content being merged.

Out of scope here

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinginfraInfrastructure/tooling

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions