From f25dcc14eb87ec139383c95e6a05d18e38876d3c Mon Sep 17 00:00:00 2001
From: Shubham Malik
Date: Tue, 7 Apr 2026 19:23:24 +0530
Subject: [PATCH 1/2] chore(mdm): update release process & resolve minor issues
---
.github/workflows/release.yml | 68 ++++------------------
.goreleaser.yml | 21 +++++--
CHANGELOG.md | 14 +++++
README.md | 2 +-
cmd/stepsecurity-dev-machine-guard/main.go | 15 ++---
examples/sample-output.json | 2 +-
internal/buildinfo/version.go | 4 +-
internal/output/html_test.go | 2 +-
internal/output/json_test.go | 2 +-
internal/output/pretty_test.go | 4 +-
internal/telemetry/telemetry.go | 2 +-
11 files changed, 57 insertions(+), 79 deletions(-)
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index ff0be71..d155ef0 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -7,12 +7,12 @@ permissions: {}
jobs:
release:
- name: Build, Sign & Release
+ name: Build & Draft Release
runs-on: ubuntu-latest
permissions:
- contents: write # create tag, release, and upload assets
- id-token: write # OIDC token for cosign keyless signing and build provenance
- attestations: write # SLSA build provenance
+ contents: write
+ id-token: write
+ attestations: write
steps:
- name: Harden the runner (Audit all outbound calls)
@@ -36,12 +36,11 @@ jobs:
tag="v${version}"
echo "version=${version}" >> "$GITHUB_OUTPUT"
echo "tag=${tag}" >> "$GITHUB_OUTPUT"
- echo "Detected version: ${version} (tag: ${tag})"
- name: Check tag does not already exist
run: |
if git rev-parse "refs/tags/${{ steps.version.outputs.tag }}" >/dev/null 2>&1; then
- echo "::error::Tag ${{ steps.version.outputs.tag }} already exists. Bump Version in internal/buildinfo/version.go before releasing."
+ echo "::error::Tag ${{ steps.version.outputs.tag }} already exists."
exit 1
fi
@@ -69,70 +68,25 @@ jobs:
- name: Install cosign
uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0
- - name: Prepare release artifacts for signing
+ - name: Sign artifacts with Sigstore
run: |
- # Copy binaries to match the exact names users download from the release.
- # GoReleaser uploads as name_template (e.g. stepsecurity-dev-machine-guard_darwin_amd64)
- # but keeps them in build subdirs locally. We copy to dist/ with release names
- # so cosign signs the same bytes users verify against.
- AMD64_SRC=$(find dist -type f -name 'stepsecurity-dev-machine-guard' -path '*darwin_amd64*' | head -1)
- ARM64_SRC=$(find dist -type f -name 'stepsecurity-dev-machine-guard' -path '*darwin_arm64*' | head -1)
-
- for label in "amd64:${AMD64_SRC}" "arm64:${ARM64_SRC}"; do
- name="${label%%:*}"
- path="${label#*:}"
- if [ -z "$path" ] || [ ! -f "$path" ]; then
- echo "::error::Binary not found for ${name}"
- find dist -type f
- exit 1
- fi
- done
-
- cp "$AMD64_SRC" dist/stepsecurity-dev-machine-guard_darwin_amd64
- cp "$ARM64_SRC" dist/stepsecurity-dev-machine-guard_darwin_arm64
- echo "Prepared release artifacts for signing"
-
- - name: Sign artifacts with Sigstore (keyless)
- run: |
- cosign sign-blob dist/stepsecurity-dev-machine-guard_darwin_amd64 \
- --bundle dist/stepsecurity-dev-machine-guard_darwin_amd64.bundle --yes
- cosign sign-blob dist/stepsecurity-dev-machine-guard_darwin_arm64 \
- --bundle dist/stepsecurity-dev-machine-guard_darwin_arm64.bundle --yes
+ ARCHIVE=$(find dist -name 'stepsecurity-dev-machine-guard-*-darwin.tar.gz' | head -1)
+ cosign sign-blob "$ARCHIVE" --bundle "${ARCHIVE}.bundle" --yes
cosign sign-blob stepsecurity-dev-machine-guard.sh \
--bundle dist/stepsecurity-dev-machine-guard.sh.bundle --yes
- - name: Generate checksums
- run: |
- # Separate checksum file for cosign-signed artifacts (script + bundles).
- # GoReleaser already generates checksums for the Go binaries in its own SHA256SUMS file.
- sha256sum dist/stepsecurity-dev-machine-guard_darwin_amd64 > dist/cosign-checksums.txt
- sha256sum dist/stepsecurity-dev-machine-guard_darwin_arm64 >> dist/cosign-checksums.txt
- sha256sum stepsecurity-dev-machine-guard.sh >> dist/cosign-checksums.txt
-
- - name: Upload signature bundles and checksums to release
+ - name: Upload cosign bundles
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release upload "${{ steps.version.outputs.tag }}" \
- dist/stepsecurity-dev-machine-guard_darwin_amd64.bundle \
- dist/stepsecurity-dev-machine-guard_darwin_arm64.bundle \
+ dist/stepsecurity-dev-machine-guard-*-darwin.tar.gz.bundle \
dist/stepsecurity-dev-machine-guard.sh.bundle \
- dist/cosign-checksums.txt \
--clobber
- - name: Mark release as immutable (not a draft, not a prerelease)
- env:
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: |
- gh release edit "${{ steps.version.outputs.tag }}" \
- --draft=false \
- --prerelease=false \
- --latest
-
- name: Attest build provenance
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
with:
subject-path: |
- dist/stepsecurity-dev-machine-guard_darwin_amd64
- dist/stepsecurity-dev-machine-guard_darwin_arm64
+ dist/stepsecurity-dev-machine-guard-*-darwin.tar.gz
stepsecurity-dev-machine-guard.sh
diff --git a/.goreleaser.yml b/.goreleaser.yml
index 74747e1..381ab10 100644
--- a/.goreleaser.yml
+++ b/.goreleaser.yml
@@ -21,14 +21,23 @@ builds:
env:
- CGO_ENABLED=0
-archives:
- - format: binary
- name_template: "{{ .Binary }}_{{ .Os }}_{{ .Arch }}"
+universal_binaries:
+ - id: universal
+ ids:
+ - stepsecurity-dev-machine-guard
+ replace: true
+ name_template: stepsecurity-dev-machine-guard
-checksum:
- name_template: "{{ .ProjectName }}_{{ .Version }}_SHA256SUMS"
- algorithm: sha256
+archives:
+ - id: archive
+ ids:
+ - universal
+ formats:
+ - tar.gz
+ strip_binary_directory: true
+ name_template: "stepsecurity-dev-machine-guard-{{ .Version }}-darwin"
release:
+ draft: true
extra_files:
- glob: stepsecurity-dev-machine-guard.sh
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d4a2343..1c6316e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,17 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
See [VERSIONING.md](VERSIONING.md) for why the version starts at 1.8.1.
+## [1.9.1] - 2026-04-07
+
+### Fixed
+
+- Config `quiet: false` now correctly shows progress (was ignored previously).
+- Enterprise auto-detect mode respects the configured quiet setting instead of overriding it.
+- Release now produces a single universal macOS binary (amd64 + arm64).
+
## [1.9.0] - 2026-04-03
Migrated from shell script to a compiled Go binary. All existing scanning features, detection logic, CLI flags, output formats, and enterprise telemetry are preserved — this release changes the implementation, not the functionality.
### Added
+
- **Go binary**: Single compiled binary (`stepsecurity-dev-machine-guard`) replaces the shell script. Zero external dependencies, no runtime required.
- **`configure` / `configure show` commands**: Interactive setup and display of enterprise credentials, search directories, and preferences. Saved to `~/.stepsecurity/config.json`.
## [1.8.2] - 2026-03-17
### Added
+
- `--search-dirs DIR [DIR...]` flag to scan specific directories instead of `$HOME` (replaces default; repeatable)
- Accepts multiple directories in a single flag: `--search-dirs /tmp /opt /var`
- Supports repeated use: `--search-dirs /tmp --search-dirs /opt`
@@ -28,6 +38,7 @@ Migrated from shell script to a compiled Go binary. All existing scanning featur
First open-source release. The scanning engine was previously an internal enterprise tool (v1.0.0-v1.8.1) running in production. This release adds community mode for local-only scanning while keeping the enterprise codebase intact.
### Added
+
- **Community mode** with three output formats: pretty terminal, JSON, and HTML report
- **AI agent and CLI tool detection**: Claude Code, Codex, Gemini CLI, Kiro, Aider, OpenCode, and more
- **General-purpose AI agent detection**: OpenClaw, ClawdBot, GPT-Engineer, Claude Cowork
@@ -41,17 +52,20 @@ First open-source release. The scanning engine was previously an internal enterp
- ShellCheck CI workflow with Harden-Runner
### Changed
+
- Enterprise config variables are now clearly labeled and placed below the community-facing header
- Progress messages suppressed by default in community mode (enable with `--verbose`)
- Node.js scanning off by default in community mode (enable with `--enable-npm-scan`)
### Enterprise (unchanged from v1.8.1)
+
- `install`, `uninstall`, and `send-telemetry` commands
- Launchd scheduling (LaunchDaemon for root, LaunchAgent for user)
- S3 presigned URL upload with backend notification
- Execution log capture and base64 encoding
- Instance locking to prevent concurrent runs
+[1.9.1]: https://github.com/step-security/dev-machine-guard/compare/v1.9.0...v1.9.1
[1.9.0]: https://github.com/step-security/dev-machine-guard/compare/v1.8.2...v1.9.0
[1.8.2]: https://github.com/step-security/dev-machine-guard/compare/v1.8.1...v1.8.2
[1.8.1]: https://github.com/step-security/dev-machine-guard/releases/tag/v1.8.1
diff --git a/README.md b/README.md
index 483910b..dca9199 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@
-
+
diff --git a/cmd/stepsecurity-dev-machine-guard/main.go b/cmd/stepsecurity-dev-machine-guard/main.go
index 916d038..d6cc6b5 100644
--- a/cmd/stepsecurity-dev-machine-guard/main.go
+++ b/cmd/stepsecurity-dev-machine-guard/main.go
@@ -37,26 +37,27 @@ func main() {
}
if !cfg.OutputFormatSet && config.OutputFormat != "" {
cfg.OutputFormat = config.OutputFormat
- cfg.OutputFormatSet = true // treat saved format as explicitly set
+ // Note: do NOT set OutputFormatSet here — saved config is a default preference,
+ // not an explicit CLI flag. Enterprise auto-detection should still work
+ // when no CLI flags are passed.
if config.OutputFormat == "html" && cfg.HTMLOutputFile == "" && config.HTMLOutputFile != "" {
cfg.HTMLOutputFile = config.HTMLOutputFile
}
}
exec := executor.NewReal()
- quiet := !cfg.Verbose
- // Apply saved quiet preference
- if config.Quiet != nil && *config.Quiet {
- quiet = true
+
+ // Quiet resolution: config is the base, CLI overrides.
+ quiet := true
+ if config.Quiet != nil {
+ quiet = *config.Quiet
}
- // --verbose always overrides quiet config
if cfg.Verbose {
quiet = false
}
if cfg.OutputFormat == "json" {
quiet = true
}
- // Enterprise commands (send-telemetry, install) always show progress
if cfg.Command == "send-telemetry" || cfg.Command == "install" {
quiet = false
}
diff --git a/examples/sample-output.json b/examples/sample-output.json
index 6244830..ce950aa 100644
--- a/examples/sample-output.json
+++ b/examples/sample-output.json
@@ -1,5 +1,5 @@
{
- "agent_version": "1.9.0",
+ "agent_version": "1.9.1",
"scan_timestamp": 1741305600,
"scan_timestamp_iso": "2026-03-07T00:00:00Z",
"device": {
diff --git a/internal/buildinfo/version.go b/internal/buildinfo/version.go
index 1d5dfc0..949b9b7 100644
--- a/internal/buildinfo/version.go
+++ b/internal/buildinfo/version.go
@@ -3,14 +3,14 @@ package buildinfo
import "fmt"
const (
- Version = "1.9.0"
+ Version = "1.9.1"
AgentURL = "https://github.com/step-security/dev-machine-guard"
)
// Build-time variables set via -ldflags by goreleaser or Makefile.
var (
GitCommit string // short commit hash (Makefile) or full commit (goreleaser)
- ReleaseTag string // e.g., "v1.9.0" (goreleaser only)
+ ReleaseTag string // e.g., "v1.9.1" (goreleaser only)
ReleaseBranch string // e.g., "main" (goreleaser only)
)
diff --git a/internal/output/html_test.go b/internal/output/html_test.go
index 9098a92..08fe980 100644
--- a/internal/output/html_test.go
+++ b/internal/output/html_test.go
@@ -13,7 +13,7 @@ func TestHTML_GeneratesFile(t *testing.T) {
defer os.Remove(tmpFile)
result := &model.ScanResult{
- AgentVersion: "1.9.0",
+ AgentVersion: "1.9.1",
ScanTimestamp: 1700000000,
ScanTimestampISO: "2023-11-14T22:13:20Z",
Device: model.Device{
diff --git a/internal/output/json_test.go b/internal/output/json_test.go
index f7cd061..22419ed 100644
--- a/internal/output/json_test.go
+++ b/internal/output/json_test.go
@@ -10,7 +10,7 @@ import (
func TestJSON_ValidOutput(t *testing.T) {
result := &model.ScanResult{
- AgentVersion: "1.9.0",
+ AgentVersion: "1.9.1",
AgentURL: "https://github.com/step-security/dev-machine-guard",
ScanTimestamp: 1700000000,
ScanTimestampISO: "2023-11-14T22:13:20Z",
diff --git a/internal/output/pretty_test.go b/internal/output/pretty_test.go
index 2fa656c..9f5971c 100644
--- a/internal/output/pretty_test.go
+++ b/internal/output/pretty_test.go
@@ -10,7 +10,7 @@ import (
func TestPretty_ContainsHeaders(t *testing.T) {
result := &model.ScanResult{
- AgentVersion: "1.9.0",
+ AgentVersion: "1.9.1",
ScanTimestamp: 1700000000,
ScanTimestampISO: "2023-11-14T22:13:20Z",
Device: model.Device{
@@ -40,7 +40,7 @@ func TestPretty_ContainsHeaders(t *testing.T) {
func TestPretty_ContainsBanner(t *testing.T) {
result := &model.ScanResult{
- AgentVersion: "1.9.0",
+ AgentVersion: "1.9.1",
ScanTimestamp: 1700000000,
Device: model.Device{Hostname: "test"},
AIAgentsAndTools: []model.AITool{},
diff --git a/internal/telemetry/telemetry.go b/internal/telemetry/telemetry.go
index 4edc629..5d36c56 100644
--- a/internal/telemetry/telemetry.go
+++ b/internal/telemetry/telemetry.go
@@ -65,7 +65,7 @@ type PerformanceMetrics struct {
// Output format matches the shell script's sample_log:
//
// ==========================================
-// StepSecurity Device Agent v1.9.0
+// StepSecurity Device Agent v1.9.1
// ==========================================
// [scanning] Lock acquired (PID: 32560)
// [scanning] Device ID (Serial): ...
From 3cbd1f0efa3b92361360e16fccdd9e4f69b186b6 Mon Sep 17 00:00:00 2001
From: Shubham Malik
Date: Wed, 8 Apr 2026 20:15:15 +0530
Subject: [PATCH 2/2] fix(mdm): update release flow
---
.github/workflows/release.yml | 19 ++++-
.goreleaser.yml | 10 +--
docs/release-process.md | 151 +++++++++++++++++-----------------
3 files changed, 93 insertions(+), 87 deletions(-)
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index d155ef0..a10f152 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -68,10 +68,21 @@ jobs:
- name: Install cosign
uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0
+ - name: Locate binary
+ id: binary
+ run: |
+ BINARY=$(find dist -type f -name '*darwin_unnotarized' | head -1)
+ if [ -z "$BINARY" ] || [ ! -f "$BINARY" ]; then
+ echo "::error::Binary not found"
+ find dist -type f
+ exit 1
+ fi
+ echo "path=$BINARY" >> "$GITHUB_OUTPUT"
+
- name: Sign artifacts with Sigstore
run: |
- ARCHIVE=$(find dist -name 'stepsecurity-dev-machine-guard-*-darwin.tar.gz' | head -1)
- cosign sign-blob "$ARCHIVE" --bundle "${ARCHIVE}.bundle" --yes
+ cosign sign-blob "${{ steps.binary.outputs.path }}" \
+ --bundle "${{ steps.binary.outputs.path }}.bundle" --yes
cosign sign-blob stepsecurity-dev-machine-guard.sh \
--bundle dist/stepsecurity-dev-machine-guard.sh.bundle --yes
@@ -80,7 +91,7 @@ jobs:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release upload "${{ steps.version.outputs.tag }}" \
- dist/stepsecurity-dev-machine-guard-*-darwin.tar.gz.bundle \
+ "${{ steps.binary.outputs.path }}.bundle" \
dist/stepsecurity-dev-machine-guard.sh.bundle \
--clobber
@@ -88,5 +99,5 @@ jobs:
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
with:
subject-path: |
- dist/stepsecurity-dev-machine-guard-*-darwin.tar.gz
+ ${{ steps.binary.outputs.path }}
stepsecurity-dev-machine-guard.sh
diff --git a/.goreleaser.yml b/.goreleaser.yml
index 381ab10..c5aeda9 100644
--- a/.goreleaser.yml
+++ b/.goreleaser.yml
@@ -26,16 +26,14 @@ universal_binaries:
ids:
- stepsecurity-dev-machine-guard
replace: true
- name_template: stepsecurity-dev-machine-guard
+ name_template: "stepsecurity-dev-machine-guard-{{ .Version }}-darwin_unnotarized"
archives:
- - id: archive
- ids:
+ - ids:
- universal
formats:
- - tar.gz
- strip_binary_directory: true
- name_template: "stepsecurity-dev-machine-guard-{{ .Version }}-darwin"
+ - binary
+ name_template: "stepsecurity-dev-machine-guard-{{ .Version }}-darwin_unnotarized"
release:
draft: true
diff --git a/docs/release-process.md b/docs/release-process.md
index 73c35c8..3e69ca5 100644
--- a/docs/release-process.md
+++ b/docs/release-process.md
@@ -1,6 +1,6 @@
# StepSecurity Dev Machine Guard — Release Process
-This document describes how releases are created, signed, and verified.
+This document describes how releases are created, signed, notarized, and verified.
> Back to [README](../README.md) | See also: [CHANGELOG](../CHANGELOG.md) | [Versioning](../VERSIONING.md)
@@ -8,16 +8,12 @@ This document describes how releases are created, signed, and verified.
## Overview
-Releases are created via a manually triggered GitHub Actions workflow (`workflow_dispatch`) that requires approval from the `release` environment. The workflow uses [GoReleaser](https://goreleaser.com/) to:
+Releases are a two-phase process:
-1. Read the version from `internal/buildinfo/version.go` (`const Version = "1.9.0"`)
-2. Verify the tag does not already exist (immutability)
-3. Build platform-specific binaries with GoReleaser
-4. Sign the binaries with [Sigstore](https://www.sigstore.dev/) cosign (keyless)
-5. Generate SHA256 checksums
-6. Create a Git tag and GitHub Release
-7. Attach binaries, Sigstore bundles, and checksums as release assets
-8. Generate SLSA build provenance attestation
+1. **CI (automated)** — GitHub Actions builds the universal macOS binary, signs it with Sigstore, and creates a **draft** release with the binary named `stepsecurity-dev-machine-guard-VERSION-darwin_unnotarized`.
+2. **Apple notarization (manual)** — Download the binary, sign and notarize it with an Apple Developer account, upload the notarized binary to the draft release, and publish.
+
+---
## How to Create a Release
@@ -26,109 +22,110 @@ Releases are created via a manually triggered GitHub Actions workflow (`workflow
Update `Version` in `internal/buildinfo/version.go`:
```go
-const Version = "1.9.0"
+const Version = "1.9.1"
```
-Update the [CHANGELOG.md](../CHANGELOG.md) with a new section for the version.
-
-Commit and push to `main`.
+Update [CHANGELOG.md](../CHANGELOG.md). Commit and push to `main`.
### 2. Trigger the release workflow
1. Go to [Actions > Release](https://github.com/step-security/dev-machine-guard/actions/workflows/release.yml)
-2. Click **Run workflow**
-3. Select the `main` branch
-4. Click **Run workflow**
+2. Click **Run workflow** on the `main` branch
-### 3. Approve the release
+The workflow will:
+- Create a git tag (`v1.9.1`)
+- Build a universal macOS binary (amd64 + arm64) via GoReleaser
+- Sign with Sigstore cosign (keyless)
+- Upload as `stepsecurity-dev-machine-guard-VERSION-darwin_unnotarized` to a **draft** release
+- Record the SHA256 of the unnotarized binary in the release notes
+- Generate SLSA build provenance attestation
-The workflow uses a GitHub Environment called `release` that requires approval. A designated reviewer must approve the run before it proceeds.
+### 3. Apple notarization (manual)
-### 4. Verify the release
+On a Mac with the Apple Developer certificate installed:
-Once approved, the workflow will create the tag, build the binaries, sign them, create the GitHub Release, and upload the artifacts. Check the [Releases page](https://github.com/step-security/dev-machine-guard/releases) to confirm.
+```bash
+VERSION="1.9.1"
----
+# Download the unnotarized binary
+gh release download "v${VERSION}" --repo step-security/dev-machine-guard \
+ --pattern "stepsecurity-dev-machine-guard-${VERSION}-darwin_unnotarized"
-## Release Artifacts
+# Rename for signing
+cp "stepsecurity-dev-machine-guard-${VERSION}-darwin_unnotarized" \
+ "stepsecurity-dev-machine-guard-${VERSION}-darwin"
-Each release includes the following artifacts:
+# Sign with Apple Developer ID
+codesign --sign "Developer ID Application: ()" \
+ --options runtime --timestamp "stepsecurity-dev-machine-guard-${VERSION}-darwin"
-| Artifact | Description |
-|----------|-------------|
-| `stepsecurity-dev-machine-guard_darwin_amd64` | macOS Intel binary |
-| `stepsecurity-dev-machine-guard_darwin_arm64` | macOS Apple Silicon binary |
-| `checksums.txt` | SHA256 checksums of all release artifacts |
-| `*.bundle` | Sigstore cosign bundles (signature, certificate, and Rekor transparency log entry) |
+# Notarize with Apple (~5 min)
+xcrun notarytool submit "stepsecurity-dev-machine-guard-${VERSION}-darwin" \
+ --apple-id --team-id \
+ --password --wait
----
-
-## Verifying a Release
-
-Anyone can verify the authenticity of a release artifact using [cosign](https://docs.sigstore.dev/cosign/system_config/installation/).
+# Upload the notarized binary to the draft release
+gh release upload "v${VERSION}" "stepsecurity-dev-machine-guard-${VERSION}-darwin" \
+ --repo step-security/dev-machine-guard
+```
-### Install cosign
+### 4. Publish the release
```bash
-# macOS
-brew install cosign
-
-# Other platforms: https://docs.sigstore.dev/cosign/system_config/installation/
+gh release edit "v${VERSION}" --repo step-security/dev-machine-guard \
+ --draft=false --latest
```
-### Verify the binary signature
+---
-```bash
-# Download the release artifacts
-gh release download v1.9.0 --repo step-security/dev-machine-guard
-
-# Verify the Sigstore signature (example for Apple Silicon)
-cosign verify-blob stepsecurity-dev-machine-guard_darwin_arm64 \
- --bundle stepsecurity-dev-machine-guard_darwin_arm64.bundle \
- --certificate-identity-regexp "github.com/step-security/dev-machine-guard" \
- --certificate-oidc-issuer "https://token.actions.githubusercontent.com"
-```
+## Release Artifacts
-A successful verification confirms:
+Each release includes:
-- The binary was signed by the `step-security/dev-machine-guard` GitHub Actions workflow
-- The signature is recorded in the [Rekor transparency log](https://search.sigstore.dev/)
-- The binary has not been tampered with since signing
+| Artifact | Description |
+|----------|-------------|
+| `stepsecurity-dev-machine-guard-VERSION-darwin` | Notarized universal macOS binary (amd64 + arm64) |
+| `stepsecurity-dev-machine-guard-VERSION-darwin_unnotarized` | Original CI-built binary (for provenance verification) |
+| `stepsecurity-dev-machine-guard-VERSION-darwin_unnotarized.bundle` | Sigstore cosign bundle for the unnotarized binary |
+| `stepsecurity-dev-machine-guard.sh` | Legacy shell script |
+| `stepsecurity-dev-machine-guard.sh.bundle` | Sigstore cosign bundle for the shell script |
-### Verify the checksum
+---
-```bash
-sha256sum -c checksums.txt
-```
+## Verifying a Release
-### Verify build provenance
+### Verify a release
```bash
-gh attestation verify stepsecurity-dev-machine-guard_darwin_arm64 \
- --repo step-security/dev-machine-guard
-```
+VERSION="1.9.1"
----
+# Download release artifacts
+gh release download "v${VERSION}" --repo step-security/dev-machine-guard \
+ --pattern "stepsecurity-dev-machine-guard-${VERSION}-darwin*"
-## Immutability Guarantees
+# Verify Apple signature and notarization
+codesign --verify --deep --strict "stepsecurity-dev-machine-guard-${VERSION}-darwin"
+spctl --assess --type execute "stepsecurity-dev-machine-guard-${VERSION}-darwin"
-Releases are designed to be immutable through multiple layers:
+# Verify Sigstore signature on the unnotarized binary
+cosign verify-blob "stepsecurity-dev-machine-guard-${VERSION}-darwin_unnotarized" \
+ --bundle "stepsecurity-dev-machine-guard-${VERSION}-darwin_unnotarized.bundle" \
+ --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
+ --certificate-identity-regexp "github.com/.*/dev-machine-guard"
-1. **Tag protection** — configure [tag protection rules](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/managing-repository-settings/configuring-tag-protection-rules) in repository settings to prevent tag deletion or overwriting.
-2. **Duplicate tag check** — the release workflow fails if the tag already exists, preventing accidental re-releases of the same version.
-3. **Sigstore transparency log** — every signature is recorded in the public [Rekor](https://rekor.sigstore.dev/) transparency log. Even if an artifact were replaced, verification against the original log entry would fail.
-4. **SLSA build provenance** — the attestation links the artifact to the exact workflow run, commit SHA, and build environment.
+# Verify build provenance
+gh attestation verify "stepsecurity-dev-machine-guard-${VERSION}-darwin_unnotarized" \
+ --repo step-security/dev-machine-guard
+```
---
-## Environment Setup
-
-The release workflow requires a GitHub Environment named `release` with required reviewers. To configure:
+## Immutability Guarantees
-1. Go to **Settings > Environments** in the repository
-2. Create an environment named `release`
-3. Enable **Required reviewers** and add the appropriate team members
-4. Optionally restrict to the `main` branch under **Deployment branches**
+1. **Draft → publish flow** — binaries are uploaded to a draft release, notarized manually, then published. Once published, the release is immutable.
+2. **Sigstore transparency log** — the unnotarized binary signature is recorded in the public [Rekor](https://rekor.sigstore.dev/) transparency log.
+3. **SLSA build provenance** — attestation links the artifact to the exact workflow run, commit SHA, and build environment.
+4. **Duplicate tag check** — the release workflow fails if the tag already exists.
---