From c2de12117698fd228aa4282194b809054ed78972 Mon Sep 17 00:00:00 2001 From: Thibault Koechlin Date: Thu, 2 Jul 2026 14:41:26 +0200 Subject: [PATCH] slightly improve error tracking related to appsec-config loading --- pkg/acquisition/modules/appsec/config.go | 4 ++++ pkg/appsec/appsec.go | 6 ++++++ pkg/appsec/appsec_config_test.go | 24 ++++++++++++++++++++++++ 3 files changed, 34 insertions(+) diff --git a/pkg/acquisition/modules/appsec/config.go b/pkg/acquisition/modules/appsec/config.go index 9f85eb52d6e..7c7439cb648 100644 --- a/pkg/acquisition/modules/appsec/config.go +++ b/pkg/acquisition/modules/appsec/config.go @@ -261,6 +261,10 @@ func (w *Source) Configure(ctx context.Context, yamlConfig []byte, logger *log.E appsecRuntime, err := appsecCfg.Build(ctx, w.hub) if err != nil { + if len(appsecCfg.LoadedFrom) > 0 { + return fmt.Errorf("unable to build appsec_config [%s]: %w", strings.Join(appsecCfg.LoadedFrom, ", "), err) + } + return fmt.Errorf("unable to build appsec_config: %w", err) } diff --git a/pkg/appsec/appsec.go b/pkg/appsec/appsec.go index 98a66a63173..08436398ccb 100644 --- a/pkg/appsec/appsec.go +++ b/pkg/appsec/appsec.go @@ -519,6 +519,10 @@ type AppsecConfig struct { LogLevel *log.Level `yaml:"log_level"` Logger *log.Entry `yaml:"-"` + + // LoadedFrom lists the appsec-config file(s) merged into this config, in load order. + // Used to track error origins at load time + LoadedFrom []string `yaml:"-"` } func (w *AppsecRuntimeConfig) NewRequestState() AppsecRequestState { @@ -607,6 +611,8 @@ func (wc *AppsecConfig) LoadByPath(file string) error { // into flat fields. Hooks stay in the phase sections for Build() to compile separately. tmp.normalizePhaseScoped() + wc.LoadedFrom = append(wc.LoadedFrom, file) + if wc.Name == "" && tmp.Name != "" { wc.Name = tmp.Name } diff --git a/pkg/appsec/appsec_config_test.go b/pkg/appsec/appsec_config_test.go index 234e7fd4e22..16dff75ee1c 100644 --- a/pkg/appsec/appsec_config_test.go +++ b/pkg/appsec/appsec_config_test.go @@ -1,6 +1,7 @@ package appsec import ( + "context" "os" "path/filepath" "testing" @@ -193,6 +194,29 @@ inband: // Phase-scoped hook from file 2 require.NotNil(t, cfg.InBand) assert.Len(t, cfg.InBand.OnMatch, 1) + + // Both source files are tracked, in load order. + assert.Equal(t, []string{f1, f2}, cfg.LoadedFrom) +} + +// A hook filter that fails to compile must surface an error naming the +// appsec-config file it came from, so `crowdsec -t` points at the faulty config. +func TestBuildErrorNamesSourceFile(t *testing.T) { + cfg := newTestConfig() + f := writeTempYAML(t, ` +name: broken-config +inband: + pre_eval: + - filter: "req.URL.Path matches '(?i)\\.(css)$'" +`) + + require.NoError(t, cfg.LoadByPath(f)) + require.Equal(t, []string{f}, cfg.LoadedFrom) + + // hub is unused: compilation fails before any rule is loaded. + _, err := cfg.Build(context.Background(), nil) + require.Error(t, err) + assert.Contains(t, err.Error(), "unable to compile filter") } func TestLoadByPathPhaseOptionsOverride(t *testing.T) {