Repository navigation
Release 0.6.0 - #223
Merged
Merged
Release 0.6.0#223
Conversation
The monitor was inert while packaging/gpg.key was absent, so deleting the key later would have turned a live repository into a green, unchecked one. It is now inert only while every APT and DNF entry point answers a clean HTTP 404. Any other answer (a network or TLS error, 403, 5xx, a redirect) fails the run, and so does a served index without a readable key; the existing issue mechanism then opens or reuses the issue. The decision lives in packaging/repo/live.sh so the CI control job can test it with a local server. Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
The control job now also covers the cases that decide whether the monitor runs at all: a domain that serves nothing is inert, while a served index with the key missing, empty or unreadable, and any fetch error that is not a clean 404 (500, 403, a redirect, a refused connection), are red. A silent monitor must never be the result of a deleted key or a broken fetch, so each of these is proven to fail. Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
CONTRIBUTING said the monitor waits for packaging/gpg.key. It now waits only for the domain to serve an index, and a served index without the key is a failure, which maintainers need to know when reading a repo-freshness issue. Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
The README and the site page now give the full setup for Debian and Ubuntu (a keyring file with signed-by, after checking the key's fingerprint) and for Fedora, RHEL, Rocky Linux and AlmaLinux (the .repo file, with the DNF 4 and DNF 5 commands), plus upgrading and the one-line key refresh for when the signing key's expiry is extended. The README and the changelog carry @@PACKAGES_KEY_FINGERPRINT@@ until the key is committed; the site page gets the fingerprint from the key when the repository is built. Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
…it expires gpgv exits 0 for a signature made by an expired or a revoked key and for an expired signature; it reports EXPKEYSIG, REVKEYSIG or EXPSIG on the status fd only. build.sh's self-check and freshness.sh looked at the exit code alone, so such an index would have verified. Both now read the status lines through one shared helper and require a GOODSIG and a VALIDSIG made by the repository key's primary key, and fail on EXPKEYSIG, REVKEYSIG, EXPSIG, BADSIG or ERRSIG. freshness.sh also reads the expiry of the primary key and of its usable signing subkeys from packaging/gpg.key and exits 5 when either expires within 60 days. The weekly workflow turns that into a warning and an issue, not a red run, so the renewal (which needs a release) is not left until clients start to fail. The control job gains throwaway keys made with a faked clock: a signing subkey that has expired, a revoked one, and keys that expire within 60 days. The first two also check that gpgv itself exits 0 for them, so the cases keep proving what they claim. Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
A plain rpm --import keeps the old copy of a key that has the same fingerprint (rpm 4.20), so after a renewal dnf could keep checking against the expired binding. The refresh now removes the old key first. The APT refresh now includes the fingerprint check that the setup steps already ask for, so the key is never installed unchecked. Both are mirrored on the site page, with the reminder to refresh before the old expiry date. Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
Documents which parts of the key exist and where, the renewal steps and what each needs (extending the primary key as well as the subkey, replacing the CI secret, publishing a release so every index is signed again), and what to do if the signing subkey leaks or the primary key is stolen or lost, so none of it depends on one person remembering it. Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
The CI secret holds the subkey with the binding signature it was exported with. When the key is renewed, packaging/gpg.key carries the extended expiry, but the subkey in the keyring would still show the old one and gpg would refuse to sign with it once that date passed, however fresh the committed key. Importing the committed public key into the throwaway keyring first merges the current binding signatures onto the subkey. A trial with a key renewed from 30 days to 2 years showed the secret subkey picking up the new expiry, and the keyring still holds exactly one secret signing subkey, so that check is unaffected. Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
CI lints packaging/repo/*.sh from the repository root, where the relative path of the sourced sigcheck.sh does not resolve. The helper is linted on its own as one of the inputs, so the source line need not be followed. Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
packaging/gpg.key is the public key of the APT and DNF repositories: an RSA 4096 certify-only primary key, AA2A F212 85A9 AB99 0833 9089 35D5 F941 71C1 CD0B, with one sign-only subkey, DC7E2403287CDB08BD84AD7DD7E72E1B635666B0. Both expire on 2028-10-09. The README and the changelog now show the fingerprint users check before trusting the key. Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
Part of #161. DNF users write the repository file from the README, with the key in a local file they have checked, instead of fetching a .repo file and a key from the package site. The key refresh for DNF imports the checked local copy. The changelog entry points to the README for the fingerprint. Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com> # Conflicts: # CHANGELOG.md # README.md # packaging/repo/index.html
Part of #161. The DNF test client now takes the .repo file from the README block that users copy, so the tests install and upgrade with exactly the documented configuration. The case for another copy of the same key is renamed P7: P4 was already taken. Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
Take the published OpenAPI document, regenerate the client, and let the alerts list work without page, total and total_pages: --all ends on a null cursor or a short page, and the page footer is skipped. Closes #210 Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
Follow /v1 2026-10-25: pagination totals may be absent Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
The integrity check derived the files to authenticate from the bundles attached to the release, so removing a bundle together with replacing its file went unnoticed as long as one bundle remained. The required set is now SHA256SUMS, every file it lists and every attached archive or installer, whichever bundles are present; a missing bundle is reported. Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
The signing steps accepted any Developer ID certificate with a TeamIdentifier, and never read the installer certificate's team. The expected Team ID is now a constant in the workflows, compared exactly with the binary's TeamIdentifier and the installer certificate's Team ID. An empty or malformed value fails the first step. The constant is empty until a maintainer sets it. Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
The administrative extraction of the MSI runs AdminExecuteSequence, so standard actions such as SelfRegModules could load a DLL shipped under the name of a payload file. The check now also refuses the tables that make such actions load payload code, requires every action of the administrative sequences to be a passive standard action, and compares LICENSE, NOTICE and USER-INFORMATION.md with the repository's copies by SHA-256. The signing job now checks out the commit for that comparison. Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
The MSI check now reads _Tables once and refuses any table the package does not use, reads every row without swallowing errors, allows only the standard actions seen in each sequence (as a subset) and pins the Registry and Environment rows to the InstallDir value and the PATH entry. Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
Mode bits do not cover NFSv4-style ACLs, which a parent folder can pass on to the sessions directory and the token file. On macOS the CLI now removes any ACL from the directory and the temporary file it creates, before a token is written, and refuses an existing directory or file that has one. On Linux a POSIX ACL is limited by the mask that chmod 600 sets, so nothing changes there. Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
It was committed by accident; __pycache__/ is now ignored. Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
…-0383 rustls-webpki 0.103.16 rejects certificate paths through the distrusted intermediates, and webpki-root-certs 1.0.10 drops the root that issued them. Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
Harden the token store, macOS signing, the release integrity check and the MSI check Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
Follow version 2026-10-26 of the API document: GET /v1/me omits data.user.email for a token without the email scope. The generated user type now has an optional email, so the answer still decodes, and whoami falls back to the name, then the id. Browser sign-in also requests the email scope, but only when the app lists it in scopes_supported; an app that publishes no list is not asked for it. Closes #217 Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
ctutils 0.4.4 makes the mask generation and u128 comparisons constant-time, and tokio 1.53.3 fixes the I/O safety violation in AsyncFd::new and related constructors. Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com> # Conflicts: # CHANGELOG.md
Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
Read whoami when the API leaves out the user's email Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
Each request used --max-time 60 with up to 3 retries and no overall limit: 4 attempts of 60 s plus 3 delays of 2 s is 246 s per request, 738 s for the three, which is more than the 10 minutes the job may run. An attempt now lasts at most 20 s and no retry starts after 40 s from the first attempt, so a request takes at most 60 s and the three of them 180 s. Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
The check counted every signing subkey that gpg lists, including one whose secret part is absent (ssb marked #) and a revoked or expired one, so a key rotation that leaves the old subkey in the public key made it fail. It now counts a subkey only when it can sign, is not revoked, expired or invalid, and its secret is present. Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
…hed it A clean 404 on every entry point was always only a notice, so the monitor could stay green forever after go-live. live.sh now pins LAST_RELEASE_WITHOUT_REPOSITORY=0.5.1 and takes the latest stable release and its age from the workflow: a 404 is accepted for a release up to that version, or for a newer one inside the grace period (48 hours, as for freshness), and is a failure afterwards. The workflow reads the release before calling live.sh and fails when it cannot. freshness-test.sh covers 0.5.1, a newer release past the grace period, one inside it, and a malformed version. Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
- actions/upload-artifact 7.0.1 -> 7.0.2 - actions/download-artifact 8.0.1 -> 8.0.2 - actions/create-github-app-token 3.2.0 -> 3.3.0 - ossf/scorecard-action 2.4.4 -> 2.5.0 - github/codeql-action/upload-sarif 4.38.2 -> 4.38.3 - github/codeql-action/init 4.38.2 -> 4.38.3 - github/codeql-action/analyze 4.38.2 -> 4.38.3 - google/osv-scanner-action 2.6.0 -> 2.7.0 Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
The README had users compare the fingerprint by eye, which passes a file that holds the real key and a second one; the second would then be trusted by signed-by and by the rpm keyring. Each place now runs a command that fails unless the file has exactly one primary key with the published fingerprint, and removes the file otherwise so that the install step that follows fails. freshness.sh also checks that the /gpg.key being served holds exactly the committed primary key and no other, with the same primaries logic as publish.sh. freshness-test.sh covers another key, an extra key and a missing file. Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
rpm -e with an empty list exits with an error, so the rpm --import after the && never ran. The old key is now removed through xargs -r, which does nothing for an empty list, and the import is its own step. The steps also run dnf clean all, with a note that DNF keeps its own copy of the key for repository metadata checks; it is harmless when nothing was cached. Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
packaging/repo/keycheck.sh holds the fingerprint as a constant and fails unless a key file holds exactly one primary key with it; given the README it also checks that the README states the same fingerprint. The install-script job runs it on packaging/gpg.key and the README, the repos job of package-managers.yml runs it before anything is signed or uploaded, and the freshness workflow runs it before it looks at the repositories, so a mismatch opens the issue. Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
The note said gpg refuses the old secret whatever packaging/gpg.key says, but the signing job imports packaging/gpg.key after the secret, which replaces the binding signature. It now says the new key must be committed before the release that signs. Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
On success nothing changes. On failure the status lines go to stderr so the log says why; they hold no key material. Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
shellcheck 0.10.0, the version on the runner image, reports SC2015 for the A && B || die chain; 0.11.0 does not. The check is now an if. The CI command is clean with both versions. Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
The gate runs in a subshell on the left of ||, where set -e does not apply, so a failing keycheck.sh was ignored. The subshell no longer relies on set -e: the key check, reading the tag and running live.sh each end in an explicit exit. Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
This reverts commit 4ca77e3. Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
The check printed STOP but the install lines after it still ran, so the APT source and the .repo file were written anyway. Each step now runs only if the check passes, with one STOP handler for the whole chain, which also removes the downloaded file. The text now says the check needs gpg. The DNF renewal note no longer claims that dnf clean all makes DNF read the key file again: it is harmless, and the renewal is still to be tested. Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
This reverts commit 2d9bef0. Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
Publish the package repository at packages.hodeishield.com Closes #161 Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
Update serde_json and GitHub Actions pins Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
Prepare 0.6.0: version and changelog Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
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.
Added
packages.hodeishield.comfor x86_64 and arm64, soaptanddnfinstall and upgrade the CLI. The README has the setup steps for Debian and Ubuntu, and forFedora, RHEL, Rocky Linux and AlmaLinux, with the signing key's fingerprint. It is the only place
that states the fingerprint and the repository configuration: check the key against it.
(#161)
Changed
/v1client follows version 2026-10-25 of the API document.alerts listnowprints each alert's
updated_atin--jsonand--csvoutput (the table is unchanged), and--sortacceptsupdated_at. The/v1/meanswer now also carriesbetaanduser; anulluser (an API key) is still no user.
(#210)
Fixed
whoamiworks when the API leaves out the user'semail, which it does for a token without theemailscope: it shows the name, or the id, and no longer fails to read the answer. Browsersign-in now also asks for the
emailscope, but only when the app lists it among its supportedscopes. The generated client follows version 2026-10-26 of the API document.
(#217)
alerts listno longer depends onpage,totalandtotal_pagesin the answer, which the APInow treats as optional. When they are missing,
--allstops at a page with a nullnext_cursoror fewer alerts than the page size, and the
Page N of Mline is not printed.(#210)
Security
(ctutils mask generation and
u128comparisons were not constant-time on some platforms) andRUSTSEC-2026-0386 (
AsyncFd::newand related constructors in tokio violate I/O safety). The CLIdoes not call either affected API directly; both crates are dependencies of its dependencies.