GitHub
09/12/2026, 7:33 PMPOST /{env}/verify, the endpoint osctrld polls for what it should install.
osquery_sha256 in the verify response
osctrld#22 refuses to install an osquery package it cannot verify, and reads the digest from VerifyResponse.OsquerySHA256 (json:"osquery_sha256") before falling back to its own --osquery-sha256. osctrl did not send that field. It does now, sourced from a new osquery.sha256 setting, so nodes on that branch start verifying downloads with no client change.
Set it only on a uniform fleet. osquery ships a different artifact per format and architecture — deb amd64/arm64, rpm x86_64/aarch64, the macOS pkg, the Windows msi — and osctrld builds its download URL from its own `runtime.GOOS`/`GOARCH` without telling the server which one it will fetch. One scalar digest can therefore only be right for a deployment that installs a single package. Empty is the default and is safe, not a downgrade: osctrld then uses its own flag, or refuses. Returning a digest would make five of six platforms fail closed on a mismatch.
The complete fix for mixed fleets is small and spans both repos: osctrld sends its package format and arch on verify, osctrl keys digests by deb-amd64, rpm-x86_64, pkg, msi. Worth a follow-up issue against osctrld#22 — the server half is then a map lookup in the helper this PR adds.
--osquery-version had no reader
The flag is documented as "Version of osquery to be used" and params.Osquery.Version was populated at boot and consumed by nothing. Pinning a version in config or env changed nothing: verify handed every node the compile-time version.OsqueryVersion, and since that value is what osctrld installs and upgrades to, the whole fleet quietly tracked whatever osctrl was built against.
osqueryVersion() now resolves configured → trimmed → build-time fallback. The fallback matters: an empty string reaches osctrld's osqueryInstallDecision, which treats an unparseable side as "unknown" and reinstalls, so an operator with no pin would get reinstall churn rather than today's behavior.
The five package-filename call sites keep using the build constant (the MSI one moved onto it, so it stops being the odd one out). Those label a `.deb`/`.msi` osctrl didn't build — naming them after a config pin would make the label wrong in a new way.
Files
| File | Change |
| -------------------------------------- | --------------------------------------------------------------- |
| pkg/types/types.go | OsquerySHA256 on VerifyResponse, tag matching osctrld verbatim |
| pkg/config/types.go | osquery.sha256 setting |
| cmd/tls/handlers/handlers.go | osquerySHA256() and osqueryVersion(), both nil-safe and trimmed |
| cmd/tls/handlers/post.go | verify returns both; MSI filename consistency |
| deploy/config/tls.yml | documents the digest setting and when not to set it |
| cmd/tls/handlers/verify_sha256_test.go | 7 tests |
Not added to `api.yml`: osctrl-api never serves /verify, so the knob would be dead config there.
Tests
Config path, whitespace trimming (a digest pasted into YAML picks up newlines and neither side trims before comparing), both empty cases including a nil config, the version fallback chain, and a wire-format guard — renaming osquery_sha256 silently disables verification on every node, so that one is pinned explicitly.
go build ./... clean; cmd/..., pkg/types, pkg/config green. No OpenAPI regen: VerifyResponse is a TLS endpoint and doesn't appear in the API spec.
jmpsec/osctrlGitHub
09/12/2026, 7:39 PM