From 6959804fdac9f4948941c53d4578bb98d9e9851d Mon Sep 17 00:00:00 2001 From: Alexis-Maurer Fortin Date: Thu, 18 Jun 2026 16:24:32 -0400 Subject: [PATCH] prevent a warning log when running the standalone when it parses configs --- cmd/bagel/root.go | 3 ++- pkg/config/config.go | 8 +++++-- pkg/config/config_test.go | 46 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 3 deletions(-) diff --git a/cmd/bagel/root.go b/cmd/bagel/root.go index e2df149..ab02447 100644 --- a/cmd/bagel/root.go +++ b/cmd/bagel/root.go @@ -128,7 +128,8 @@ func initConfig() { viper.AddConfigPath(config.GetConfigDir()) viper.AddConfigPath(".") viper.SetConfigName("bagel") - viper.SetConfigType("yaml") + // No SetConfigType: it would make viper match the extensionless "bagel" + // binary as a config file (see pkg/config.Load for the full rationale). } viper.SetEnvPrefix("BAGEL") diff --git a/pkg/config/config.go b/pkg/config/config.go index c82e459..4a0bbd6 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -26,9 +26,13 @@ func Load(configPath string) (*models.Config, error) { if configPath != "" { v.SetConfigFile(configPath) } else { - // Look for config in standard locations + // Look for config in standard locations. We deliberately do NOT call + // SetConfigType: with a type set, viper also matches an extensionless + // file named "bagel" in the search path — so running `./bagel scan` + // from the binary's own directory makes viper try to parse the bagel + // binary as YAML ("control characters are not allowed"). Without a + // type, viper only matches bagel.. v.SetConfigName("bagel") - v.SetConfigType("yaml") v.AddConfigPath(GetConfigDir()) v.AddConfigPath(".") } diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index ef1a1a3..ef04c7f 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -4,10 +4,13 @@ package config import ( + "bytes" "os" "path/filepath" "testing" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -108,3 +111,46 @@ func TestLoad_ExplicitConfigFailsLoudly(t *testing.T) { _, err := Load(missing) require.Error(t, err) } + +// TestLoad_ExtensionlessBinaryNotMatched guards the regression where running +// `./bagel scan` from the binary's own directory crashed with +// "yaml: control characters are not allowed": with a config type set, viper +// matches the extensionless `bagel` binary as a config file and tries to parse +// it. Load must not even attempt to parse it. +// +// This asserts on the log output, not just the absence of an error: the +// resilience switch in Load would swallow the parse failure and let the test +// pass regardless. The only thing that proves the binary was never parsed is +// that no "parsing config" failure was logged — which fails if SetConfigType +// is restored. +func TestLoad_ExtensionlessBinaryNotMatched(t *testing.T) { + logs := captureGlobalLog(t) + + t.Setenv("HOME", t.TempDir()) + dir := t.TempDir() + require.NoError(t, os.WriteFile(filepath.Join(dir, "bagel"), + []byte("\x7fELF\x02\x01\x01\x00\x00\x00"), 0o755)) // binary with control chars + t.Chdir(dir) + + cfg, err := Load("") + require.NoError(t, err) + require.NotNil(t, cfg) + assert.True(t, cfg.Probes.Git.Enabled) + assert.NotEmpty(t, cfg.FileIndex.Patterns) + + // The binary must never be fed to the YAML parser. + assert.NotContains(t, logs.String(), "parsing config", + "the bagel binary was matched and parsed as a config file; "+ + "is SetConfigType set during auto-discovery?") +} + +// captureGlobalLog redirects the package-global zerolog logger into a buffer +// for the duration of the test and restores it afterward. +func captureGlobalLog(t *testing.T) *bytes.Buffer { + t.Helper() + buf := &bytes.Buffer{} + prev := log.Logger + log.Logger = zerolog.New(buf) + t.Cleanup(func() { log.Logger = prev }) + return buf +}