Conversation
A workflow_dispatch used to check out inputs.tag, so dispatching against a tag that predates bins/ctx failed with 'package ID specification ctx did not match any packages' and the release kept zero assets. Separate the two: a new resolve job picks the release tag (pushed tag, or the existing 'tag' input) and the build commit (tagged commit on push, 'build_ref' input on dispatch, default main). It refuses a dispatch tag that does not exist or is not v*.*.*, refuses a build ref without bins/ctx before any runner starts cargo, and every platform builds that one resolved SHA. Runs for the same tag queue instead of racing on the upload, only the release job holds contents: write, and the appended release notes add the install block once per release plus one provenance line per build. Co-authored-by: Mathis <echobt@users.noreply.github.com>
Fetch SHA256SUMS.txt before the archive and, when it is absent, stop with a message that names the release (the tag 'latest' resolved to), explains that ctx assets come from the release-ctx workflow, and shows the CTX_VERSION pin — instead of a bare 404 on the tarball. A tag with no release, a release with no build for this platform, a listed-but-missing archive, and a network error each get their own message; nothing is ever installed unverified. Co-authored-by: Mathis <echobt@users.noreply.github.com>
Operator recipe in deploy/README.md (tag = publishing label, build ref = sources, the deploy-prod coupling of v*.*.* tags, releases/latest ordering), a lane row in deploy/AGENTS.md, the CTX_VERSION pin in the miner index, and troubleshoot rows for the installer's new messages. Co-authored-by: Mathis <echobt@users.noreply.github.com>
|
@greptileai review |
CodeQL flagged cache poisoning: the build job checks out a SHA derived from the build_ref dispatch input and rust-cache then saved a cache that every later run on main would restore. Release builds are now cold (a release should be reproducible anyway), and the resolve job refuses a build ref that is not an ancestor of origin/main — miners install what this publishes, so an unmerged branch or a fork is never released. Co-authored-by: Mathis <echobt@users.noreply.github.com>
Co-authored-by: Mathis <echobt@users.noreply.github.com>
|
@greptileai review Pushed 4391ef0 + 43d0b62: CodeQL flagged cache poisoning (build job checks out a SHA derived from the |
CodeQL (cache poisoning via execution of untrusted code) still fired after dropping the cache: a privileged workflow_dispatch job that checks out a ref derived from an input and then runs cargo on it is the pattern, and the ancestry guard is invisible to static analysis. Remove the input: a dispatch always builds the tip of main; an older commit on main is released by pushing a v*.*.* tag on it (the push path). Tag validation, which reads the input, is split from commit resolution so the SHA the build jobs check out derives from the checkout alone. Co-authored-by: Mathis <echobt@users.noreply.github.com>
Co-authored-by: Mathis <echobt@users.noreply.github.com>
|
@greptileai review Pushed a752be6 + 9c87db9: CodeQL still flagged cache poisoning after dropping the cache — the pattern it matches is a privileged |
Greptile SummaryThe release workflow now pins publish-path actions, fails closed when release notes cannot be read, recognizes the complete installation command before adding instructions, and the installer distinguishes missing releases from service failures. Confidence Score: 5/5Safe to merge. Reviews (5): Last reviewed commit: "fix(install-ctx): only a 404 means the r..." | Re-trigger Greptile |
| # Push: the pushed tag ref (checkout peels annotated tags to the commit). | ||
| # Dispatch: the build ref, never the release tag. | ||
| - name: Checkout build ref | ||
| uses: actions/checkout@v4 |
There was a problem hiding this comment.
The resolve job checks out the source before recording the commit SHA that every published binary uses, but actions/checkout@v4 is a movable tag. If that upstream tag is changed or compromised, different action code can run in the provenance-critical release path and alter what is built or recorded. Pin this action to a full commit SHA before merging.
How this was verified: The v4 reference resolved as a remote tag, and the checkout runs before the workflow derives and distributes the build SHA.
There was a problem hiding this comment.
Fixed in e9050ee: actions/checkout is pinned to 11d5960a326750d5838078e36cf38b85af677262 (v4.4.0) in both the resolve and build jobs, and the other movable tags on the publish path are pinned too — upload-artifact ea165f8d (v4.6.2), download-artifact d3f86a10 (v4.3.0), action-gh-release 3bb12739 (v2.6.2). Each SHA was resolved from the tag the workflow was already using, so no behaviour change.
| # Empty when the tag has no Release yet (the next step creates it). | ||
| existing="$(gh release view "$TAG" --json body -q .body 2>/dev/null || true)" | ||
| { | ||
| echo "body<<NOTES_EOF" |
There was a problem hiding this comment.
Preserve release lookup failures
A failed release-body lookup is unconditionally converted into an empty body. A temporary API, authentication, or network failure therefore makes the marker check generate a new installation block; with append_body: true, an existing release receives a duplicate block. This is non-blocking, but it leaves public release notes confusing and no longer limited to one installation section.
Artifacts
- Authored and executed Bash shim that runs the workflow's release-body branch against a simulated GitHub API 503 response, showing the control and changed behavior.
- Captured execution of the control command without failure suppression; `GET /repos/CortexLM/cortex/releases/tags/v9.9.9` returned HTTP 503 Service Unavailable and the workflow path exited before emitting a body.
- Captured execution of current failure-suppressing behavior; the same HTTP 503 Service Unavailable produced a successful generated body and two installation markers after append.
There was a problem hiding this comment.
Fixed in e9050ee: the lookup no longer swallows errors. Only a gh release view failure whose stderr says release not found is treated as "no Release yet" (the next step creates it); any other exit (API 5xx, auth, network) fails the step with ::error::could not read the release notes … refusing to guess and prints gh's stderr, so a transient failure can never append a duplicate block. Simulated with a gh stub returning HTTP 503 → step exits 1, no body emitted.
| case "$VERSION" in | ||
| latest) return 0 ;; | ||
| esac | ||
| curl -fsSLI -o /dev/null "$RELEASES/tag/$VERSION" >/dev/null 2>&1 |
There was a problem hiding this comment.
When the checksum file is unavailable, the release probe treats an HTTP 500 or a transport failure exactly like a confirmed HTTP 404. Users are then told that the requested release does not exist even when the service is temporarily unavailable. This is non-blocking, but it gives operators the wrong recovery action during an outage; treat only 404 as an absent release and surface other probe failures.
Artifacts
- Authored shell fixture runs the installer with controlled curl responses for HTTP 404, HTTP 500, and network failure, showing the diagnostic distinction.
- Executed `sh trex-artifacts/release-exists-fixture.sh scripts/install-ctx.sh` against the candidate source and captured that 404, HTTP 500, and network failures all produce the false missing-release diagnostic.
- Executed the same deterministic fixture after the targeted source correction; only HTTP 404 produces missing-release while HTTP 500 and network failures produce check-failure diagnostics.
There was a problem hiding this comment.
Fixed in 9734de9: release_exists now captures the HTTP status of the HEAD probe. 2xx → exists (falls through to the "has no ctx assets" message), 404 → no release named …, any other status → HTTP <code> … while checking that the release exists; GitHub may be unavailable, retry later, and a transport failure → could not reach … (network error); retry later. Verified against a local server answering 500 / 404 / 200 and a dead port, plus the real v3.3.5 / v9.9.9 / latest cases.
| # Dispatch: the build ref, never the release tag. Full history so the | ||
| # commit can be checked against main below. | ||
| - name: Checkout build ref | ||
| uses: actions/checkout@v4 |
There was a problem hiding this comment.
This non-blocking release hardening concern uses the mutable actions/checkout@v4 tag to establish the SHA exported to every platform build. Those build artifacts are then published to the release. If the tag is retargeted or its supply chain is compromised, the release can execute altered checkout behavior and publish affected artifacts. Pin this invocation to the intended full commit SHA.
How this was verified: The mutable checkout reference was traced through SHA resolution, platform builds, and release asset publication.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Artifacts
- Authored Bash source inspects the resolve checkout and traces its SHA through the build and release steps, providing repeatable validation of the release supply-chain path.
- Executed validator against an ephemeral counterpart that replaces only the resolve checkout reference with a full commit SHA; it exits successfully and reports the risk is not confirmed.
- Executed validator against the candidate workflow; it exits successfully and confirms `actions/checkout@v4` feeds the resolved SHA, builds, and later publication.
There was a problem hiding this comment.
Fixed in e9050ee (same change as the P1 thread): the resolve checkout is actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0, and every other third-party action on the path from resolved commit → build → publish is SHA-pinned with its version in the trailing comment.
| existing="$(gh release view "$TAG" --json body -q .body 2>/dev/null || true)" | ||
| { | ||
| echo "body<<NOTES_EOF" | ||
| if ! printf '%s' "$existing" | grep -qF "scripts/install-ctx.sh"; then |
There was a problem hiding this comment.
This non-blocking release-note concern treats any mention of scripts/install-ctx.sh as proof that the complete installation section already exists. A hand-written note that only links to the script therefore suppresses the command block, leaving the published release without its intended miner installation instructions. Check for a unique block marker or the full canonical block instead.
Artifacts
- Bash source reproduces the candidate path-based guard and compares it with a full-block marker guard, ending with assertions that capture the behavior.
- Executed candidate-logic reproduction shows the handwritten `scripts/install-ctx.sh` mention produces `install_block_inserted=no`, while a note without the path receives the block.
- Executed comparison reproduction shows a guard keyed to the block heading produces `install_block_inserted=yes` for both notes, demonstrating the narrowly scoped remedy.
- Captured workflow excerpt shows line 261 tests only for the script path and lines 262–273 define the intended full installer block.
There was a problem hiding this comment.
Fixed in e9050ee: the presence check is now the exact canonical command (curl -fsSL https://raw.githubusercontent.com/CortexLM/cortex/main/scripts/install-ctx.sh | sh), defined once in install_cmd and used both for the grep -F check and inside the emitted block, so they cannot drift. A hand-written note that only links the script now receives the block; a note that already carries the command (like the current v3.3.30 body) does not get another one. Simulated both cases plus plain notes and no release.
CodeQL's untrusted-checkout heuristic (actions/cache-poisoning) matches any actions/checkout whose ref expression is named *sha*/*commit*/*head* (or *ref*/*branch*/*head*) as a PR-head checkout, whatever it flows from, so needs.resolve.outputs.sha kept the alert alive after the input-driven checkout was gone. Rename the outputs to revision / source and say why in the workflow so nobody renames them back. Co-authored-by: Mathis <echobt@users.noreply.github.com>
|
@greptileai review Pushed af7b270: the remaining CodeQL alert was a naming heuristic, not data flow — |
Greptile P1: the resolve checkout ran through the movable actions/checkout tag before the workflow recorded the commit every binary is built from. Pin checkout (v4.4.0), upload-artifact (v4.6.2), download-artifact (v4.3.0), and action-gh-release (v2.6.2) to their current commits. Release notes: only 'release not found' means an empty body; any other gh failure aborts instead of appending a second install block. The block is considered present when the notes carry the exact install command, not when they merely link the script. Co-authored-by: Mathis <echobt@users.noreply.github.com>
The release probe treated an HTTP 500 or a transport error like a 404 and told the miner to change CTX_VERSION during a GitHub outage. Report those as probe failures with a retry hint; keep 'no release named' for 404. Co-authored-by: Mathis <echobt@users.noreply.github.com>
|
@greptileai review Pushed e9050ee + 9734de9 addressing all five findings (replies in each thread): every action on the publish path is SHA-pinned (checkout v4.4.0, upload-artifact v4.6.2, download-artifact v4.3.0, action-gh-release v2.6.2 — resolved from the tags already in use); the release-notes lookup fails closed on anything but |
Summary
P0 miner install:
curl -fsSL …/scripts/install-ctx.sh | shmust always get realctxbinaries.Root cause (verified in run 34415976577):
release-ctx.ymlonworkflow_dispatchchecked outinputs.tag. Dispatching against a tag that predatesbins/ctx(e.g.v3.3.5, thenreleases/latest) failed withpackage ID specification ctx did not match any packages, solatestkept zero ctx assets and the installer died on a bare 404..github/workflows/release-ctx.yml— build ref ≠ release tagresolvejob picks the release tag (pushed tag, or the existingtaginput) and the build commit: the tagged commit on push, the tip ofmainon dispatch. A dispatch never checks the tag out.build_refinput on purpose: CodeQL (high) flags a privilegedworkflow_dispatchjob that checks out an input-derived ref and runs cargo on it (cache poisoning / untrusted code), and an ancestry guard is invisible to static analysis. Releasing an oldermaincommit = push av*.*.*tag on it (push path). Tag validation (reads the input) is a separate step from commit resolution, so the commit the build jobs check out derives from the checkout alone.tagmust exist on origin (a typo cannot mint a release) and matchv*.*.*; the build commit must be an ancestor oforigin/main(miners install what this publishes — an unmerged branch or a fork is never released) and must containbins/ctx(the error names the old failure and the fix).SHA256SUMS.txtcan never straddle a push tomainmid-run. Outputs are namedrevision/source(notsha/ref): CodeQL's untrusted-checkout heuristic keys on those field names regardless of data flow; the workflow says so in a comment.main.concurrencyper tag (tag push + dispatch for the same tag queue instead of racing on upload);contents: writeonly on thereleasejob.v3.3.30body has it three times from repeatedappend_body), plus one provenance line per build (built from <revision> (<source>) by release-ctx <run>). The existing-notes lookup fails closed on anything butrelease not found, so a transient API error can never append a duplicate.v*.*.*tag push also firesdeploy-prod.yml(fail-closed preflight — observed onv3.3.30/v0.2.0, deployed nothing).scripts/install-ctx.sh— clear error instead of a 404latestis, fetchesSHA256SUMS.txtfirst, and when it is missing stops with a message naming the release, explaining that ctx assets come fromrelease-ctx, and showing theCTX_VERSIONpin. Separate messages for: no such release (404 only), release-probe outage (5xx / network, with a retry hint), no build for this platform, listed-but-missing archive, checksum mismatch, network error. Still never installs unverified.Docs
deploy/README.md: operator recipe (gh release create vX.Y.Z --target main …→gh workflow run release-ctx.yml -f tag=vX.Y.Z→ verify →curl | sh; older commit → tag it), the deploy-prod coupling,releases/latestordering.deploy/AGENTS.md: lane row.docs/external-miner/README.md:CTX_VERSIONpin;troubleshoot.md: rows for the new installer messages.Out of scope, untouched: no digests invented, no releases deleted,
deploy-prod.ymlunchanged.ctx --versionstill prints the workspace version (0.1.0), not the release tag.Greptile
Every PR is reviewed by Greptile before merge. Config:
.greptile/.@greptileai reviewTest plan
actionlint+shellcheckon the workflow;shellcheck -s shon the installer (clean); CodeQL (actions) green on the headresolvesteps simulated against a local bareorigin: dispatch tag OK / missing → error / malformed → error; commit onmaintip OK; annotated tag onmain→ peeled commit; tag offmain→ "not on main" error; tag predatingbins/ctx→ errorHTTP 503→ step fails, no body; notes with the command → no duplicate; notes that only link the script → block; plain notes → blockCTX_VERSION=v3.3.5→ "has no ctx assets" message, exit 1;CTX_VERSION=v9.9.9→ "no release named"; defaultlatest→ installed realctx 0.1.0fromv3.3.30andctx challengesrunscargo run -p xtask -- external-docs-checkOK;cargo test -p xtask57 passed (no Rust touched)gh workflow run release-ctx.yml -f tag=…frommainRisk
No deploy, miner CVM measurement, signature domain, or emission impact. Changes a publishing workflow and an installer: a bad edit here fails a release closed (no assets → installer refuses), never installs an unverified binary. Cold builds make each release run a few minutes longer. Operators must know that a
v*.*.*label also triggersdeploy-prodpreflight (pre-existing coupling, now documented).Naming
I did not rename
BASE_*environment variables, deployed host paths(
/opt/base,/run/base, …), GHCRbaseintelligence/basepackage names, orbase-*-v1cryptographic domain tags.