Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion cmd/bagel/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
8 changes: 6 additions & 2 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.<ext>.
v.SetConfigName("bagel")
v.SetConfigType("yaml")
v.AddConfigPath(GetConfigDir())
v.AddConfigPath(".")
}
Expand Down
46 changes: 46 additions & 0 deletions pkg/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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?")
Comment thread
SUSTAPLE117 marked this conversation as resolved.
}

// 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
}
Loading