From ac09426f10262dba374d6897079884e25f7ca780 Mon Sep 17 00:00:00 2001 From: Raphael Fakhri Date: Tue, 27 Oct 2026 03:58:53 +0200 Subject: [PATCH] fix(app): keep signed numeric env values unchanged (#14180) --- agent/utils/env/env.go | 15 +++++++------ agent/utils/env/env_test.go | 42 +++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 6 deletions(-) create mode 100644 agent/utils/env/env_test.go diff --git a/agent/utils/env/env.go b/agent/utils/env/env.go index 7f5e17db4..ae64eaeaa 100644 --- a/agent/utils/env/env.go +++ b/agent/utils/env/env.go @@ -29,7 +29,7 @@ func Write(envMap map[string]string, filename string) error { func Marshal(envMap map[string]string) (string, error) { lines := make([]string, 0, len(envMap)) for k, v := range envMap { - if d, err := strconv.Atoi(v); err == nil && !isStartWithZero(v) { + if d, ok := canonicalInt(v); ok { lines = append(lines, fmt.Sprintf(`%s=%d`, k, d)) } else if shouldUseSingleQuotes(v) { lines = append(lines, fmt.Sprintf(`%s='%s'`, k, v)) @@ -86,7 +86,7 @@ func MarshalWithOrder(envMap map[string]string, orders []string) (string, error) } func formatEnvLine(k, v string) string { - if d, err := strconv.Atoi(v); err == nil && !isStartWithZero(v) { + if d, ok := canonicalInt(v); ok { return fmt.Sprintf(`%s=%d`, k, d) } else if shouldUseSingleQuotes(v) { return fmt.Sprintf(`%s='%s'`, k, v) @@ -107,11 +107,14 @@ func GetEnvValueByKey(envPath, key string) (string, error) { return value, nil } -func isStartWithZero(value string) bool { - if strings.HasPrefix(value, "0") && len(value) > 1 { - return true +// canonicalInt reports whether value is an integer that formats back to the +// same string, so values such as "007", "+1" or "-01" keep their exact text. +func canonicalInt(value string) (int, bool) { + d, err := strconv.Atoi(value) + if err != nil || strconv.Itoa(d) != value { + return 0, false } - return false + return d, true } func hasEvenDoubleQuotes(s string) bool { diff --git a/agent/utils/env/env_test.go b/agent/utils/env/env_test.go new file mode 100644 index 000000000..fbe9cdec4 --- /dev/null +++ b/agent/utils/env/env_test.go @@ -0,0 +1,42 @@ +package env + +import ( + "testing" + + "github.com/joho/godotenv" +) + +func TestMarshalKeepsValuesExactly(t *testing.T) { + values := []string{"3306", "0", "-1", "007", "+123456", "-0123", "+0", "-0", "a'b", `a"b`, `a\b`} + for _, v := range values { + envMap := map[string]string{"PANEL_DB_ROOT_PASSWORD": v} + for name, marshal := range map[string]func(map[string]string) (string, error){ + "Marshal": Marshal, + "MarshalWithOrder": func(m map[string]string) (string, error) { + return MarshalWithOrder(m, []string{"PANEL_DB_ROOT_PASSWORD"}) + }, + } { + content, err := marshal(envMap) + if err != nil { + t.Fatalf("%s(%q) returned error: %v", name, v, err) + } + parsed, err := godotenv.Unmarshal(content) + if err != nil { + t.Fatalf("%s(%q) produced unparsable content %q: %v", name, v, content, err) + } + if got := parsed["PANEL_DB_ROOT_PASSWORD"]; got != v { + t.Errorf("%s(%q) wrote %q, which reads back as %q", name, v, content, got) + } + } + } +} + +func TestMarshalWritesCanonicalIntegersUnquoted(t *testing.T) { + content, err := Marshal(map[string]string{"PANEL_APP_PORT_HTTP": "8080"}) + if err != nil { + t.Fatal(err) + } + if content != "PANEL_APP_PORT_HTTP=8080" { + t.Fatalf("Marshal() = %q, want %q", content, "PANEL_APP_PORT_HTTP=8080") + } +}