Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
118 commits
Select commit Hold shift + click to select a range
f349508
feat(faultinject): add build-tagged self-update fault checkpoints
swarit-stepsecurity Oct 19, 2026
2942701
feat(updatestate): add self-update layout, state.json, lock and run r…
swarit-stepsecurity Oct 19, 2026
0bdc787
feat(updatestate): add EnsureProtectedDir for versions and state ACLs
swarit-stepsecurity Oct 19, 2026
967163d
feat(selfupdate): add Windows latest-binary client, verified download…
swarit-stepsecurity Oct 19, 2026
6b24aee
feat(launcher): add bootstrap mode (run, update, adopt-seed)
swarit-stepsecurity Oct 19, 2026
5717b68
feat(authenticode): verify timestamped signatures and compare the sig…
swarit-stepsecurity Oct 19, 2026
d3144ae
feat(schtasks): add the SYSTEM update task and bootstrap scan actions
swarit-stepsecurity Oct 19, 2026
5effb3e
feat(msi): ship the bootstrap and seed, adopt-seed on every install
swarit-stepsecurity Oct 19, 2026
350f83c
test(authenticode): run Windows PowerShell without the runner's PSMod…
swarit-stepsecurity Oct 19, 2026
a1e8417
feat(selfupdate): add the SYSTEM updater state machine
swarit-stepsecurity Oct 19, 2026
35cd006
feat(windows): wire self-update --system, self-check and update events
swarit-stepsecurity Oct 19, 2026
9a101c9
fix(updatestate): match run records by run_id
swarit-stepsecurity Oct 19, 2026
23ede4d
fix(updatestate): keep unknown state.json keys across Save
swarit-stepsecurity Oct 19, 2026
3d53163
fix(updatestate): never return nil quarantine or dedupe maps from Load
swarit-stepsecurity Oct 19, 2026
ff52fbb
fix(faultinject): panic on malformed fault specs
swarit-stepsecurity Oct 19, 2026
e01e0f9
fix(updatestate): read a directory at the state path as corrupt on Wi…
swarit-stepsecurity Oct 19, 2026
0a68278
test(selfupdate): assert per-event execution ids and guard required e…
swarit-stepsecurity Oct 19, 2026
5cd48fe
test(selfupdate): add the trace-driven U0 kill matrix
swarit-stepsecurity Oct 19, 2026
8446de7
fix(selfupdate): take dmg-linux from the same latest-binary response
swarit-stepsecurity Oct 19, 2026
9925c44
fix(selfupdate): report update_updater_failed only after reaching the…
swarit-stepsecurity Oct 19, 2026
ad9f030
Merge branch 'swarit/feat/wt/win-msi-self-update' into swarit/feat/wt…
swarit-stepsecurity Oct 19, 2026
d61e623
fix(msi): use release file names in the seed and only write auto_upda…
swarit-stepsecurity Oct 19, 2026
962d537
fix(updatestate): keep unknown keys inside outbox entries
swarit-stepsecurity Oct 19, 2026
8cd671a
fix(faultinject): exit 64 with a marker on a malformed spec and valid…
swarit-stepsecurity Oct 19, 2026
8240165
fix(updatestate): require a unique run_id in AppendRun
swarit-stepsecurity Oct 19, 2026
47e299c
test(selfupdate): give each kill-matrix install its own instance mutex
swarit-stepsecurity Oct 19, 2026
c670a46
test(selfupdate): make the non-sha256 checksum case reach the shape c…
swarit-stepsecurity Oct 19, 2026
ffbb219
fix(selfupdate): refuse redirects from https to http
swarit-stepsecurity Oct 19, 2026
5021d6e
fix(selfupdate): let NO_PROXY win over the WinHTTP machine proxy
swarit-stepsecurity Oct 19, 2026
78266cf
fix(authenticode): turn revocation checking off for GENERIC_VERIFY_V2
swarit-stepsecurity Oct 19, 2026
4a52630
fix(authenticode): take the publisher from the chain WinVerifyTrust v…
swarit-stepsecurity Oct 19, 2026
3845cb3
fix(msi): stop running DMG processes before files change; never auto-…
swarit-stepsecurity Oct 19, 2026
0964af0
ci: show the forged-bag and revocation-flag Authenticode tests in the…
swarit-stepsecurity Oct 19, 2026
5a4c486
fix(selfupdate): never drop a tick's events when the state write fails
swarit-stepsecurity Oct 19, 2026
ad7624d
fix(device): give the SYSTEM updater an identity that never reads the…
swarit-stepsecurity Oct 19, 2026
650726d
fix(selfupdate): drop update events only on a validation reject
swarit-stepsecurity Oct 19, 2026
6ae8f6d
fix(selfupdate): keep probation and quarantine timing sane across clo…
swarit-stepsecurity Oct 19, 2026
09bbb4d
feat(msi): let AUTOUPDATE=1 re-enable self-update; unset leaves it alone
swarit-stepsecurity Oct 19, 2026
5ea5a4b
fix(selfupdate): count Bad runs toward an inconclusive probation
swarit-stepsecurity Oct 19, 2026
121006c
fix(selfupdate): give the smoke test its own time budget
swarit-stepsecurity Oct 19, 2026
6d9a814
fix(selfupdate): run the smoke self-check in a kill-on-close job
swarit-stepsecurity Oct 19, 2026
944cce7
fix(selfupdate): keep a deferred target staged and count deferrals pe…
swarit-stepsecurity Oct 19, 2026
a8f0194
fix(updatestate): read a higher state schema instead of rebuilding on…
swarit-stepsecurity Oct 19, 2026
97a6672
fix(bootstrap): read only schema/current/previous; never replace an e…
swarit-stepsecurity Oct 19, 2026
4d8189b
test(selfupdate): close the kill-matrix and unit test gaps
swarit-stepsecurity Oct 19, 2026
544d1ee
fix(bootstrap): lock runs\<v>.json around every run-record write
swarit-stepsecurity Oct 19, 2026
56adb3a
test(msi-smoke): assert the versions/state DACL exactly
swarit-stepsecurity Oct 19, 2026
1a10a60
feat(bootstrap): set RunningVersion whenever adopt-seed sets current
swarit-stepsecurity Oct 19, 2026
c9c1f4b
test(windows): cover the bootstrap tree kill and uninstall's process …
swarit-stepsecurity Oct 19, 2026
b3c510d
fix(bootstrap): let an expired seed quarantine stop blocking adopt-seed
swarit-stepsecurity Oct 19, 2026
7b8446c
fix(windows): parse the bootstrap fallback reason and the version it ran
swarit-stepsecurity Oct 19, 2026
a528ef9
test(msi-smoke): cover AUTOUPDATE against a false baseline and the SC…
swarit-stepsecurity Oct 19, 2026
4985efc
test(msi-smoke): call AutoUpdate before comparing it
swarit-stepsecurity Oct 19, 2026
4158ae6
fix(selfupdate): stop waiting for the scan mutex once the wait is use…
swarit-stepsecurity Oct 19, 2026
024641e
fix(schtasks): count an already-exiting DMG process as stopped on uni…
swarit-stepsecurity Oct 19, 2026
69d96f8
fix(updatestate): never evict an open run record when trimming
swarit-stepsecurity Oct 19, 2026
f2fbb77
fix(telemetry): take the run-record lock for the started marker
swarit-stepsecurity Oct 19, 2026
edaa83d
fix(bootstrap): record updater failures through the tolerant state read
swarit-stepsecurity Oct 19, 2026
431f414
fix(bootstrap): count updater failures on newer-schema states too
swarit-stepsecurity Oct 19, 2026
7d97065
fix(updatestate): abandon dead launches' records and never evict the …
swarit-stepsecurity Oct 19, 2026
572ab74
fix(faultinject): reserve exit code 64 for a malformed spec
swarit-stepsecurity Oct 19, 2026
5616937
fix(selfupdate): decide the env proxy per scheme before the machine p…
swarit-stepsecurity Oct 19, 2026
dbbe689
fix(device): never rewrite the shared device-id from a fallback probe
swarit-stepsecurity Oct 19, 2026
408df6a
fix(selfupdate): stop keeping a deferred target once it is quarantined
swarit-stepsecurity Oct 19, 2026
eee6824
fix(selfupdate): keep repeats quiet while state.json can't be written
swarit-stepsecurity Oct 19, 2026
104a55f
Merge branch 'swarit/feat/wt/win-msi-self-update-boot' into swarit/fe…
swarit-stepsecurity Oct 19, 2026
360f5ba
fix(updatestate): bump generation in the bootstrap's updater record a…
swarit-stepsecurity Oct 19, 2026
051c096
fix(windows): read the fallback suffix as the version that failed, pe…
swarit-stepsecurity Oct 19, 2026
1a1bcf1
refactor(selfupdate): find the seed with the bootstrap's FindSeed
swarit-stepsecurity Oct 19, 2026
b1f17b0
docs(windows): describe MSI self-update and add the changelog entry
swarit-stepsecurity Oct 19, 2026
c26cc9d
fix(updatestate): abandon a run record only when its bootstrap is gone
swarit-stepsecurity Oct 19, 2026
36eb264
fix(updatestate): never trim run records when reading
swarit-stepsecurity Oct 19, 2026
6d93445
fix(windows): resolve the SYSTEM self-check's device id via SystemID
swarit-stepsecurity Oct 19, 2026
db91ddf
docs(windows): correct the self-update docs, MSI README and a test co…
swarit-stepsecurity Oct 19, 2026
d29894c
fix(selfupdate): keep the smoke test from quarantining good targets o…
swarit-stepsecurity Oct 19, 2026
2a52597
fix(telemetry): end a recovered scan panic as a crash (exit 2)
swarit-stepsecurity Oct 19, 2026
e165fa5
fix(windows): run the scan watchdogs on awake time and treat a kill a…
swarit-stepsecurity Oct 19, 2026
7833192
fix(device): drop a cached configured device id once config no longer…
swarit-stepsecurity Oct 19, 2026
12a1f70
fix(updatestate): treat session-end exits as neutral runs
swarit-stepsecurity Oct 19, 2026
426050e
fix(selfupdate): count probation runs by sequence, not by time
swarit-stepsecurity Oct 19, 2026
0bbf8a3
docs(windows): note crash-after-started and launch numbering in the h…
swarit-stepsecurity Oct 19, 2026
0c6ac17
fix(selfupdate): adopt a newer MSI seed the adopt-seed action couldn't
swarit-stepsecurity Oct 19, 2026
076e795
test(selfupdate): stop the kill matrix's race-built children sleeping…
swarit-stepsecurity Oct 19, 2026
657bf19
fix(selfupdate): report missing checksums or signatures as a verify f…
swarit-stepsecurity Oct 20, 2026
c43d241
fix(updatestate): count every watchdog kill as a bad run
swarit-stepsecurity Oct 20, 2026
f3c7388
fix(windows): roll adopt-seed back when the MSI transaction fails
swarit-stepsecurity Oct 20, 2026
40c24a6
fix(windows): verify the seed before adopting it and wait out msiexec
swarit-stepsecurity Oct 20, 2026
13bfc5f
feat(faultinject): add a sleep<duration> action
swarit-stepsecurity Oct 20, 2026
66062cb
fix(windows): stop DMG processes and wait until MSI files are free
swarit-stepsecurity Oct 20, 2026
5783737
fix(msi): never reboot on our own and stop DMG before file actions
swarit-stepsecurity Oct 20, 2026
25584bd
fix(msi): schedule stop-for-msi with Before only (WIX0035)
swarit-stepsecurity Oct 20, 2026
affa3e1
fix(msi): run stop-for-msi after RemoveExistingProducts
swarit-stepsecurity Oct 20, 2026
0fe776f
ci(msi-smoke): judge only the pending renames a busy repair schedules
swarit-stepsecurity Oct 20, 2026
ef42e2c
fix(selfupdate): stop re-staging a version an AV keeps deleting files…
swarit-stepsecurity Oct 20, 2026
eef1694
fix(msi): stop DMG processes before a failed transaction restores files
swarit-stepsecurity Oct 20, 2026
8a5873e
chore(authenticode): annotate the SHA-1 key-identifier lookup for gosec
swarit-stepsecurity Oct 20, 2026
fe1e40f
chore(selfupdate): set the Windows self-update floor to 1.18.0, the r…
swarit-stepsecurity Oct 20, 2026
5ad3675
ci: run the fault-tagged telemetry tests and the winproc release-wait…
swarit-stepsecurity Oct 20, 2026
dee812d
ci: on Windows, run only the fault-tagged telemetry scan tests
swarit-stepsecurity Oct 20, 2026
22ee063
Merge branch 'main' into swarit/feat/wt/win-msi-self-update
swarit-stepsecurity Oct 23, 2026
2cd1c13
fix(selfupdate): enforce the self-check output caps on exec pipes
swarit-stepsecurity Oct 23, 2026
2cd4377
ci: run one command per Windows test step so no failure is masked
swarit-stepsecurity Oct 23, 2026
b571e1f
test(launcher): child must join its kill-on-close job before it runs
swarit-stepsecurity Oct 23, 2026
e86bc92
fix(launcher): put the child in its kill-on-close job before it runs
swarit-stepsecurity Oct 23, 2026
fb778f3
test(telemetry): run the hanging scan in a subprocess and reap it
swarit-stepsecurity Oct 23, 2026
08bc9a7
fix(selfupdate): derive the install root from the running binary
swarit-stepsecurity Oct 23, 2026
5eb1acb
fix(updatestate): use the real TrustedInstaller SID in the root check
swarit-stepsecurity Oct 23, 2026
a08a48b
fix(launcher): adopt-seed rollback retries a held version dir and log…
swarit-stepsecurity Oct 23, 2026
bb32e44
fix(config): configure --from-file merges into the existing config
swarit-stepsecurity Oct 24, 2026
9222408
fix(launcher): a failed upgrade's rollback restores the previous MSI …
swarit-stepsecurity Oct 24, 2026
4eb421e
ci(msi-smoke): BOOTSTRAPFILE upgrades keep an AUTOUPDATE=0 opt-out
swarit-stepsecurity Oct 24, 2026
280154b
docs(windows): add the self-update guide for IT admins (SCCM, Intune)
swarit-stepsecurity Oct 24, 2026
a48362d
docs(sccm): add self-update, the safe repair command and current upgr…
swarit-stepsecurity Oct 24, 2026
54efb57
docs: link the Windows self-update admin guide
swarit-stepsecurity Oct 24, 2026
3e9e6d8
Merge branch 'main' into swarit/feat/wt/win-msi-self-update
swarit-stepsecurity Oct 24, 2026
bf1dff9
test: bound every test subprocess with a deadline and a parent watch
swarit-stepsecurity Oct 24, 2026
11b9ea5
test(aiagents/cli): mock the hook executor and isolate HOME
swarit-stepsecurity Oct 24, 2026
ba05270
test: drop the unchecked Wait error in the helper guard tests
swarit-stepsecurity Oct 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/fuzz.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ jobs:
- ./internal/detector/configaudit
- ./internal/state
- ./internal/detector/credentials
- ./internal/updatestate
env:
CGO_ENABLED: '0'
# Short campaigns on the PR and merge-queue paths so they don't hold up a
Expand Down
540 changes: 394 additions & 146 deletions .github/workflows/msi-smoke.yml

Large diffs are not rendered by default.

44 changes: 42 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,28 @@ jobs:
with:
go-version-file: go.mod
- run: make test
- name: Test self-update fault injection (selfupdate_faults tag)
run: go test -race -count=1 -tags selfupdate_faults,selfupdate_testkeys ./internal/faultinject/ ./internal/updatestate/ ./internal/selfupdate/ ./internal/authenticode/ ./internal/telemetry/
- name: Test bootstrap fault checkpoints (selfupdate_faults tag)
run: go test -race -count=1 -tags selfupdate_faults ./internal/launcher/

# Self-update U0 harness on Linux: the trace-driven kill matrix and the
# state corruption matrix (portable parts; Windows runs them natively in
# test-windows-native).
test-selfupdate-linux:
name: Test (self-update kill matrix, linux)
runs-on: ubuntu-latest
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
with:
egress-policy: audit

- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0
with:
go-version-file: go.mod
- run: go test -race -count=1 -tags selfupdate_faults,selfupdate_testkeys ./internal/faultinject/ ./internal/updatestate/ ./internal/selfupdate/

# These packages have Windows-only code and Windows-specific behavior whose
# tests the macOS test job can only cross-compile, not run:
Expand All @@ -126,6 +148,9 @@ jobs:
# - executor: Readlink on a real directory junction (ModeIrregular entry,
# NT-namespace target), which the skills detector's junction folding
# depends on
# - updatestate: MoveFileEx state replace, LockFileEx, protected DACLs
# - selfupdate: native arch (IsWow64Process2), WinHTTP machine proxy
# - authenticode: WinVerifyTrust against a test-signed copy (elevated runner)
# - detector TestReal_*: the Kiro CLI registry reader against a scratch
# HKCU key, and skills junction folding over real junctions (the rest of
# the detector suite assumes a POSIX host, hence the -run filter)
Expand All @@ -144,10 +169,25 @@ jobs:
- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0
with:
go-version-file: go.mod
- run: go test -race -count=1 ./internal/devicepolicy/ ./internal/secureuserfile/ ./internal/detector/credentials/ ./internal/safepath/ ./internal/executor/
- run: go test -race -count=1 ./internal/devicepolicy/ ./internal/secureuserfile/ ./internal/detector/credentials/ ./internal/safepath/ ./internal/executor/ ./internal/updatestate/ ./internal/selfupdate/ ./internal/authenticode/ ./internal/awakeclock/
- run: go test -race -count=1 -run '^TestReal_' ./internal/detector/
- name: Test scheduled scan console suppression
run: go test -race -count=1 -run 'Test(IsTaskRegistered|DetectInvocationMethod_Windows|HideWindow)' ./internal/schtasks/ ./internal/telemetry/ ./internal/winproc/
run: go test -race -count=1 -run 'Test(IsTaskRegistered|DetectInvocationMethod_Windows|HideWindow|StopUnderThenWaitReleased)' ./internal/schtasks/ ./internal/telemetry/ ./internal/winproc/
# One command per step: the default pwsh shell only fails a step on the
# LAST native command's exit code, so a multi-line run would mask an
# earlier failure.
- name: Test self-update fault injection + U0 kill matrix (selfupdate_faults tag)
run: go test -race -count=1 -tags selfupdate_faults,selfupdate_testkeys ./internal/faultinject/ ./internal/updatestate/ ./internal/selfupdate/ ./internal/authenticode/
- name: Test scan panic/hang run classification (selfupdate_faults tag)
run: go test -race -count=1 -tags selfupdate_faults,selfupdate_testkeys -run 'Test(ScanPanicIsABadRun|ScanHangAfterStartedIsABadRun)' ./internal/telemetry/
- name: Authenticode against test-signed binaries (verbose, shows skips)
run: go test -count=1 -v -run 'TestVerify|TestForgedBag|TestTrustData|TestSameSeedPublisher' ./internal/authenticode/
- name: Test launcher bootstrap mode
run: go test -race -count=1 ./internal/launcher/
- name: Test launcher bootstrap fault checkpoints (selfupdate_faults tag)
run: go test -race -count=1 -tags selfupdate_faults ./internal/launcher/
- name: Test self-update scheduled tasks
run: go test -race -count=1 -run 'Test(UpdateTask|ResolveBootstrapRoot|BuildCreateArgs_Bootstrap|InstallUpdateTask|Uninstall_|TerminateUnder|WaitForInstanceMutex|StopProcess)' ./internal/schtasks/

smoke:
name: Smoke Tests
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ See [VERSIONING.md](VERSIONING.md) for why the version starts at 1.8.1.

### Added

- **Windows MSI installs update themselves.** The MSI now installs a stable bootstrap, a seed copy of the agent and a SYSTEM `StepSecurity Dev Machine Guard (Update)` task. The task follows the tenant's `latest-binary` target. It verifies each release's SSH-signed checksums, sha256 and Authenticode publisher (it must match the running agent), stages it as an immutable `versions\<v>` directory, smoke-tests it as SYSTEM and switches only when no scan is running. A new version is on probation until a scan reaches the backend; two crashes roll it back and quarantine it on that device. Every outcome is reported as a `run-status` event with `status: "update"`. The MSI never downgrades a self-updated device, and repairs keep the current version. `AUTOUPDATE=0` opts out (the device still reports `update_disabled`). Devices on older MSIs need one final MSI push. See `docs/windows-self-update.md`, and `docs/windows-self-update-admin.md` for SCCM/Intune admins (network and proxy requirements, the safe repair command, verifying devices and troubleshooting by update event).
- Passive PHP/Composer package inventory and configuration audit for developer machines. Collects project and global declarations, lockfiles and installed receipts, with redacted settings and configuration findings. PHP, Composer, plugins and scripts are never executed.
- GitHub Copilot plugin inventory for the CLI, standalone app and VS Code. Collects recorded installations and live directory-marketplace selections, supplied skills, custom agents, command/rule/hook/LSP declarations, and sanitized plugin/user/project MCP definitions. Collection is file-only; uncertain formats report incomplete coverage. Runtime activation is not inferred. No Copilot usage or credential collection is added.

Expand Down
69 changes: 33 additions & 36 deletions cmd/stepsecurity-dev-machine-guard-task/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,16 @@
// `powershell.exe -File loader.ps1 send-telemetry` in the same
// no-console envelope the MSI flow uses for the agent.
//
// - Bootstrap mode. Invoked under the file name
// stepsecurity-dev-machine-guard-bootstrap.exe (the MSI installs a
// second copy of this launcher under that name), the verbs run /
// update / adopt-seed resolve the self-update state.json pointer and
// launch the versioned binaries. See internal/launcher/bootstrap.go.
//
// When launched by the bootstrap (STEPSEC_BOOTSTRAP_VERSION set), failing to
// resolve or start the agent exits 70 so the run counts as bad (D5); other
// invocations keep their legacy exit codes.
//
// The agent (and any --exec target) stays console-subsystem so
// interactive CLI use continues to work normally.
//
Expand All @@ -40,14 +50,13 @@ import (
"os"
"os/exec"
"syscall"
"unsafe"

"github.com/step-security/dev-machine-guard/internal/faultinject"
"github.com/step-security/dev-machine-guard/internal/launcher"
"github.com/step-security/dev-machine-guard/internal/winproc"
"golang.org/x/sys/windows"
)

const createNoWindow uint32 = 0x08000000

func main() {
os.Exit(run(os.Args[1:]))
}
Expand All @@ -56,6 +65,11 @@ func main() {
// after the program name (os.Args[1:] in main); accepting it explicitly
// keeps the windows-only test path open if we add one later.
func run(argv []string) int {
if me, err := os.Executable(); err == nil && launcher.IsBootstrapBinary(me) {
return launcher.NewBootstrap(me, winproc.IsLocalSystem).Main(argv)
}
underBootstrap := os.Getenv(launcher.EnvBootstrapVersion) != ""

target, childArgs, err := launcher.ResolveTarget(argv)
if err != nil {
// Two distinct failure shapes, matched to the legacy contract:
Expand All @@ -76,16 +90,22 @@ func run(argv []string) int {
fmt.Fprintln(os.Stderr, err)
return 2
}
if underBootstrap {
return launcher.ExitCannotStart
}
return 1
}

cmd := exec.Command(target, childArgs...)
cmd.SysProcAttr = &syscall.SysProcAttr{
HideWindow: true,
CreationFlags: createNoWindow,
CreationFlags: launcher.CreateNoWindow,
}

if err := cmd.Start(); err != nil {
if underBootstrap {
return launcher.ExitCannotStart
}
return 1
}

Expand All @@ -94,17 +114,18 @@ func run(argv []string) int {
// exit, which fires the kill. Failure here only weakens lifecycle
// (orphan possible on forced termination), not the work the child
// was started to do.
if job, jerr := newKillOnCloseJob(); jerr == nil {
if h, oerr := windows.OpenProcess(
windows.PROCESS_SET_QUOTA|windows.PROCESS_TERMINATE,
false,
uint32(cmd.Process.Pid),
); oerr == nil {
_ = windows.AssignProcessToJobObject(job, h)
_ = windows.CloseHandle(h)
if job, jerr := launcher.NewKillOnCloseJob(); jerr == nil {
if aerr := launcher.AssignToJob(job, cmd.Process.Pid); aerr != nil {
_ = windows.CloseHandle(job)
}
}

// Lets a test build crash a version's scans after launch (harness
// baked fault boot.launched@user:exit2); a no-op in release builds.
if underBootstrap {
_ = faultinject.Point("boot.launched")
}

if err := cmd.Wait(); err != nil {
var ee *exec.ExitError
if errors.As(err, &ee) {
Expand All @@ -114,27 +135,3 @@ func run(argv []string) int {
}
return 0
}

func newKillOnCloseJob() (windows.Handle, error) {
job, err := windows.CreateJobObject(nil, nil)
if err != nil {
return 0, err
}
info := windows.JOBOBJECT_EXTENDED_LIMIT_INFORMATION{
BasicLimitInformation: windows.JOBOBJECT_BASIC_LIMIT_INFORMATION{
// BREAKAWAY_OK lets the agent's CREATE_BREAKAWAY_FROM_JOB spawns
// (WSL guest scans) outlive this run instead of being killed.
LimitFlags: windows.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | windows.JOB_OBJECT_LIMIT_BREAKAWAY_OK,
},
}
if _, err := windows.SetInformationJobObject(
job,
windows.JobObjectExtendedLimitInformation,
uintptr(unsafe.Pointer(&info)),
uint32(unsafe.Sizeof(info)),
); err != nil {
_ = windows.CloseHandle(job)
return 0, err
}
return job, nil
}
12 changes: 9 additions & 3 deletions cmd/stepsecurity-dev-machine-guard/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ func main() {
os.Exit(aiagentscli.RunHook(os.Stdin, os.Stdout, os.Stderr, os.Args[2:]))
}

// agent.prestart fault checkpoint and the Windows self-update verbs
// (self-update --system, self-check); they exit here.
runEarlyVerbs(os.Args[1:])

// Load persisted config (~/.stepsecurity/config.json) before parsing CLI
// --config must be honoured before Load(), which runs ahead of flag
// parsing and keeps the first values it reads.
Expand Down Expand Up @@ -238,6 +242,7 @@ func main() {
APIEndpoint: cfg.ConfigAPIEndpoint,
APIKey: cfg.ConfigAPIKey,
ScanFrequency: cfg.ConfigScanFrequency,
AutoUpdate: cfg.ConfigAutoUpdate,
}
if opts.APIKey == "" {
// Env-var fallback keeps the secret off the msiexec command
Expand Down Expand Up @@ -274,6 +279,7 @@ func main() {
log.Error("Enterprise configuration not found. Run '%s configure' or download the script from your StepSecurity dashboard.", os.Args[0])
os.Exit(1)
}
reportBootstrapFallback()
// Server-driven run gate: exit 0 quietly when the backend says this
// invocation isn't due (or another instance is mid-scan). Sits before
// the watchdog and telemetry.Run so a skipped wakeup posts no beacon,
Expand All @@ -294,7 +300,7 @@ func main() {
if telemetryErr != nil {
restoreTarget()
log.Error("%v", telemetryErr)
os.Exit(1)
os.Exit(telemetry.ExitCode(telemetryErr))
}
runHookStateReconcile(exec, log, cfg)
if targetOK {
Expand Down Expand Up @@ -417,7 +423,7 @@ func main() {
} else {
restoreTarget()
log.Error("%v", telemetryErr)
os.Exit(1)
os.Exit(telemetry.ExitCode(telemetryErr))
}
}
runHookStateReconcile(exec, log, cfg)
Expand Down Expand Up @@ -546,7 +552,7 @@ func main() {
restoreTarget()
if telemetryErr != nil {
log.Error("%v", telemetryErr)
os.Exit(1)
os.Exit(telemetry.ExitCode(telemetryErr))
}
default:
log.Debug("dispatch: community scan (default)")
Expand Down
Loading
Loading