Repository navigation
Release 0.5.0 - #192
Merged
Merged
Release 0.5.0#192
Conversation
Replace the proposal with the decision: keep the keychain as the default and add an explicit opt-in token store that is a plain-text file, with no fallback in either direction. Record the file checks, the location in the state directory, the deferral of Windows and of the Linux kernel keyring, and the server-side refresh token lifetime that is still to be confirmed. The text now matches the fail-closed sign-in behaviour, where tokens that cannot be stored are revoked. Refs #39 Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
People who work from a jump host or another machine without a keychain could only use a tenant API key. Add a second token store, selected by HODEISHIELD_TOKEN_STORE=plaintext-file or the global token_store setting, that keeps one file per profile in the state directory. The keychain stays the default and the two stores never fall back to each other. Every read and write refuses, without repairing, a symbolic link, a file or directory owned by another user, or one open to group or others. Files are created with mode 600 and replaced atomically. The effective user id comes from rustix, which is already in the dependency graph on Linux, so no unsafe code is needed. Windows is refused for now. Refs #39 Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
Add a README section for servers, containers and remote sessions that puts API keys first and the opt-in file second with its trade-off, the new variable and setting in their tables, the network home directory note, and the exit code for a refused token file. Update the places that promised tokens are never in a file, and add the changelog entry. Refs #39 Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
Say that on macOS the sessions directory is a sibling of the application support folder, not inside the configuration folder, that the parents of the sessions directory must not be writable by other users, that the file names are a hash and not a way to hide a profile name, that running as root for a user's home does not work, and that a refused file makes login and logout exit 1 while other commands exit 3. Refs #39 Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
Keep the sessions outside the configuration directory on macOS, where the platform gives the same folder for both. Open the sessions directory once without following links, check it on the open handle and do every later operation relative to it, so replacing the path after the check cannot redirect a read, write or removal. Open files without following links and without blocking, remove stale temporary files that a crash could leave with live tokens, and choose the store once per process so a command checks and saves in the same one. State precisely in the message that group and others must have no access. Tests cover the temporary file guard, the leftovers, the opposite direction of the missing fallback, and that a refused file is left byte for byte as it was. Refs #39 Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
The sessions moved to a sibling of the configuration directory on macOS; the integration tests still looked inside it, so on macOS they found no file. Refs #39. Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
The service's token lifetimes are now known: access tokens last 15 minutes, refresh tokens 30 days from the last refresh with reuse detection, and an absolute 90-day limit is being added. The README and the decision record state today's exposure of a leaked file and how to end it, instead of an open question. Refs #39. Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
The test moved the sessions directory into the state directory, which does not exist on macOS (the sessions directory sits under Application Support there), so the move failed before the case under test ran. Refs #39. Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
Its only users are the Linux check and a Unix-only test, so on a Windows test build it was dead code and clippy refused it. Refs #39. Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com> # Conflicts: # crates/hodeishield-cli/src/cli.rs # crates/hodeishield-cli/src/commands/mod.rs # crates/hodeishield-cli/tests/cli.rs
The service now caps a refresh chain at 90 days from the original sign-in and ends CLI sessions on a password change or reset, an administrator revocation or removal from the tenant. The README and ADR 0001 give these figures instead of the earlier "is adding" wording. The changelog entry moves from the released 0.4.0 section, where the merge of dev left it, to Unreleased. Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
CodeQL (rust/cleartext-logging) reads the assert_eq! on the effective uid as writing it to a log. A uid is not secret, but the comparison needs no values in its message either. Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
CodeQL (rust/cleartext-logging) flags the assertion that prints the refusal reason, which names the file owner's uid. The assertion now prints nothing; the effective-uid comparison goes back to assert_eq!, which was not the flagged line. Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
The vendored document adds GET /v1/me, the reports operations and the known values of the risks domain and status and the compliance controls status filters. The generator now accepts an operation that needs no scope (x-hs-scope none) and an answer that is a file: such an operation returns a Download with the bytes, the content type and the file name, read up to the document's 20 MiB limit, and sends an Accept header with the declared media types. Two shapes that share a name are renamed (WhoAmIOrganization, ReportDetail). The filter tests now cover the warning for each newly listed filter; the vendors category filter stays free text. Sign-in asks for reports:read when the app offers it. Closes #145 Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
Homebrew now refuses to load a formula from a tap that is not trusted, and adding a tap does not trust it, so the documented brew tap then brew install hodeishield fails. Installing the fully qualified name adds the tap and trusts only this formula. The README says so and how to trust the formula when the tap was added earlier. Refs #179 Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
Add an opt-in plain-text token store for machines without a keychain Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com> # Conflicts: # CHANGELOG.md
whoami reads GET /v1/me and shows the tenant the credential reads (name and id), its organisation and, for a key, its scopes; for a portfolio key, the MSSP and the number of client tenants. --json gains the tenant, organization, portfolio and credential_scopes fields. When the API cannot be read, whoami still prints the local lines, the new fields are null and a warning says why; the exit code does not change. Names from the API are cleaned before they are printed. Closes #48 Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
The reason a GET /v1/me call failed can carry the API's own message, which is untrusted; the warning printed it as received. It now goes through clean(), like every other API string the CLI prints, and the contract test checks that no escape sequence reaches the terminal. Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
Install with Homebrew by the formula's full name Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com> # Conflicts: # CHANGELOG.md
Follow version 2026-10-19 of the API document Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
reports list filters by --type (also --report-type), --status and --since, with --sort, paging, --json, --csv, --tenant and --all-tenants like the other lists; the listed type and status values are shown in --help and an unlisted one gets a warning. reports get shows a report with the formats it can be downloaded in. reports download saves a report's file (--format pdf|xlsx, --locale) in the current directory under the name the API suggests, reduced to a plain file name (last component only, [A-Za-z0-9._-], no leading dots or dashes, no Windows device names), or report-<id>.<format> when nothing usable is left. --output saves it at a path or into a directory; --output - writes it to standard output and is refused when that is a terminal. The file goes to a temporary file in the same directory and is renamed into place; without --force the destination is first claimed with an exclusive create, so an existing file is never replaced. With --json, standard output holds the path, size and content type. The 409 answers of the download (REPORT_NOT_READY, FORMAT_UNSUPPORTED, FILE_TOO_LARGE) get their own messages and hints, still with exit code 1. All server text printed on either stream is cleaned. Closes #55 Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com> # Conflicts: # CHANGELOG.md
Show the credential's tenant in whoami Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
The extension of a saved report is always that of its format, whatever name the API suggests. The file is written and synced under a temporary name and then linked into place, so an interrupted save leaves nothing behind, and on Unix it is readable only by its owner. The target directory is checked before the download, and error bodies are read with the normal size limit. Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
Add the reports commands: list, get and download Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
The README now describes the System Settings route (Privacy & Security, Open Anyway), says that unpacking with tar in Terminal keeps the quarantine flag while a curl download carries none, and notes that the Gatekeeper dialog follows the system language. Refs #16 Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
Add .well-known/security.txt (RFC 9116) with the private reporting link, the security address, the preferred languages, the policy and the canonical URL. SECURITY.md, the risk assessment and the technical documentation point to it. A weekly workflow (and any pull request that changes the file or the check) validates the file and fails when it is invalid, has expired or has less than 60 days left; the scheduled run opens an issue labelled security-txt and closes it when the check passes again. Closes #125 Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
Explain how to open a blocked macOS binary Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
a_connection_error_is_retried bound a listener on an ephemeral port, dropped it and expected every connection to that port to be refused. Between the drop and the client's attempts, another test running in parallel (mockito servers bind ephemeral ports) could receive the same port, so the client got an answer and the retry count was not 2. The test now keeps the listener bound for its whole duration, so the port cannot be reused. The listener never accepts, so each attempt times out (short client timeout), which the client retries the same way as a refused connection. A reset after accept would not be retried, as it is not a connect error, so accepting and closing was not used. The assertions are unchanged: Error::Transport and exactly 2 retries. Closes #183 Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
Publish a security.txt and check its expiry Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
Keep the refused-connection test from racing other tests Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
The release workflow already holds the macOS signing steps behind the MACOS_SIGNING_ENABLED variable. This makes them safe to switch on: - When signing is off, the macOS job says so in the run summary and as a notice, so a skip cannot be read as a pass. - When signing is on, the job checks the Developer ID Application authority, the Team ID, the hardened runtime and the timestamp after signing, and asserts that Gatekeeper reports the binary as Notarized Developer ID and that the package is signed, stapled and accepted. It writes the result to the run summary. - A new on-demand "macOS signing check" workflow, bound to the release-signing environment, runs the same chain on a throwaway executable and package. It fails and lists the names of any missing secret. - docs/release-signing.md describes the identities, the secrets, who may change them and what to do if they are compromised; docs/verifying-releases.md explains how to verify the signatures. The signing steps stay inline in both workflows: the signing job checks nothing out and runs no repository code, and a shared script would break that. Refs #50 Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
whoami now reads the user of a sign-in from the body of GET /v1/me as received (data.user), not from the generated type, because the vendored API document does not describe the field yet. It is read leniently: a missing field, null or a malformed value is no user, never an error. The User line and the --json user string keep their name and type. They are filled from the API first, formatted as Name <email>, and the authorization server's userinfo endpoint is only asked when the API gives no user. For a key the field is ignored. Every string is cleaned before it is printed. No extra request is made, and nothing changes in the output until the API returns the field. Closes #182 Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
Show the signed-in user in whoami from /v1/me Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
Prepare Developer ID signing and notarization for macOS Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
The default procedure to enable macOS signing is to store the nine secrets and set MACOS_SIGNING_ENABLED to true: the next release signs and notarizes, and its draft is published only after the macOS job and its checks pass. The signing check workflow stays documented as an optional way to check the secrets before a release, with its two preconditions. Refs #50 Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
Make the macOS signing check optional in the release-signing steps Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
Version 0.5.0 (workspace crates only in Cargo.lock), the changelog section dated 2026-10-15 with an empty Unreleased section above it and the compare links, the version in the examples of the README and docs/verifying-releases.md, and 0.5.0 in the range of the Authenticode table. Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
Prepare 0.5.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.
Release 0.5.0: merges
devintomain. The full notes are the## [0.5.0]section ofCHANGELOG.md, which also becomes the release notes.Added
HODEISHIELD_TOKEN_STORE=plaintext-file), with strict file checks; not available on Windows yet. A copied file stays usable at most 90 days. (without a system keychain, a person cannot sign in #39, Add an opt-in plain-text token store for machines without a keychain #119)reports list,reports getandreports download: the tenant's generated reports and their PDF or XLSX files. Files are saved without overwriting, under a plain name with the format's extension. (the cli cannot download the reports the app generates #55, Add the reports commands: list, get and download #184)security.txt(RFC 9116) for this repository, with a weekly expiry check. (no security.txt pointing to the security policy #125, Publish a security.txt and check its expiry #186)Changed
--domainand--statusfilters and the compliance controls--statusfilter list the values the API publishes. An unlisted value gets a warning and is still sent as typed. (The category, domain and status filters have no listed values yet #145, Follow version 2026-10-19 of the API document #178)/v1client follows the API document of 2026-10-19 (GET /v1/me, reports).loginalso asks forreports:readwhen the app offers it. (Follow version 2026-10-19 of the API document #178)whoamishows the tenant, the organisation, a key's scopes and, for a portfolio key, the client tenant count, read fromGET /v1/me. It also shows the signed-in user when the API provides it.--jsongains fields; the exit codes do not change. (whoami does not show which tenant the credential gives access to #48, Show the credential's tenant in whoami #181, whoami shows no user for a sign-in: the app does not publish who the user is #182, Show the signed-in user in whoami from /v1/me #189)Fixed
tarin Terminal keeps the block, and that the dialog follows the system language. (readme does not say what to do when macOS or Windows block the downloaded binary #16, Explain how to open a blocked macOS binary #185)Tests
No change to the scripting surface that breaks a script: the new
--jsonfields are additions. macOS binaries remain without a Developer ID signature in this release, as in 0.4.4.