feat(ci): weekly cooldown-gated dependency updates - #13
Closed
JonZeolla wants to merge 2 commits into
Closed
Conversation
There was a problem hiding this comment.
Nice work! 😎
I didn't find anything of concern
List of skipped files due to configuration
Risk: 🟡 Medium
Reviewed with 🤟 by Zenable
Adds the automation that refreshes every pinned dependency in this repo — container image tags, uv.lock files, and GitHub Action SHAs — behind a supply-chain cooldown and an upstream-provenance check. Runs Tuesdays at 03:16 UTC and opens two pull requests. Every adopted version must be at least 7 days old, so a compromised upstream release has time to be noticed by someone else first. Candidates are walked newest-first and the first past the window wins, so a 3-day-old release is skipped while an 8-day-old one is still picked up rather than the pin stalling. Python dependencies get the same window declaratively, via exclude-newer in each project's [tool.uv], which binds a reader's uv sync as well as uv lock --upgrade. GitHub-sourced versions are verified before anything is written: the tag must exist as a ref in the repository it claims to come from, which a fork's tags never do. Actions are hash-pinned by pinact and then checked twice, including a reachability pass pinact has no equivalent for; that pass also fails any uses: that is not SHA-pinned, standing in for a per-PR zizmor gate. supply_chain/ is a trimmed vendoring of zenable_monorepo from next-gen-governance, which is private while this repo is public. Only what these pins need is carried, and the security-relevant logic is kept close to upstream so a fix there ports as a readable diff. Two jobs rather than one so permission-workflows: write is requested in exactly one place, and every workflow-file edit lands in its own PR. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dnn74khJT1rBHyZSgmaroE
The first run of the new update automation, with every bump validated by actually running the lab it touches rather than trusting that it starts. Keycloak 26.7.1 -> 26.7.2 in the a2a and agent-identity rigs. 26.7.3 exists but was 3 days old, so the cooldown correctly held it back. Jaeger all-in-one 1.68.0 -> 1.76.0, held to the 1.x line because Jaeger v2 is a different image with a different CLI. agentgateway stays at v1.4.1 deliberately. v1.5.0 has now cleared the cooldown, but it renames the trace spans this lab teaches: POST /* disappears, delete_session and get_stream become DELETE/GET get-started, and tools/call get-started splits per tool. That invalidates two transcripts and two pieces of navigation prose in agentgateway-mcp.mdx, so it is lab-content work for an author rather than a version bump. update-pins.sh records why, and the pin stays automated so the next run proposes it again. mcp-get-started keeps the fastmcp 4 line from #11 and raises its floor to 4.0.2, resolving against current PyPI rather than the 7-day window. The lab teaches the SEP-2663 tasks extension, which only exists in fastmcp 4 — released days ago — so a cooldown on the lab's own subject leaves the lock unsatisfiable rather than safe. server.py imports fastmcp_tasks directly, so the [tasks] extra stays: base fastmcp pulls only fastmcp-slim. evidence/ regenerated by each lab's own capture-evidence.sh, never by hand. a2a's end-to-end.txt and agent-identity's sdjwt-walkthrough.txt came back byte-identical, and agent-identity reports 22/22 negative tests behaving as documented. ema-mcp passes 7/7. mcp-get-started's evidence is left alone: it was captured on the e2e harness and already records fastmcp 4.0.2. ema-mcp's Keycloak is now pinned by digest. ceposta/keycloak:id-jag is a mutable tag on a third party's personal Docker Hub account with no version and no release feed, so there is nothing for the automation to measure or move it to; the digest at least fixes the bytes. The README files are generated from the better-agentgateway branch in next-gen-governance and exported here, never edited in this repo. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
JonZeolla
force-pushed
the
automated-dependency-updates
branch
from
September 3, 2026 20:29
0e88c6d to
1ce3b13
Compare
2 tasks
Member
Author
|
Superseded by a different approach. Labs is generated from next-gen-governance, so dependency automation belongs there: it can bump the pin, run the labs to regenerate evidence, and re-export the READMEs in one atomic change, with no vendored copy of zenable_monorepo in a public repo. Reimplementing in next-gen-governance; this repo stays a sync target. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds weekly automation that refreshes every pinned dependency here — container image tags,
uv.lockfiles, and GitHub Action SHAs — behind a supply-chain cooldown and an upstream-provenance check, plus the first run of it.Two commits, reviewable separately: the tooling, then the bumps it produced.
What it guarantees
A 7-day cooldown on every adopted version, so a compromised upstream release has time to be noticed by someone else first (context). Candidates are walked newest-first and the first past the window wins — a 3-day-old release is skipped while an 8-day-old one is still picked up, rather than the pin stalling. Python deps get the same window declaratively via
exclude-newer = "7 days"in each[tool.uv], which binds a reader'suv synctoo.Provenance for GitHub-sourced versions. The tag must exist as a ref in the repo it claims to come from — a fork's tags never appear in the parent's refs, which is what the imposter-commit attack lacks, since it references a bare SHA with no ref behind it.
Actions hash-pinned and verified twice.
pinact run -ure-pins past its own cooldown,--check --verify-commentconfirms each version comment still names the pinned SHA, andverify_action_pins.pyconfirms that SHA is reachable from a branch upstream — the check pinact has no equivalent for. It also fails anyuses:that isn't SHA-pinned, standing in for a per-PRzizmorgate.supply_chain/is a trimmed vendoring ofzenable_monorepofrom next-gen-governance, which is private while this repo is public. ~700 lines rather than ~1,300: only the two sources these pins need, with the security-relevant logic kept close to upstream so a fix there ports as a readable diff.The first run
26.7.1 → 26.7.2. 26.7.3 exists but was 3 days old — correctly held.1.68.0 → 1.76.0, held to the 1.x line: Jaeger v2 is a different image with a different CLI.uv.lockexclude-newerEvery bump was validated by running the lab, not by checking that the container starts:
end-to-end.txtregenerated byte-identicalsdjwt-walkthrough.txtbyte-identical/api/services→["agentgateway"]slow_shouttask tool presentevidence/came from each lab's owncapture-evidence.sh.mcp-get-started's is left alone: it was captured on the e2e harness and already records 4.0.2, so overwriting it with laptop output would be a downgrade.agentgateway v1.5.0 is deliberately not taken
It has cleared the cooldown, and the automation will propose it again next run. Read that PR carefully — v1.5.0 renames the trace spans this lab teaches:
POST /*delete_session,get_streamDELETE get-started,GET get-startedtools/call get-startedtools/call get-started_add,..._shoutThat invalidates two recorded transcripts, the sentence about a tool call naming its target, and the instruction to click a
POST /*row to find the parent span — all inagentgateway-mcp.mdx. Lab-content work for an author, not a version bump.update-pins.shrecords the reasoning.fastmcp
Keeps the fastmcp 4 line from #11 and raises the floor to
>=4.0.2,<5, resolving against current PyPI rather than the 7-day window. The lab teaches the SEP-2663 tasks extension, which only exists in fastmcp 4 — released days ago — so a cooldown on the lab's own subject makes the lock unsatisfiable, not safe. The[tasks]extra stays:server.pydoesfrom fastmcp_tasks import TasksExtension, and basefastmcppulls onlyfastmcp-slim[client,server].READMEs come from next-gen-governance
Every
labs/*/README.mdis generated from the.mdxin next-gen-governance and carries a "Do not edit by hand" banner, so the Jaeger transcript was recaptured onbetter-agentgateway(the system of record for this lab, next-gen-governance#6754) and exported here — bde5386dc5. Merge that branch's PR first.Recapturing rather than editing caught two things a hand-edit would have got wrong: Jaeger 1.76.0 no longer declares the
4317-4318/9411/14250/14268ports, andmcp-get-startedmust not show(healthy)— after the rig split it builds fromDockerfile.get-started, which deliberately omits a HEALTHCHECK to keep the STATUS column clean.The export also brings the rest of
better-agentgatewayintoacp-gooseandmcp-get-started, which is the sync doing its job.check_doc_drift.pyreports this drift rather than fixing it, and names which of the two homes applies (evidence/here vs.mdxupstream). Its*Dockerfileglob is now*Dockerfile*, which had been silently skippingDockerfile.get-startedandDockerfile.tickets.Deliberately not automated
FROM python:3.13-slim— a floating tag, rebuilt nightly, so itslast_updatedis perpetually a day old and a release cooldown is structurally unmeasurable. What the pin controls is 3.13-vs-3.14, a compatibility call for a human PR.ceposta/keycloak:id-jag— mutable tag on a third party's personal Docker Hub account, no version, no release feed. Nothing to cooldown or move to, so it's digest-pinned instead.task e2e.Setup before the first scheduled run
Install the Zenable Automation app on this repo with contents / pull-requests / workflows write, and make
ZENABLE_AUTOMATION_CLIENT_IDandRELEASE_APP_PRIVATE_KEYvisible to it — this repo is public, so org secrets need explicit sharing.Unrelated bug found along the way
forecast_agent.py's agent card grows itssignaturesarray by one on every fetch (measured 7, 8, 9 across three consecutivecurls). The A2A SDK's signer appends, and the agent serves one long-livedAgentCardthat the modifier mutates in place. Not touched here — it predates this work and isn't about pinning — but the evidence was captured with the card unfetched so it records 1 signature, matching a reader's first fetch. Worth its own issue.🤖 Generated with Claude Code