Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
be64cb7
ANE-1036: Support glob patterns in .fossa.yml path filters
zlav Apr 24, 2026
db3921b
Fix Windows glob path matching and link changelog entry
zlav Apr 24, 2026
05d99bc
Add glob filter test coverage for ?, character classes, root-level globs
zlav Apr 27, 2026
51f4f66
Document System.FilePattern '?'/'[...]' literal-match limitation in t…
zlav Apr 28, 2026
0ff98ea
Merge remote-tracking branch 'origin/master' into ane-1036-glob-exclu…
zlav May 4, 2026
772a8b0
Normalize backslashes in glob patterns; correct ?/[] doc semantics
zlav May 5, 2026
daa1cd9
Limit glob trigger to `*`; drop unreachable `?`/`[]` literal tests
zlav May 6, 2026
9e45762
Doc: show concrete example directories for each glob pattern
zlav May 6, 2026
39b220b
Echo active path filters at analyze startup
zlav May 6, 2026
fd76b52
Surface walker-pruned subtrees and add walker filter test coverage
zlav May 6, 2026
7b38be1
Revert Has Logger walker propagation; gate prune-enumeration walk
zlav May 6, 2026
c70b408
Merge remote-tracking branch 'origin/master' into ane-1036-glob-exclu…
zlav May 6, 2026
0dd78cd
Fix hlint hits in FiltersSpec backslash-normalization test
zlav May 6, 2026
e0e19ad
Fix duplicate viaShow import in Analyze.hs
zlav May 6, 2026
539d3c7
Condense changelog entry for 3.17.6 to one line
zlav May 6, 2026
05f78d8
Address CodeRabbit review: include-glob reachability + JSON shape + f…
zlav May 6, 2026
6321b48
Allow any path as ancestor when include-glob's literal prefix is empty
zlav May 6, 2026
7b94af4
Address CodeRabbit nitpicks: stdlib isPrefixOf + drop list comprehension
zlav May 7, 2026
6a964be
Drop logActivePathFilters per review feedback
zlav May 8, 2026
7110d89
Use unsnoc instead of double-reverse in trimTrailingSlash
zlav May 8, 2026
2e058b3
Rename glob ancestor-reachability test + clarify the comment
zlav May 8, 2026
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
4 changes: 4 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# FOSSA CLI Changelog

## 3.17.6

- Config: `paths.only` and `paths.exclude` in `.fossa.yml` now accept glob patterns. ([#1703](https://github.com/fossas/fossa-cli/pull/1703))

## 3.17.5

- Vendetta: Debug bundles now include per-file component match data from Vendetta scans, making it easier to diagnose why a vendored dependency was or wasn't detected. ([#1706](https://github.com/fossas/fossa-cli/pull/1706))
Expand Down
22 changes: 22 additions & 0 deletions docs/references/files/fossa-yml.md
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,28 @@ The list of paths to exclude from scanning in your directory.

This section is intended to be used as the inverse to `paths.only`. If you have a certain directory such as `development` you wish to exclude, `paths.exclude` enables you to do this.

#### Glob patterns

Entries in `paths.only` and `paths.exclude` may also be glob patterns. An entry is treated as a glob if it contains `*`; other entries keep their existing semantics (match the directory and all of its children). Glob matching follows [`System.FilePattern`][filepattern] semantics: `*` matches any sequence of characters within a single path segment, and `**` matches any number of segments.

Patterns use forward slashes (`/`) as path separators; backslashes are normalized so Windows-native patterns also work.

```yaml
paths:
exclude:
- "**/vendor/**"
- "**/node_modules/**"
- "build/generated/*"
```

Each example above excludes a different shape of directory:

- `**/vendor/**` skips Go-style vendored trees at any depth, e.g. `services/billing/vendor/k8s.io/apimachinery/pkg/apis/meta/v1/`.
- `**/node_modules/**` skips installed npm packages wherever they appear, e.g. `apps/web-frontend/node_modules/@babel/preset-env/lib/plugins/syntax-dynamic-import/`.
- `build/generated/*` is anchored at the repo root and matches *direct* children of `build/generated/` only. `build/generated/proto-go/` matches; the walker then prunes its entire subtree (e.g. `build/generated/proto-go/v1/messagepb/`).

[filepattern]: https://hackage.haskell.org/package/filepattern

### Analysis target configuration
Analysis target configuration allows you to select a very specific subset of your directory for scanning. The `targets` and `paths` sections allow users to configure which targets and directories should be scanned. This is useful if you have a custom test directory or development projects within the root project.

Expand Down
6 changes: 3 additions & 3 deletions docs/references/files/fossa-yml.v3.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -528,18 +528,18 @@
},
"paths": {
"type": "object",
"description": "The paths filtering section allows you to specify which paths should be scanned and which should not. The paths should be listed as their location from the root of your project.",
"description": "The paths filtering section allows you to specify which paths should be scanned and which should not. Entries may be concrete directory paths (which match the directory and all of its children) or glob patterns. An entry is treated as a glob if it contains `*`. Glob syntax follows System.FilePattern: `*` matches any sequence of characters within a single path segment, and `**` matches any number of segments.",
"properties": {
"only": {
"type": "array",
"description": "The list of paths to only allow scanning within.",
"description": "The list of paths or glob patterns to only allow scanning within.",
"items": {
"type": "string"
}
},
"exclude": {
"type": "array",
"description": "The list of paths to exclude from scanning in your directory.",
"description": "The list of paths or glob patterns to exclude from scanning in your directory.",
"items": {
"type": "string"
}
Expand Down
38 changes: 35 additions & 3 deletions src/App/Fossa/Analyze.hs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ import Data.Aeson qualified as Aeson
import Data.ByteString.Lazy qualified as BL
import Data.Error (createBody)
import Data.Flag (Flag, fromFlag)
import Data.Foldable (traverse_)
import Data.Foldable (for_, traverse_)
import Data.Functor (($>))
import Data.List.NonEmpty qualified as NE
import Data.Map qualified as Map
Expand All @@ -132,8 +132,9 @@ import Data.Traversable (for)
import Diag.Diagnostic as DI
import Diag.Result (resultToMaybe)
import Discovery.Archive qualified as Archive
import Discovery.Filters (AllFilters, MavenScopeFilters, applyFilters, filterIsVSIOnly, ignoredPaths, isDefaultNonProductionPath)
import Discovery.Filters (AllFilters (..), MavenScopeFilters, applyFilters, combinedPathGlobs, combinedPaths, filterIsVSIOnly, ignoredPaths, isDefaultNonProductionPath)
import Discovery.Projects (withDiscoveredProjects)
import Discovery.Walk (enumeratePrunedSubtrees)
import Effect.Exec (Exec)
import Effect.Logger (
Logger,
Expand Down Expand Up @@ -297,6 +298,32 @@ runAnalyzers allowedTactics filters withoutDefaultFilters basedir pathPrefix = d
where
single (DiscoverFunc f) = withDiscoveredProjects f basedir (runDependencyAnalysis basedir filters withoutDefaultFilters pathPrefix allowedTactics)

-- | Walk the tree once at startup and surface every directory the path
-- filters will prune. Each prune is logged once at info level here, instead
-- of emitting per-strategy duplicates from inside the walker (~28 strategies
-- would otherwise each report the same prune). Short-circuits when no path
-- filters are configured so the extra walk is only paid for when it can
-- produce output.
logPrunedSubtrees ::
( Has Logger sig m
, Has ReadFS sig m
, Has Diag.Diagnostics sig m
) =>
AllFilters ->
Path Abs Dir ->
m ()
logPrunedSubtrees filters basedir =
unless (noPathFilters filters) $ do
pruned <- enumeratePrunedSubtrees filters basedir
for_ pruned $ \p ->
logInfo $ "Skipping path " <> viaShow p <> " (excluded by paths filter)"
where
noPathFilters AllFilters{includeFilters = i, excludeFilters = e} =
null (combinedPaths i)
&& null (combinedPathGlobs i)
&& null (combinedPaths e)
&& null (combinedPathGlobs e)

analyze ::
( Has Debug sig m
, Has Diag.Diagnostics sig m
Expand Down Expand Up @@ -331,6 +358,12 @@ analyze cfg = Diag.context "fossa-analyze" $ do
withoutDefaultFilters = Config.withoutDefaultFilters cfg
enableSnippetScan = Config.snippetScan cfg
enableVendetta = Config.xVendetta cfg
-- Discovery runs with `mempty` when `--no-discovery-exclusion` is set
-- (see definition further down). Log against the same filter set so the
-- startup output matches what discovery actually applies.
discoveryFilters = if fromFlag NoDiscoveryExclusion noDiscoveryExclusion then mempty else filters

logPrunedSubtrees discoveryFilters basedir

manualDepsResult <-
Diag.errorBoundaryIO . diagToDebug $
Expand Down Expand Up @@ -434,7 +467,6 @@ analyze cfg = Diag.context "fossa-analyze" $ do
pure Nothing
else Diag.context "first-party-scans" . runStickyLogger SevInfo $ runFirstPartyScan basedir maybeApiOpts cfg
let firstPartyScanResults = join . resultToMaybe $ maybeFirstPartyScanResults
let discoveryFilters = if fromFlag NoDiscoveryExclusion noDiscoveryExclusion then mempty else filters
let strategyCfg =
(Config.strategyConfig cfg)
{ Config.useGitBackedCargoLocators = Config.UseGitBackedCargoLocators $ maybe True orgSupportsGitBackedCargoLocators orgInfo
Expand Down
10 changes: 6 additions & 4 deletions src/App/Fossa/Config/Common.hs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ import Data.String (IsString)
import Data.String.Conversion (ToText (toText))
import Data.Text (Text, null, strip, toLower)
import Diag.Result (Result (Failure, Success), renderFailure)
import Discovery.Filters (AllFilters (AllFilters), MavenScopeFilters (..), comboExclude, comboInclude, setExclude, setInclude, targetFilterParser)
import Discovery.Filters (AllFilters (AllFilters), MavenScopeFilters (..), comboExcludeWithGlobs, comboIncludeWithGlobs, partitionPathFilters, setExclude, setInclude, targetFilterParser)
import Effect.Exec (Exec)
import Effect.Logger (Logger, logDebug, logInfo, renderIt, vsep)
import Effect.ReadFS (ReadFS, doesDirExist, doesFileExist)
Expand Down Expand Up @@ -624,11 +624,13 @@ collectConfigFileFilters configFile = do
let pullFromFile :: (a -> [b]) -> (ConfigFile -> Maybe a) -> [b]
pullFromFile field section = maybe [] field (section configFile)
onlyT = pullFromFile targetsOnly configTargets
onlyP = pullFromFile pathsOnly configPaths
(onlyP, onlyG) = partitionPathFilters $ pullFromFile pathsOnly configPaths
excludeT = pullFromFile targetsExclude configTargets
excludeP = pullFromFile pathsExclude configPaths
(excludeP, excludeG) = partitionPathFilters $ pullFromFile pathsExclude configPaths

AllFilters (comboInclude onlyT onlyP) (comboExclude excludeT excludeP)
AllFilters
(comboIncludeWithGlobs onlyT onlyP onlyG)
(comboExcludeWithGlobs excludeT excludeP excludeG)

collectConfigMavenScopeFilters :: ConfigFile -> MavenScopeFilters
collectConfigMavenScopeFilters configFile = do
Expand Down
6 changes: 4 additions & 2 deletions src/App/Fossa/Config/ConfigFile.hs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ module App.Fossa.Config.ConfigFile (
ConfigRevision (..),
ConfigTargets (..),
ConfigPaths (..),
PathFilter (..),
ConfigTelemetry (..),
ConfigTelemetryScope (..),
ExperimentalConfigs (..),
Expand Down Expand Up @@ -57,6 +58,7 @@ import Data.Set qualified as Set
import Data.String.Conversion (ToString (toString), ToText (toText))
import Data.Text (Text, strip, toLower)
import Diag.Diagnostic (ToDiagnostic (..))
import Discovery.Filters (PathFilter (..))
import Effect.Logger (
Logger,
logDebug,
Expand Down Expand Up @@ -257,8 +259,8 @@ data ConfigTargets = ConfigTargets
deriving (Eq, Ord, Show)

data ConfigPaths = ConfigPaths
{ pathsOnly :: [Path Rel Dir]
, pathsExclude :: [Path Rel Dir]
{ pathsOnly :: [PathFilter]
, pathsExclude :: [PathFilter]
}
deriving (Eq, Ord, Show)

Expand Down
Loading
Loading