From 3eb7150de9ccf109f30db70fdbab58bf34001418 Mon Sep 17 00:00:00 2001 From: ChethanUK Date: Wed, 15 Jul 2026 10:05:27 +0200 Subject: [PATCH] fix(scan): check user include patterns before the extension allowlist MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The scan path evaluated the built-in extension allowlist before user include globs, so an explicit include (e.g. **/*.ftl) could never force a non-allowlisted extension into a scan — while the preview/diff path already checks user includes first. The two paths disagreed about which files the same include selects. Reorders whyExcluded in scan/agent.go to match preview.go exactly: binary, user-exclude, user-include, extension allowlist, default excluded paths. User excludes still take precedence over includes, and binary/size guards still run regardless. Adds a regression test: a .ftl file with a matching include glob now yields ExcludeNone (fails on the previous ordering). Related to #371 --- internal/scan/agent.go | 9 ++++++--- internal/scan/coverage_test.go | 10 ++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/internal/scan/agent.go b/internal/scan/agent.go index aa0b8342..8f740cd9 100644 --- a/internal/scan/agent.go +++ b/internal/scan/agent.go @@ -349,13 +349,16 @@ func (a *Agent) whyExcluded(it model.ScanItem) model.ExcludeReason { if a.args.FileFilter != nil && a.args.FileFilter.IsUserExcluded(path) { return model.ExcludeUserRule } + // User-include is checked before the extension allowlist (matching the + // preview/diff path in internal/agent) so an explicit include glob can + // admit otherwise-unsupported extensions (e.g. .ftl). See #371. + if a.args.FileFilter != nil && a.args.FileFilter.HasInclude() && a.args.FileFilter.IsUserIncluded(path) { + return model.ExcludeNone + } ext := extFromPath(path) if ext != "" && !allowedext.IsAllowedExt(ext) { return model.ExcludeExtension } - if a.args.FileFilter != nil && a.args.FileFilter.HasInclude() && a.args.FileFilter.IsUserIncluded(path) { - return model.ExcludeNone - } if allowedext.IsExcludedPath(path) { return model.ExcludeDefaultPath } diff --git a/internal/scan/coverage_test.go b/internal/scan/coverage_test.go index 9c4a1e2f..0fd9423e 100644 --- a/internal/scan/coverage_test.go +++ b/internal/scan/coverage_test.go @@ -155,6 +155,16 @@ func TestWhyExcluded_AllBranches(t *testing.T) { filter: &rules.FileFilter{Include: []string{"src/**"}}, want: model.ExcludeNone, }, + { + // #371: a user-include glob must override the built-in extension + // allowlist (as it already does on the preview/diff path). A + // non-allowlisted extension (.ftl) with a matching include glob + // must be included, not rejected as ExcludeExtension. + name: "user include overrides extension allowlist", + item: model.ScanItem{Path: "templates/email.ftl", Content: "x"}, + filter: &rules.FileFilter{Include: []string{"**/*.ftl"}}, + want: model.ExcludeNone, + }, { name: "default excluded path", item: model.ScanItem{Path: "pkg/handler_test.go", Content: "x"},