Skip to content

Commit f9b67cc

Browse files
committed
ci: make the stale-secrets check able to actually detect stale secrets
The job's own header claims it catches "drift between the secrets and the working tree ... if the maintainer edited src/*.rs locally and forgot to re-pack + refresh the GitHub secrets". It could not. It runs on a fresh checkout where src/* is gitignored and therefore absent, so `verify --from-env` unpacks the secrets and compares them against themselves - self-consistent by construction, and green no matter how old they are. That gap is not hypothetical. A fix to src/fast_uf.rs sat local-only for over four hours today while this job reported success on every push: the secrets were last written at 12:46 and the edit was made at 17:04, so CI and every wheel it would have built were compiled from source nobody was testing against. Anchor the core's identity in something that travels with a commit. `pack` now writes rust_core.sha256 - tracked, unlike src/* - and the new `check-manifest` subcommand rebuilds the archive from the restored tree and compares. In CI that runs after `unpack --from-env`, so the restored tree hashes to whatever the secrets actually hold: if they are older than the last pack the digests differ and the job fails with the exact commands to fix it. Verified both directions locally: matching tree exits 0; appending one comment line to src/fast_uf.rs makes it exit 1 with the mismatch report. Blast radius is small on purpose - check-manifest gates nothing but this advisory job, and release-build's publish path still keys off `verify`.
1 parent 61158de commit f9b67cc

3 files changed

Lines changed: 64 additions & 0 deletions

File tree

.github/workflows/stale-secrets.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,15 @@ jobs:
6060
# deterministic (fixed mtime, sorted names) so a matching hash
6161
# is a real guarantee that the secrets are self-consistent.
6262
python scripts/pack_rust_core.py verify --from-env
63+
# `verify` above proves the secrets are self-consistent, which on a
64+
# fresh checkout is all it CAN prove: src/* is gitignored and absent
65+
# until the line above restores it, so the comparison is the secrets
66+
# against themselves. It cannot see failure mode 2 in this file's
67+
# header - a maintainer who edited src/, re-packed, and never ran
68+
# `gh secret set`. That drift is exactly what shipped a local fix
69+
# nowhere for hours.
70+
#
71+
# rust_core.sha256 IS tracked, so it travels with the commit. Compare
72+
# the restored tree against it: if the secrets are older than the last
73+
# pack, the digests differ and this fails with instructions.
74+
python scripts/pack_rust_core.py check-manifest

rust_core.sha256

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
c3bea92be184174a01d7e3b6a91e081d2ec648c558210126332408a0695b8825

scripts/pack_rust_core.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,12 @@
7171
REPO_ROOT = pathlib.Path(__file__).resolve().parent.parent
7272
SRC_DIR = REPO_ROOT / "src"
7373

74+
# Tracked digest of the packed core. `src/*` is gitignored, so this file is the
75+
# only record of the core's identity that actually travels with a commit - and
76+
# the only thing CI can compare the restored secrets against. See
77+
# cmd_check_manifest for why stale-secrets-check cannot do this on its own.
78+
MANIFEST = REPO_ROOT / "rust_core.sha256"
79+
7480
# Deterministic archives: identical sources must produce an identical base64
7581
# string, so an unchanged core does not churn the secrets on every pack.
7682
FIXED_MTIME = 0
@@ -222,6 +228,45 @@ def cmd_pack(args) -> int:
222228
f"Add {ENV_PREFIX}{MIN_CHUNKS + 1}..{ENV_PREFIX}{n} in repository settings; "
223229
"the CI restore step reads chunks until the first gap, so it adapts automatically."
224230
)
231+
MANIFEST.write_text(digest + "\n", encoding="ascii")
232+
print(f"\nWrote {MANIFEST.name} - COMMIT IT in the same change as the secret refresh.")
233+
return 0
234+
235+
236+
def cmd_check_manifest(args) -> int:
237+
"""Fail if src/ does not hash to the digest recorded in the tracked manifest.
238+
239+
Closes the hole `stale-secrets-check` cannot: that job runs on a fresh
240+
checkout, where `src/*` is gitignored and absent, so `verify` compares the
241+
unpacked secrets against themselves and is self-consistent by construction.
242+
It therefore cannot see a maintainer who edited src/, re-packed, and never
243+
ran `gh secret set` — the exact drift its docstring claims to catch, and the
244+
one that silently kept a local fix out of CI and the wheels for hours.
245+
246+
Run this *after* `unpack --from-env` in CI: the restored tree hashes to what
247+
the secrets actually hold, and the manifest records what the last `pack`
248+
produced. A mismatch means the two have diverged.
249+
"""
250+
if not MANIFEST.is_file():
251+
print(f"{MANIFEST.name} is missing — run `pack_rust_core.py pack` and commit it.")
252+
return 1
253+
expected = MANIFEST.read_text(encoding="ascii").strip()
254+
actual = hashlib.sha256(_build_archive()).hexdigest()
255+
print(f"manifest sha256: {expected}")
256+
print(f"src/ sha256: {actual}")
257+
if expected != actual:
258+
print(
259+
"\nMISMATCH: the Rust core in src/ is not the one the manifest records.\n"
260+
"In CI this means the RUST_SRC_B64_* secrets are stale: someone packed a\n"
261+
"change and never uploaded it, so CI and every released wheel are built\n"
262+
"from older source than the manifest claims. Re-run:\n"
263+
" python scripts/pack_rust_core.py pack\n"
264+
" for i in $(seq 1 12); do "
265+
"gh secret set RUST_SRC_B64_$i < .secrets/RUST_SRC_B64_$i.txt; done\n"
266+
"and commit the refreshed manifest."
267+
)
268+
return 1
269+
print("OK - src/ matches the committed manifest.")
225270
return 0
226271

227272

@@ -271,6 +316,12 @@ def main(argv=None) -> int:
271316
p.add_argument("--out", default=".secrets", help="output directory (default: .secrets)")
272317
p.set_defaults(func=cmd_pack)
273318

319+
p = sub.add_parser(
320+
"check-manifest",
321+
help="fail if src/ does not hash to the committed rust_core.sha256",
322+
)
323+
p.set_defaults(func=cmd_check_manifest)
324+
274325
p = sub.add_parser("verify", help="check packed chunks match the working tree")
275326
p.add_argument("--in", dest="inp", default=".secrets", help="directory holding the chunk files")
276327
p.add_argument("--from-env", action="store_true", help="read chunks from RUST_SRC_B64_* env vars")

0 commit comments

Comments
 (0)