Skip to content

Commit 3b6b0cd

Browse files
philipbalinovclaudearlimus
authored
🐛 fix: prevent binary self-update from disabling provider auto-update (#6851)
* 🐛 fix: prevent binary self-update from disabling provider auto-update After a binary self-update, the ExecUpdatedBinary() function was setting MONDOO_AUTO_UPDATE=false to prevent infinite update loops. However, this also disabled provider auto-installation because viper's AutomaticEnv() picks up MONDOO_AUTO_UPDATE and maps it to the auto_update config key. This fix introduces a separate internal environment variable (MONDOO_BINARY_SELF_UPDATE_SKIP) that is only used for preventing binary self-update loops. This ensures: 1. Binary self-update infinite loops are still prevented 2. Provider auto-installation works correctly in the re-exec'd process 3. User's explicit MONDOO_AUTO_UPDATE=false preference is still respected Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * 🧹 rename flag to MONDOO_AUTO_UPDATE_ENGINE to be consistent with other auto-update flag Signed-off-by: Dominik Richter <dominik.richter@gmail.com> * 🟢 use t.Setenv on tests Signed-off-by: Dominik Richter <dominik.richter@gmail.com> --------- Signed-off-by: Dominik Richter <dominik.richter@gmail.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: Dominik Richter <dominik.richter@gmail.com>
1 parent 395ac0e commit 3b6b0cd

File tree

4 files changed

+173
-8
lines changed

4 files changed

+173
-8
lines changed

cli/selfupdate/exec_unix.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ import (
1313
// ExecUpdatedBinary replaces the current process with the updated binary.
1414
// On Unix systems, this uses syscall.Exec which replaces the current process entirely.
1515
func ExecUpdatedBinary(binaryPath string, args []string) error {
16-
// Disable auto-update for the new process to prevent infinite loops
17-
os.Setenv(EnvAutoUpdate, "false")
16+
// Disable engine auto-update in the new process to prevent infinite update loops.
17+
// Provider auto-update (which reads MONDOO_AUTO_UPDATE via viper) is not affected.
18+
os.Setenv(EnvAutoUpdateEngine, "false")
1819

1920
// Replace the current process with the new binary
2021
// syscall.Exec replaces the current process image with the new one

cli/selfupdate/exec_windows.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ import (
1414
// On Windows, syscall.Exec is not available, so we spawn a new process,
1515
// wait for it to complete, and exit with its exit code.
1616
func ExecUpdatedBinary(binaryPath string, args []string) error {
17-
// Disable auto-update for the new process to prevent infinite loops
18-
os.Setenv(EnvAutoUpdate, "false")
17+
// Disable engine auto-update in the new process to prevent infinite update loops.
18+
// Provider auto-update (which reads MONDOO_AUTO_UPDATE via viper) is not affected.
19+
os.Setenv(EnvAutoUpdateEngine, "false")
1920

2021
// On Windows, we spawn the new process and wait for it to complete
2122
cmd := exec.Command(binaryPath, args[1:]...)

cli/selfupdate/selfupdate.go

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,14 @@ import (
3131
const (
3232
// DefaultRefreshInterval is the minimum time between update checks in seconds (1 hour)
3333
DefaultRefreshInterval = 3600
34-
// EnvAutoUpdate can be set to "false" or "0" to disable auto-update.
35-
// This is also set to "false" after an update to prevent infinite loops.
34+
// EnvAutoUpdate can be set to "false" or "0" to disable all auto-updates
35+
// (both engine binary and providers). When off, EnvAutoUpdateEngine is also off.
3636
EnvAutoUpdate = "MONDOO_AUTO_UPDATE"
37+
// EnvAutoUpdateEngine can be set to "false" or "0" to disable engine binary
38+
// auto-update specifically. It is also set to "false" after a binary self-update
39+
// to prevent infinite update loops. Provider auto-update (which reads
40+
// MONDOO_AUTO_UPDATE via viper) is not affected by this variable.
41+
EnvAutoUpdateEngine = "MONDOO_AUTO_UPDATE_ENGINE"
3742
// DefaultReleaseURL is the URL to fetch the latest release information
3843
DefaultReleaseURL = "https://releases.mondoo.com/mql/latest.json"
3944
// markerFilePrefix is the prefix for per-binary marker files that track when the last update check occurred.
@@ -79,9 +84,16 @@ func CheckAndUpdate(cfg Config) (bool, error) {
7984
return false, nil
8085
}
8186

82-
// Skip if auto-update is disabled (also prevents infinite loops after an update)
87+
// Skip if auto-update is disabled via environment (disables both engine and providers)
8388
if val := os.Getenv(EnvAutoUpdate); val == "false" || val == "0" {
84-
log.Debug().Msg("self-update: skipping, disabled via environment")
89+
log.Debug().Msg("self-update: skipping, disabled via " + EnvAutoUpdate)
90+
return false, nil
91+
}
92+
93+
// Skip if engine auto-update is specifically disabled (e.g., after a binary self-update
94+
// to prevent infinite loops, or when the user only wants provider auto-updates)
95+
if val := os.Getenv(EnvAutoUpdateEngine); val == "false" || val == "0" {
96+
log.Debug().Msg("self-update: skipping, disabled via " + EnvAutoUpdateEngine)
8597
return false, nil
8698
}
8799

cli/selfupdate/selfupdate_test.go

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
// Copyright (c) Mondoo, Inc.
2+
// SPDX-License-Identifier: BUSL-1.1
3+
4+
package selfupdate
5+
6+
import (
7+
"testing"
8+
9+
"github.com/stretchr/testify/assert"
10+
)
11+
12+
func TestCheckAndUpdate_EnvVarBehavior(t *testing.T) {
13+
t.Run("skips when MONDOO_AUTO_UPDATE is false", func(t *testing.T) {
14+
t.Setenv(EnvAutoUpdate, "false")
15+
t.Setenv(EnvAutoUpdateEngine, "")
16+
17+
cfg := Config{
18+
Enabled: true,
19+
CurrentVersion: "1.0.0",
20+
}
21+
22+
updated, err := CheckAndUpdate(cfg)
23+
assert.NoError(t, err)
24+
assert.False(t, updated)
25+
})
26+
27+
t.Run("skips when MONDOO_AUTO_UPDATE is 0", func(t *testing.T) {
28+
t.Setenv(EnvAutoUpdate, "0")
29+
t.Setenv(EnvAutoUpdateEngine, "")
30+
31+
cfg := Config{
32+
Enabled: true,
33+
CurrentVersion: "1.0.0",
34+
}
35+
36+
updated, err := CheckAndUpdate(cfg)
37+
assert.NoError(t, err)
38+
assert.False(t, updated)
39+
})
40+
41+
t.Run("skips when MONDOO_AUTO_UPDATE_ENGINE is false", func(t *testing.T) {
42+
t.Setenv(EnvAutoUpdate, "")
43+
t.Setenv(EnvAutoUpdateEngine, "false")
44+
45+
cfg := Config{
46+
Enabled: true,
47+
CurrentVersion: "1.0.0",
48+
}
49+
50+
updated, err := CheckAndUpdate(cfg)
51+
assert.NoError(t, err)
52+
assert.False(t, updated)
53+
})
54+
55+
t.Run("skips when MONDOO_AUTO_UPDATE_ENGINE is 0", func(t *testing.T) {
56+
t.Setenv(EnvAutoUpdate, "")
57+
t.Setenv(EnvAutoUpdateEngine, "0")
58+
59+
cfg := Config{
60+
Enabled: true,
61+
CurrentVersion: "1.0.0",
62+
}
63+
64+
updated, err := CheckAndUpdate(cfg)
65+
assert.NoError(t, err)
66+
assert.False(t, updated)
67+
})
68+
69+
t.Run("skips engine when MONDOO_AUTO_UPDATE is on but MONDOO_AUTO_UPDATE_ENGINE is off", func(t *testing.T) {
70+
t.Setenv(EnvAutoUpdate, "true")
71+
t.Setenv(EnvAutoUpdateEngine, "false")
72+
73+
cfg := Config{
74+
Enabled: true,
75+
CurrentVersion: "1.0.0",
76+
}
77+
78+
updated, err := CheckAndUpdate(cfg)
79+
assert.NoError(t, err)
80+
assert.False(t, updated)
81+
})
82+
83+
t.Run("MONDOO_AUTO_UPDATE off overrides MONDOO_AUTO_UPDATE_ENGINE on", func(t *testing.T) {
84+
t.Setenv(EnvAutoUpdate, "false")
85+
t.Setenv(EnvAutoUpdateEngine, "true")
86+
87+
cfg := Config{
88+
Enabled: true,
89+
CurrentVersion: "1.0.0",
90+
}
91+
92+
updated, err := CheckAndUpdate(cfg)
93+
assert.NoError(t, err)
94+
assert.False(t, updated)
95+
})
96+
97+
t.Run("does not skip when neither env var is set", func(t *testing.T) {
98+
t.Setenv(EnvAutoUpdate, "")
99+
t.Setenv(EnvAutoUpdateEngine, "")
100+
101+
cfg := Config{
102+
Enabled: true,
103+
CurrentVersion: "1.0.0-rolling", // Use rolling to skip network check
104+
}
105+
106+
// Will return false due to rolling version, but won't skip due to env vars
107+
updated, err := CheckAndUpdate(cfg)
108+
assert.NoError(t, err)
109+
assert.False(t, updated)
110+
})
111+
112+
t.Run("skips when config is disabled", func(t *testing.T) {
113+
t.Setenv(EnvAutoUpdate, "")
114+
t.Setenv(EnvAutoUpdateEngine, "")
115+
116+
cfg := Config{
117+
Enabled: false,
118+
CurrentVersion: "1.0.0",
119+
}
120+
121+
updated, err := CheckAndUpdate(cfg)
122+
assert.NoError(t, err)
123+
assert.False(t, updated)
124+
})
125+
126+
t.Run("skips for rolling version", func(t *testing.T) {
127+
t.Setenv(EnvAutoUpdate, "")
128+
t.Setenv(EnvAutoUpdateEngine, "")
129+
130+
cfg := Config{
131+
Enabled: true,
132+
CurrentVersion: "1.0.0-rolling",
133+
}
134+
135+
updated, err := CheckAndUpdate(cfg)
136+
assert.NoError(t, err)
137+
assert.False(t, updated)
138+
})
139+
}
140+
141+
// TestEnvVarSeparation verifies that MONDOO_AUTO_UPDATE_ENGINE is separate from
142+
// MONDOO_AUTO_UPDATE, ensuring that:
143+
// 1. Engine binary auto-update can be disabled independently of provider auto-update
144+
// 2. Provider auto-update (which reads MONDOO_AUTO_UPDATE via viper) is not affected
145+
func TestEnvVarSeparation(t *testing.T) {
146+
t.Run("env vars are different", func(t *testing.T) {
147+
assert.NotEqual(t, EnvAutoUpdate, EnvAutoUpdateEngine)
148+
assert.Equal(t, "MONDOO_AUTO_UPDATE", EnvAutoUpdate)
149+
assert.Equal(t, "MONDOO_AUTO_UPDATE_ENGINE", EnvAutoUpdateEngine)
150+
})
151+
}

0 commit comments

Comments
 (0)