Skip to content

Commit 4544a33

Browse files
committed
fix: prevent runner-managed CLI self-updates
1 parent c3af496 commit 4544a33

6 files changed

Lines changed: 103 additions & 0 deletions

File tree

internal/cli/root.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ var rootCmd = &cobra.Command{
6060
if cmd.CommandPath() == "flashduty update" {
6161
return nil
6262
}
63+
if update.IsManagedByRunner() {
64+
return nil
65+
}
6366
if !isTerminalFn(int(os.Stderr.Fd())) {
6467
return nil
6568
}

internal/cli/root_managed_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package cli
2+
3+
import (
4+
"runtime"
5+
"testing"
6+
7+
"github.com/flashcatcloud/flashduty-cli/internal/update"
8+
)
9+
10+
func TestRootSkipsAutoUpdateWhenManagedByRunner(t *testing.T) {
11+
saveAndResetGlobals(t)
12+
tmp := t.TempDir()
13+
t.Setenv("HOME", tmp)
14+
if runtime.GOOS == "windows" {
15+
t.Setenv("USERPROFILE", tmp)
16+
}
17+
t.Setenv("CI", "")
18+
t.Setenv("GITHUB_ACTIONS", "")
19+
t.Setenv("JENKINS_URL", "")
20+
t.Setenv("GITLAB_CI", "")
21+
t.Setenv("FLASHDUTY_NO_UPDATE_CHECK", "")
22+
t.Setenv("FLASHDUTY_MANAGED_BY_RUNNER", "1")
23+
24+
origIsTerminal := isTerminalFn
25+
isTerminalFn = func(int) bool { return true }
26+
t.Cleanup(func() { isTerminalFn = origIsTerminal })
27+
28+
called := false
29+
origCheck := checkForUpdateAutoFn
30+
checkForUpdateAutoFn = func(string) (*update.CheckResult, error) {
31+
called = true
32+
return &update.CheckResult{}, nil
33+
}
34+
t.Cleanup(func() { checkForUpdateAutoFn = origCheck })
35+
36+
if _, err := execCommand("version"); err != nil {
37+
t.Fatalf("version command should run in runner-managed mode: %v", err)
38+
}
39+
if called {
40+
t.Fatal("runner-managed CLI must not check for updates")
41+
}
42+
}

internal/cli/update.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ func newUpdateCmd() *cobra.Command {
1818
Use: "update",
1919
Short: "Update flashduty to the latest version",
2020
RunE: func(cmd *cobra.Command, _ []string) error {
21+
if update.IsManagedByRunner() {
22+
return fmt.Errorf("flashduty is managed by flashduty-runner; upgrade the runner instead")
23+
}
24+
2125
w := cmd.OutOrStdout()
2226
_, _ = fmt.Fprintf(w, "Current version: %s\n", versionStr)
2327
_, _ = fmt.Fprintf(w, "Checking for updates...\n")
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package cli
2+
3+
import (
4+
"net/http"
5+
"net/http/httptest"
6+
"strings"
7+
"testing"
8+
)
9+
10+
func TestUpdateRejectsRunnerManagedCLIWithoutChecking(t *testing.T) {
11+
saveAndResetGlobals(t)
12+
t.Setenv("FLASHDUTY_MANAGED_BY_RUNNER", "1")
13+
14+
checked := false
15+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
16+
checked = true
17+
_, _ = w.Write([]byte("v0.0.0\n"))
18+
}))
19+
t.Cleanup(server.Close)
20+
t.Setenv("FLASHDUTY_UPDATE_BASE_URL", server.URL)
21+
22+
_, err := execCommand("update", "--check")
23+
if err == nil || !strings.Contains(err.Error(), "managed by flashduty-runner") {
24+
t.Fatalf("expected runner-managed update rejection, got %v", err)
25+
}
26+
if checked {
27+
t.Fatal("runner-managed CLI must reject update before checking for releases")
28+
}
29+
}

internal/update/managed.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package update
2+
3+
import "os"
4+
5+
// IsManagedByRunner reports whether flashduty-runner owns this CLI binary.
6+
// The marker is intentionally exact so a user environment cannot
7+
// accidentally disable standalone CLI updates with a truthy-looking value.
8+
func IsManagedByRunner() bool {
9+
return os.Getenv("FLASHDUTY_MANAGED_BY_RUNNER") == "1"
10+
}

internal/update/managed_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package update
2+
3+
import "testing"
4+
5+
func TestIsManagedByRunner(t *testing.T) {
6+
t.Setenv("FLASHDUTY_MANAGED_BY_RUNNER", "1")
7+
if !IsManagedByRunner() {
8+
t.Fatal("runner-managed CLI was not detected")
9+
}
10+
11+
t.Setenv("FLASHDUTY_MANAGED_BY_RUNNER", "true")
12+
if IsManagedByRunner() {
13+
t.Fatal("only the runner's explicit marker value should enable managed mode")
14+
}
15+
}

0 commit comments

Comments
 (0)