Pulsobot security pack

Signing and supply chain

Security pack

Every public Pulsobot release is signed per platform, ships an SBOM, a signing ledger and a checksums file, and reaches downloads.pulsobot.com only through a promotion gate that refuses an incomplete release.

What a release carries

<VER> is the release version, for example 2026.10.3.

Asset What it is
pulsobot_<VER>_<os>_<arch> and pulsobot-installer_<VER>_<os>_<arch> archives pulsobot (CLI) and pulsobot-installer, for macOS, Windows and Linux (amd64, arm64)
pulsobot_<VER>_checksums.txt SHA-256 of every archive, the SBOM and the ledger, in sha256sum format. Computed from the final, signed bytes
pulsobot_<VER>_sbom.cdx.json CycloneDX JSON SBOM (generated with syft from the source at the release tag): Go modules, the dashboard's npm dependencies, the Python job wrappers
pulsobot_<VER>_signing.json Signing ledger: for each archive, the kind of signature found in the published binary (notarized, authenticode, unsigned) and whether it meets the platform's requirement

The download page links the checksums file, the SBOM and the release notes next to the downloads.

Per platform

Platform Signature How it is checked before publication
macOS Signed with Pulsover's Apple Developer ID and notarized by Apple The ledger opens the published binary, requires a Developer ID signature (not ad-hoc) and looks up its notarization ticket with Apple
Windows Authenticode, through Azure Artifact Signing signtool verify /pa on a Windows runner, then the ledger checks the published binary carries an Authenticode signature
Linux Not code-signed Covered by the checksums file and the SBOM

Code: .github/workflows/release-notarized.yml, release-windows-signed.yml, release-ledger.yml, scripts/release_signing_ledger.py.

Promotion gate

Internal builds never reach customers directly. A release is published on downloads.pulsobot.com only by the promotion workflow (.github/workflows/promote-release.yml), behind the public-release GitHub Environment, which needs a human approval. Its gate (scripts/release_promotion.py gate) fails closed unless the release has a complete signing ledger, a CycloneDX SBOM and a checksums file that verifies every promoted file. latest.json is never moved back to an older version.

Verify a download

Download the archive, pulsobot_<VER>_checksums.txt and, if you want it, the SBOM from the same release.

Checksums (all platforms):

# macOS
shasum -a 256 -c --ignore-missing pulsobot_<VER>_checksums.txt
# Linux
sha256sum -c --ignore-missing pulsobot_<VER>_checksums.txt
# Windows: compare with the matching line of the checksums file
Get-FileHash .\pulsobot_<VER>_windows_amd64.zip -Algorithm SHA256

macOS signature and notarization:

codesign --verify --strict --verbose=2 ./pulsobot-installer
codesign -dv --verbose=4 ./pulsobot-installer 2>&1 | grep -E 'Authority|TeamIdentifier'
spctl --assess --type execute --verbose ./pulsobot-installer   # "source=Notarized Developer ID"

The Authority chain must end with Developer ID Application: … for Pulsover.

Windows signature:

Get-AuthenticodeSignature .\pulsobot-installer.exe | Format-List Status, SignerCertificate, TimeStamperCertificate
signtool verify /pa /v .\pulsobot-installer.exe   # Windows SDK

Status must be Valid and the signer must name Pulsover.

SBOM: pulsobot_<VER>_sbom.cdx.json loads in any CycloneDX tool (Dependency-Track, grype sbom:pulsobot_<VER>_sbom.cdx.json, …) for your own vulnerability scanning.

Why the binaries are not obfuscated

The binaries are built with the standard Go linker flags -s -w (strip the symbol table and debug info; .goreleaser.yaml, .github/workflows/release.yml) and nothing more. We do not obfuscate them, on purpose: