From 6dfc81b6066fb95cbf76143766909d8ece773734 Mon Sep 17 00:00:00 2001 From: gnacho Date: Sun, 27 Sep 2026 21:28:12 +0200 Subject: [PATCH] fix(agent): strip inline comments in env file parsing (#883) loadEnvFile only skipped full-comment lines, so a line like NETPULSE_SCAN_INTERVAL=0 # comment yielded the literal value "0 # comment", missed the exact "0" match and fell back to the 30m default. Truncate values at the first " #" before trimming quotes; server-generated values (URL, token, SPKI FP) never contain " #". --- agent/VERSION | 2 +- agent/runtime/config.go | 9 ++++++++- agent/runtime/runtime_test.go | 29 +++++++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/agent/VERSION b/agent/VERSION index d9c62ed9..75a22a26 100644 --- a/agent/VERSION +++ b/agent/VERSION @@ -1 +1 @@ -3.0.2 \ No newline at end of file +3.0.3 diff --git a/agent/runtime/config.go b/agent/runtime/config.go index 3f07921b..c6b32d6b 100644 --- a/agent/runtime/config.go +++ b/agent/runtime/config.go @@ -100,7 +100,11 @@ func (o Options) Validate() error { return nil } -// loadEnvFile lee KEY=VALUE de un fichero env (líneas # = comentario). +// loadEnvFile lee KEY=VALUE de un fichero env. Soporta líneas de comentario +// completas (# al inicio) y comentarios inline (" #" tras el valor); estos +// últimos se recortan antes de quitar comillas y espacios. Los valores que +// genera el server (URL, token, FP SPKI) nunca contienen " #", así que +// recortar en el primero es seguro. func loadEnvFile(path string) map[string]string { out := map[string]string{} data, err := os.ReadFile(path) @@ -116,6 +120,9 @@ func loadEnvFile(path string) map[string]string { if !ok { continue } + if before, _, found := strings.Cut(v, " #"); found { + v = before + } out[strings.TrimSpace(k)] = strings.Trim(strings.TrimSpace(v), `"'`) } return out diff --git a/agent/runtime/runtime_test.go b/agent/runtime/runtime_test.go index 55658303..1c800f2d 100644 --- a/agent/runtime/runtime_test.go +++ b/agent/runtime/runtime_test.go @@ -62,6 +62,35 @@ NETPULSE_HEARTBEAT_FILE=/tmp/hb } } +// #883: los comentarios inline (" #" tras el valor) no deben formar parte +// del valor; en concreto NETPULSE_SCAN_INTERVAL=0 con comentario debe +// aplicar "sin scans periódicos", no caer en el default. +func TestLoadConfigInlineComments(t *testing.T) { + dir := t.TempDir() + base := "NETPULSE_SERVER=http://s\nNETPULSE_TOKEN=t\nNETPULSE_SLUG=s\n" + + env := filepath.Join(dir, "inline.env") + writeFile(t, env, base+"NETPULSE_SCAN_INTERVAL=0 # min entre scans de vecinos; \"0\" = sin scans periódicos\n") + opts, err := LoadConfigFromEnv(env) + if err != nil || opts.ScanInterval != probe.ScanDisabled { + t.Fatalf("SCAN_INTERVAL=0 con comentario inline: %v %v (quiero ScanDisabled)", opts.ScanInterval, err) + } + + // Comentario inline tras una duración y tras un valor entrecomillado. + env = filepath.Join(dir, "inline2.env") + writeFile(t, env, base+"NETPULSE_INTERVAL=15s # segundos entre beats\nNETPULSE_WAN_TARGET=\"1.1.1.1\" # objetivo WAN\n") + opts, err = LoadConfigFromEnv(env) + if err != nil { + t.Fatalf("LoadConfigFromEnv: %v", err) + } + if opts.Interval != 15*time.Second { + t.Fatalf("Interval con comentario inline: %v", opts.Interval) + } + if opts.WanTarget != "1.1.1.1" { + t.Fatalf("WanTarget entrecomillado con comentario inline: %q", opts.WanTarget) + } +} + func TestLoadConfigIntervalFormats(t *testing.T) { dir := t.TempDir() base := "NETPULSE_SERVER=http://s\nNETPULSE_TOKEN=t\nNETPULSE_SLUG=s\n"