# Changelog

All notable changes to this project are documented in this file. The
format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
and this project adheres to [Semantic Versioning](https://semver.org/).

## [Unreleased]

## [1.1.2] - 2026-06-08

### Changed

- deps: bump `serde_json` to `1.0.150`, `http` to `1.4.1`, `uuid` to
  `1.23.2`, `log` to `0.4.32`, and the `serial_test` dev-dependency to
  `3.5.0`. Lockfile-only refreshes with no source changes.
- CI: bump `github/codeql-action` to `4.36.2` and the `actions` group
  (workflow runner actions) across `ci.yml`, `codeql.yml`, and
  `release.yml`.
- docs: retarget the Homebrew tap at `dekobon/homebrew-tap` and tidy
  the packaging/install docs to match the rename.

## [1.1.1] - 2026-04-26

### Security

- `host-identity-cli`: bump `rustls-webpki` to `0.103.13`, patching
  `GHSA-82j2-j2ch-gfr8` (reachable panic on malformed CRL `BIT
  STRING`) and a related URI name-constraint validation bug. Reaches
  the CLI transitively via `rustls -> ureq`; CRL checking is opt-in,
  so default usage was unaffected. See
  [#27](https://github.com/dekobon/host-identity/issues/27).

### Changed

- CI: bump `softprops/action-gh-release` in the `publish` job from
  `v2.3.2` to `v3.0.0`, migrating the action runtime from Node.js 20
  to Node.js 24 ahead of the 2026-09-16 Node 20 removal from
  GitHub-hosted runners. `v3.0.0` is a runtime-only bump with no
  interface changes. See
  [#26](https://github.com/dekobon/host-identity/issues/26).
- CI: bump the `actions` group — `actions/checkout` to `v6`,
  `docker/setup-qemu-action` to `v4`, `vmactions/freebsd-vm` to
  `v1.4.5`, and `dtolnay/rust-toolchain` to its latest pinned SHA.
- CI: collapse the release-workflow `preflight.classify` step's four
  `GITHUB_OUTPUT` writes into a single redirect block (shellcheck
  `SC2129`). Pure cleanup — identical runtime behavior.

## [1.1.0] - 2026-04-21

### Fixed

- `host-identity-cli`: audit's plain and summary renderers now
  flatten embedded `\n` / `\r` in error text to spaces, preserving the
  "one line per outcome" contract that downstream scripts rely on. A
  `Source` impl that emitted a multi-line `Error::Platform.reason`
  previously produced multi-line output under `audit --format plain`
  and `audit --format summary`, breaking line-oriented parsers. JSON
  output was already safe via serde's string escaping. Drive-by: the
  plain renderer now streams each outcome via `write!`/`writeln!`
  instead of building an intermediate `String`, removing three
  allocations per outcome on the common path.
- `host-identity`: the `nix_is_root` test helper in `linux.rs` now
  reads the *effective* UID (second field of `/proc/self/status`
  `Uid:`) to match its comment and its callers' intent — a setuid-
  dropped or `sudo -E cargo test` runner with real != effective would
  have incorrectly skipped permission-denied tests. Parser extracted
  into `effective_uid_from_status` with unit coverage for typical
  shapes and malformed input. See
  [#23](https://github.com/dekobon/host-identity/issues/23).
- `host-identity`: the default-feature build no longer emits
  `dead_code` warnings for the Kubernetes chain-building helpers. The
  pod-UID and service-account pushes are inlined into
  `network_default_chain` under a `#[cfg(feature = "k8s")]` guard, so
  they're only compiled when their only caller (the
  `_transport`-gated network chain) is compiled. A new integration
  test pins the ordering contract (pod UID first after env override,
  service-account last). See
  [#22](https://github.com/dekobon/host-identity/issues/22).
- `host-identity-cli`: empty tokens in `--sources` (from `""`, leading
  comma, trailing comma, or a doubled comma like `foo,,bar`) are now
  rejected up-front with `--sources contains an empty identifier`
  instead of surfacing downstream as the unusable
  `unknown source identifier:` message with empty backticks for the
  token. Consistent with the pre-existing check that rejects an empty
  `--app-id`. See
  [#21](https://github.com/dekobon/host-identity/issues/21).
- `host-identity-cli`: `audit --format summary` now emits a compact
  `source:<uuid|skipped|ERROR msg>` form mirroring
  `resolve --format summary`, instead of collapsing to byte-identical
  `--format plain` output. Callers that parse the summary tail should
  `rsplit_once(':')` because some source labels (e.g.
  `app-specific:machine-id`) themselves contain a colon. See
  [#20](https://github.com/dekobon/host-identity/issues/20).
- `xtask` no longer leaks a heap allocation per rendered subcommand via
  `Box::leak`. Enabling the `string` feature on clap lets
  `Command::name` accept an owned `String` directly. See
  [#18](https://github.com/dekobon/host-identity/issues/18).
- `SourceKind`'s `Display` impl now honours formatter fill/align/width
  and precision by calling `fmt::Formatter::pad` instead of
  `write_str`. Width specifiers such as `{kind:<28}` previously emitted
  unpadded output, breaking column-aligned layouts in any caller —
  notably `host-identity-cli`'s audit plain-text renderer. Existing
  callers that pass no format spec continue to produce byte-identical
  output. See
  [#17](https://github.com/dekobon/host-identity/issues/17).

### Added

- `host-identity`: new `OpenStackMetadata<T>` source (identifier
  `"openstack-metadata"`, feature `openstack`) reading the
  per-instance UUID from the OpenStack Nova metadata service at
  `/openstack/2018-08-27/meta_data.json`. Joins `network_default_chain`
  after `OciMetadata`. Uses the same flat-JSON top-level-boundary
  scanner as `AwsImds` — no `serde_json` dependency. See
  [#24](https://github.com/dekobon/host-identity/issues/24).
- `LinuxHostIdFile` source reading glibc's 4-byte binary `/etc/hostid`
  (identifier `linux-hostid`). Opt-in — **not** part of `default_chain`
  or `network_default_chain`; operators who have the file (OpenZFS
  hosts, minimal non-systemd images, Red Hat containers that
  bind-mount `machine-id` but not `hostid`) push it explicitly. Reads
  the file directly rather than calling `gethostid(3)`, whose
  fallback path fabricates an unstable value from `gethostname()`.
  Rejects `0x00000000` and `0xffffffff`, and any file size other than
  four bytes. See
  [#10](https://github.com/dekobon/host-identity/issues/10).
- `AppSpecific<S>` source wrapper for systemd-style app-specific
  derivation of any inner source. HMACs the inner source's raw probe
  value with a caller-supplied `app_id` and emits a UUID-shaped probe,
  so two apps on the same host get uncorrelatable IDs and the raw
  inner value never leaks. Preserves `Wrap::Passthrough`
  round-tripping and default `Wrap::UuidV5Namespaced` semantics by
  emitting the same UUID shape as the other UUID-native sources. See
  [#11](https://github.com/dekobon/host-identity/issues/11).
- `app_specific` runnable example under `crates/host-identity/examples/`
  demonstrating `AppSpecific<MachineIdFile>` with stability across
  repeated resolves and uncorrelatability across `app_id`s. See
  [#15](https://github.com/dekobon/host-identity/issues/15).
- `Resolver::into_boxed_sources` drains a built resolver's chain in
  order. Enables post-processing — for example, wrapping every source
  with `AppSpecific` — before feeding the sources back via
  `Resolver::with_boxed_sources`.
- `host-identity-cli`: `--app-id <APP_ID>` flag on `resolve` and
  `audit` wraps every source in the chain with `AppSpecific`,
  surfacing the library's per-app derivation on the command line.
  Source labels in `--format json` / audit output become
  `app-specific:<inner>`. Companion to the library-side `AppSpecific`
  entry above.

### Changed

- CI: the `publish-crates` release job now authenticates to crates.io
  via Trusted Publishing (short-lived OIDC tokens) instead of a
  long-lived `CARGO_REGISTRY_TOKEN` secret. Releases must run in the
  new `release` GitHub Environment, and each crate must have a
  GitHub Trusted Publisher registered on crates.io matching the
  `release.yml` workflow and `release` environment. See
  [#16](https://github.com/dekobon/host-identity/issues/16).
- **Breaking (CLI JSON):** `host-identity resolve --format json` and
  `host-identity audit --format json` now emit an envelope recording
  the active `--wrap` strategy. `resolve` returns
  `{"wrap": "<v5|v3|passthrough>", "host_id": { … }}` and `audit`
  returns `{"wrap": "…", "entries": [ … ]}`. The per-entry schema is
  unchanged; the top-level shape is. `sources --json` is unaffected.
  Consumers that previously indexed `resolve` output by `.uuid` /
  `.source` should now read `.host_id.uuid` / `.host_id.source`;
  consumers that treated `audit` output as a bare array should read
  `.entries`.
- `MachineIdFile` and `DbusMachineIdFile` now reject known-duplicate
  `/etc/machine-id` values via a conservative static deny-list plus an
  all-same-nibble hex32 check. Seeded with the Whonix / Kicksecure
  deliberate anti-fingerprinting constant and seven hex values observed
  baked into widely-pulled container images (Oracle Linux 8/9 official
  images, `jrei/systemd-*`, `geerlingguy/docker-*-ansible`). A matching
  value now returns `Ok(None)` with a `log::debug!` entry so the
  resolver falls through rather than producing a fleet-wide non-unique
  identity. Scope is narrow: only cited, publicly-documented shared
  values are on the list, and the filter does not apply to other
  sources (`LinuxHostIdFile`, `DmiProductUuid`, etc.). See
  [#12](https://github.com/dekobon/host-identity/issues/12).
- `IllumosHostId` now rejects the `00000000` unset sentinel (and any
  other numeric-zero form — decimal `0`, `0x0`, `0X00000000`) by
  returning `Ok(None)` so the resolver falls through, matching the
  `SysctlKernHostId` (NetBSD/OpenBSD) contract. Previously every
  illumos host with an unset `hostid(1)` collapsed to the same
  fleet-wide UUID. See
  [#9](https://github.com/dekobon/host-identity/issues/9).

## [1.0.0-rc1] - 2026-04-19

Rehearsal pre-release of 1.0.0. Exercises the full release pipeline
(build matrix, packaging, smoke tests, signing, SBOM, SLSA
attestations, GitHub Release upload) end-to-end with the pre-release
gate keeping Homebrew, Scoop, and crates.io pushes dormant. See the
1.0.0 entry below for the feature surface being validated.

## [1.0.0] - 2026-04-19

Initial stable release. The public API surface of the library
(`resolve`, `resolve_with`, `Source`, `SourceKind`, `default_chain`,
`network_default_chain`, the `HttpTransport` trait) is now covered
by semver.

### Fixed

- CLI: honour the `HOST_IDENTITY_FILE` environment variable that the
  `--help` text already advertised. When set to a non-empty path, the
  CLI now prepends a `FileOverride` ahead of every other source (both
  the default chain and explicit `--sources` chains), so operators
  can pin identity from a file without editing the resolver code.
  `HOST_IDENTITY_FILE` outranks `HOST_IDENTITY` when both are set; an
  empty value is treated as unset. See
  [#8](https://github.com/dekobon/host-identity/issues/8).

### Changed

- The CLI binary is installed as `host-identity` (not `hostid`) to
  avoid colliding with coreutils `hostid(1)`, which ships on every
  Linux distro and the BSDs. Package artefacts, man pages
  (`host-identity.1`, `host-identity-audit.1`,
  `host-identity-resolve.1`, `host-identity-sources.1`), Homebrew
  formula class (`HostIdentity`), and Scoop manifest bin follow the
  new name. The library crate `host-identity` and the CLI crate
  `host-identity-cli` are unchanged — only the produced binary name
  was settled here.
- `DmiProductUuid` rejects well-known SMBIOS vendor-placeholder UUIDs
  (all-zero, all-F, all-same-nibble, and a curated fwupd/dmidecode
  list such as `03000200-0400-0500-0006-000700080009`) by returning
  `Ok(None)` with a `log::debug!` entry, so the resolver falls
  through to the next source instead of emitting a silently
  non-unique fleet-wide identity. See
  [#5](https://github.com/dekobon/host-identity/issues/5).

### Added

- Binary artefacts (`.deb`, `.rpm`, `.apk`, FreeBSD `.pkg`, macOS and
  Windows tarballs, Homebrew formula) ship a `THIRD-PARTY-LICENSES.md`
  bundle generated by `cargo-about` from `about.toml` and `about.hbs`,
  closing an Apache-2.0 §4(d) / `ring` (`Apache-2.0 AND ISC`)
  attribution gap. A `license-audit` CI job fails the build when a
  dependency arrives with a license not on the `accepted` allowlist.
  See [#6](https://github.com/dekobon/host-identity/issues/6).
- Core resolution pipeline: `resolve()` walks an ordered chain of
  identity sources, short-circuits on the first usable value, rejects
  known sentinels (empty files, systemd's `uninitialized`, all-zero
  UUIDs), and wraps the winning raw identifier as a UUID v5 under a
  stable project namespace. Callers that need a different policy can
  compose their own chain via `resolve_with`.
- Platform identity sources: `/etc/machine-id` and
  `/var/lib/dbus/machine-id` on Linux, SMBIOS `product_uuid` on Linux,
  `IOPlatformUUID` on macOS, `HKLM\...\Cryptography\MachineGuid` on
  Windows, `/etc/hostid` and `kenv smbios.system.uuid` on FreeBSD,
  `sysctl kern.hostid` on NetBSD/OpenBSD, `hostid(1)` on
  illumos/Solaris.
- Container-scope sources (behind the `container` feature): runtime
  ID from `/proc/self/mountinfo` and LXC/LXD container name from
  `/proc/self/cgroup`, both salted with `/etc/machine-id` so identical
  container names on different hosts do not collide.
- Cloud metadata sources (behind `cloud-aws`, `cloud-gcp`,
  `cloud-azure`, `cloud-digitalocean`, `cloud-hetzner`, `cloud-oci`):
  AWS IMDSv2, GCP Compute Engine, Azure IMDS, DigitalOcean, Hetzner,
  OCI. Network sources are generic over a caller-supplied
  `HttpTransport` — the crate ships no HTTP client.
- Kubernetes sources (behind the `kubernetes` feature): pod UID from
  `/proc/self/mountinfo`, service-account namespace, and downward-API
  projected files.
- Operator overrides: `HOST_IDENTITY` environment variable and a
  file-path override, both consulted ahead of every platform source.
- `host-identity-cli` (`host-identity`) binary: `resolve` (default),
  `audit` (walk every source without short-circuiting and report each
  outcome), `sources` (list compiled-in sources), and `--version`.
  Man pages are generated from the clap schema.
- Packaging for Debian/Ubuntu (`.deb`), RHEL/Fedora/Amazon Linux
  (`.rpm`), Alpine (`.apk`), FreeBSD (`.pkg`), Homebrew, and Scoop.
  Release artefacts ship with CycloneDX SBOMs, minisign signatures
  over `SHA256SUMS`, and SLSA build provenance.
