Skip to content
Draft
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
2 changes: 1 addition & 1 deletion .github/workflows/branch-leak-scan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ jobs:
run: |
if [ -n "$MEFOR_FORBIDDEN_TOKENS" ]; then
export MEFOR_REQUIRE_TOKENS=1
export MEFOR_MIN_DETECTORS=names=7,estate=13,site_prefixes=1
export MEFOR_MIN_DETECTORS=names=8,estate=14,site_prefixes=2
echo "token list loaded from the MEFOR_FORBIDDEN_TOKENS secret (fail-closed, per-section floor)."
else
# A push event cannot come from a fork, so unlike security.yml there is no legitimate
Expand Down
11 changes: 6 additions & 5 deletions .github/workflows/dependabot-lock-resync.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
name: Dependabot lock resync

# Re-exports the SIX committed "uv export" artifacts (requirements.lock +
# Re-exports the SEVEN committed "uv export" artifacts (requirements.lock +
# docker/locks/requirements-core.lock + docker/locks/requirements-sqlserver.lock + the HASHLESS
# constraints.lock + the two PEP 735 CI-toolchain locks ci/locks/ci-scanners.lock and
# ci/locks/ci-quality.lock) on a Dependabot PR that touched uv.lock / pyproject.toml, and commits them
# ci/locks/ci-quality.lock, and the release-signing lock ci/locks/release-tools.lock) on a Dependabot PR that touched uv.lock / pyproject.toml, and commits them
# back to the PR branch so the DEP-1 drift gate (security.yml -> pip-audit job, step "Check the
# lockfile is in sync with pyproject (DEP-1)") goes green WITHOUT a human re-export.
#
# WHY: the native "uv" Dependabot ecosystem regenerates uv.lock + pyproject.toml in its PR, but NOT
# the exported lock artifacts. The DEP-1 gate re-runs "uv lock --check" + all six "uv export"s and
# the exported lock artifacts. The DEP-1 gate re-runs "uv lock --check" + all seven "uv export"s and
# "git diff --exit-code"s the result, so a Dependabot uv PR would otherwise leave the exports stale
# and red the gate. This workflow runs the IDENTICAL commands and pushes the refreshed exports onto
# the Dependabot branch.
Expand Down Expand Up @@ -137,6 +137,7 @@ jobs:
# four above: the gate diffs them, so the bot must re-export them or the PR has no path green.
uv export --only-group ci-scanners --format requirements.txt -o ci/locks/ci-scanners.lock
uv export --only-group ci-quality --format requirements.txt -o ci/locks/ci-quality.lock
uv export --only-group release-tools --format requirements.txt -o ci/locks/release-tools.lock

- name: Commit and push the resynced locks
if: steps.creds.outputs.present == 'true'
Expand All @@ -148,11 +149,11 @@ jobs:
set -euo pipefail
git config user.name 'dependabot[bot]'
git config user.email '49699333+dependabot[bot]@users.noreply.github.com'
if git diff --quiet -- requirements.lock docker/locks/requirements-core.lock docker/locks/requirements-sqlserver.lock constraints.lock ci/locks/ci-scanners.lock ci/locks/ci-quality.lock; then
if git diff --quiet -- requirements.lock docker/locks/requirements-core.lock docker/locks/requirements-sqlserver.lock constraints.lock ci/locks/ci-scanners.lock ci/locks/ci-quality.lock ci/locks/release-tools.lock; then
echo 'Exported lock files already in sync; nothing to push.'
exit 0
fi
git add requirements.lock docker/locks/requirements-core.lock docker/locks/requirements-sqlserver.lock constraints.lock ci/locks/ci-scanners.lock ci/locks/ci-quality.lock
git add requirements.lock docker/locks/requirements-core.lock docker/locks/requirements-sqlserver.lock constraints.lock ci/locks/ci-scanners.lock ci/locks/ci-quality.lock ci/locks/release-tools.lock
git commit -m 'chore(deps): resync exported lock files (DEP-1)'
# Push with the persisted App-token credential (NOT GITHUB_TOKEN). An App-token push emits a
# synchronize event so the required checks (DEP-1, ci.yml) re-run on the new commit; a
Expand Down
33 changes: 25 additions & 8 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -245,14 +245,31 @@ jobs:
run: |
# PINNED — the sharpest of these installs: this step is unconditional (every tag AND every
# dispatch) and the very next command signs the release artifacts with the job's OIDC identity,
# the same identity that publishes to PyPI below. 4.4.0, not the newer 4.5.0: .github/
# dependabot.yml sets a 5-day supply-chain cooldown to dodge a package compromised shortly
# after publish, and 4.5.0 is <48h old — hard-pinning the SIGNING toolchain to a fresher
# artifact than the repo's own routine-update policy allows inverts that policy at the highest-
# privilege point in the pipeline. Re-evaluate to 4.5.0 once it has aged past the window.
# NOTE: this pins the TOP only; sigstore's ~30 transitive deps still float at signing time.
# Closing the Scorecard alert outright needs the hashed release-tools lock (ADR 0034 option B).
python -m pip install "sigstore==4.4.0"
# the same identity that publishes to PyPI below — so ANY code that executes here runs with
# the credential that signs the artifacts. A backdoored wheel produced at this point carries a
# VALID Sigstore bundle and VALID SLSA provenance, and every downstream verifier reports
# success: the compromise defeats the controls it is standing next to.
#
# HASHED LOCK, not an inline pin (BACKLOG #332). The previous form was
# `pip install "sigstore==4.4.0"`, which had TWO defects that a version bump does not fix:
# 1. It pinned the TOP package only. ~30 transitive dependencies still floated, unhashed,
# resolved at signing time.
# 2. NO Dependabot ecosystem parses an inline `pip install X==Y` inside a workflow `run:`
# block (.github/dependabot.yml registers uv, github-actions, npm). So the pin had no
# updater, no trigger and no owner — its own comment said "re-evaluate once it has aged
# past the window" and nothing would ever have prompted that. It was 19 days overdue when
# this landed. tests/test_ci_venv_pinning.py words the class: "a stale pin rots invisibly
# and a DELETED pin is invisible twice over."
# Routing it through `uv.lock` fixes both: the transitives are hash-pinned, and the group is
# under the `uv` ecosystem Dependabot already watches.
#
# ON THE VERSION: the lock resolves sigstore 4.5.0, which the old inline pin deliberately
# avoided. That choice was CORRECT when made — dependabot.yml sets a 5-day supply-chain
# cooldown and 4.5.0 was then <48h old, so pinning the SIGNING toolchain to a fresher artifact
# than the repo's own update policy allows would have inverted that policy at its
# highest-privilege point. MEASURED: 4.5.0 published 2026-07-28T07:34:00Z, so the window
# closed 2026-08-02. The objection is SPENT, not overridden.
python -m pip install --require-hashes -r ci/locks/release-tools.lock
# Sign the wheel + sdist AND the SBOM + VEX, so an operator can verify the provenance of the
# bill-of-materials and the exploitability assessment too — not just the code artifacts (ADR 0149).
python -m sigstore sign dist/*.tar.gz dist/*.whl \
Expand Down
9 changes: 7 additions & 2 deletions .github/workflows/security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,12 @@ jobs:
# stay out of the four exports above (and out of the SBOM / image locks / audited runtime).
uv export --only-group ci-scanners --format requirements.txt -o ci/locks/ci-scanners.lock
uv export --only-group ci-quality --format requirements.txt -o ci/locks/ci-quality.lock
git diff --exit-code -- requirements.lock docker/locks/requirements-core.lock docker/locks/requirements-sqlserver.lock constraints.lock ci/locks/ci-scanners.lock ci/locks/ci-quality.lock
# The RELEASE SIGNING toolchain (BACKLOG #332). Same mechanism as the two above, and it is
# here rather than inline in release.yml because release.yml runs ONLY on a tag push -- so a
# drift that lived there would first be observed during a release. Exported on every
# security run instead, where it is cheap to catch.
uv export --only-group release-tools --format requirements.txt -o ci/locks/release-tools.lock
git diff --exit-code -- requirements.lock docker/locks/requirements-core.lock docker/locks/requirements-sqlserver.lock constraints.lock ci/locks/ci-scanners.lock ci/locks/ci-quality.lock ci/locks/release-tools.lock
- name: Install from the hashed lockfile (DEP-1)
run: |
# --require-hashes enforces a hash for every requirement (the lockfile carries them): a
Expand Down Expand Up @@ -671,7 +676,7 @@ jobs:
# the env var first, so that file would never be read -- it would only drop the full real
# token list into the job workspace for every later step to see.
export MEFOR_REQUIRE_TOKENS=1
export MEFOR_MIN_DETECTORS=names=7,estate=13,site_prefixes=1
export MEFOR_MIN_DETECTORS=names=8,estate=14,site_prefixes=2
echo "token list loaded from the MEFOR_FORBIDDEN_TOKENS secret (fail-closed, per-section floor)."
elif [ "$IS_FORK_PR" = "true" ]; then
echo "fork PR -- the secret is unavailable BY DESIGN; structural-only scan."
Expand Down
Loading
Loading