Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
eafb3a6
feat: add --exclude flag to skip paths during scanning
Ankitsinghsisodya Jan 10, 2026
f0aa799
feat: add --exclude flag with glob and regex support
Ankitsinghsisodya Jan 10, 2026
d7e12fd
Merge remote-tracking branch 'origin/feat/exclude-paths-flag' into fe…
Ankitsinghsisodya Jan 14, 2026
f634bd1
fix: address lint issues for --exclude flag implementation
Ankitsinghsisodya Jan 14, 2026
d35d194
Merge branch 'main' into feat/exclude-paths-flag
Ankitsinghsisodya Jan 20, 2026
4223e6c
feat: Implement experimental skip directory functionality with explic…
Ankitsinghsisodya Jan 20, 2026
eab9cc8
feat: Add tests for the experimental `--skip-dir` flag with support f…
Ankitsinghsisodya Jan 20, 2026
65a8186
feat: Implement and test directory exclusion functionality using skip…
Ankitsinghsisodya Jan 20, 2026
edbbe2d
feat: Add `--experimental-skip-dir` flag with regex pattern support t…
Ankitsinghsisodya Jan 20, 2026
1138b63
refactor: rename skip directory patterns to exclude patterns and move…
Ankitsinghsisodya Jan 20, 2026
b508b49
refactor: streamline exclude patterns initialization and error handling
Ankitsinghsisodya Jan 20, 2026
a954c7e
refactor: Rename test case names to snake_case for consistency.
Ankitsinghsisodya Jan 20, 2026
cd30159
refactor: unexport exclude pattern types and functions for internal use.
Ankitsinghsisodya Jan 20, 2026
772c5a0
chore: Remove redundant vulnerability ID check and unused import from…
Ankitsinghsisodya Jan 20, 2026
0bc9ca0
build: Exclude `pkg/osvscanner/exclude.go` from the `forbidigo` linte…
Ankitsinghsisodya Jan 20, 2026
c783f97
Update pkg/osvscanner/exclude_test.go
Ankitsinghsisodya Jan 20, 2026
78d51d8
Update pkg/osvscanner/exclude_test.go
Ankitsinghsisodya Jan 20, 2026
e7ac260
refactor: rename `experimental-skip-dir` flag to `experimental-exclud…
Ankitsinghsisodya Jan 20, 2026
0ef512f
test: update `experimental-exclude` flag test names and add cases for…
Ankitsinghsisodya Jan 20, 2026
c8e92a9
refactor: Remove redundant boolean flags and simplify assertions in `…
Ankitsinghsisodya Jan 20, 2026
a2aeb05
Merge branch 'main' into feat/exclude-paths-flag
Ankitsinghsisodya Jan 20, 2026
3dc8bac
Update cmd/osv-scanner/scan/source/command.go
another-rex Jan 21, 2026
62b1e4e
Merge remote-tracking branch 'upstream/main' into feat/exclude-paths-…
another-rex Jan 29, 2026
b455ba2
Add the cassettes
another-rex Jan 29, 2026
28e12ca
Fix r: special case
another-rex Feb 3, 2026
9a564ef
Merge remote-tracking branch 'upstream/main' into feat/exclude-paths-…
another-rex Feb 5, 2026
85bfaf7
Update snapshots
another-rex Feb 6, 2026
95acdeb
snaps again...
another-rex Feb 9, 2026
56766fb
Merge remote-tracking branch 'upstream/main' into feat/exclude-paths-…
another-rex Feb 10, 2026
6d81367
Add snaps back in
another-rex Feb 10, 2026
2e4d9a9
Add log exclusion for windows only snapshot
another-rex Feb 11, 2026
11257de
Merge remote-tracking branch 'upstream/main' into feat/exclude-paths-…
another-rex Feb 11, 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
5 changes: 5 additions & 0 deletions cmd/osv-scanner/mcp/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
"time"

Expand Down Expand Up @@ -86,7 +87,11 @@ func TestIntegration_MCP_SSE_Subprocess(t *testing.T) {
t.Logf("Scan completed. Output length: %d", len(output))
testutility.NewSnapshot().MatchText(t, output)

// Verify the expected vulnerability is present in the output before using it
vulnID = "GO-2023-1558"
if !strings.Contains(output, vulnID) {
t.Fatalf("expected vulnerability %s not found in scan output", vulnID)
}
})

// Step 2: Get details for the found vulnerability
Expand Down
44 changes: 44 additions & 0 deletions cmd/osv-scanner/scan/source/__snapshots__/command_test.snap
Original file line number Diff line number Diff line change
Expand Up @@ -1471,6 +1471,50 @@ Total 11 packages affected by 45 known vulnerabilities (5 Critical, 19 High, 20

---

[TestCommand/skip-dir_with_exact_directory_name - 1]
Scanning dir ./testdata/locks-one-with-nested
Scanned <rootdir>/testdata/locks-one-with-nested/nested/composer.lock file and found 1 package
Scanned <rootdir>/testdata/locks-one-with-nested/yarn.lock file and found 1 package
No issues found

---

[TestCommand/skip-dir_with_exact_directory_name - 2]

---

[TestCommand/skip-dir_with_glob_pattern - 1]
Scanning dir ./testdata/locks-one-with-nested
Scanned <rootdir>/testdata/locks-one-with-nested/nested/composer.lock file and found 1 package
Scanned <rootdir>/testdata/locks-one-with-nested/yarn.lock file and found 1 package
No issues found

---

[TestCommand/skip-dir_with_glob_pattern - 2]

---

[TestCommand/skip-dir_with_invalid_regex_returns_error - 1]
Scanning dir ./testdata/locks-many
---

[TestCommand/skip-dir_with_invalid_regex_returns_error - 2]
failed to parse skip directory patterns: invalid regex pattern "[invalid": error parsing regexp: missing closing ]: `[invalid`

---

[TestCommand/skip-dir_with_regex_pattern - 1]
Scanning dir ./testdata/locks-one-with-nested
Scanned <rootdir>/testdata/locks-one-with-nested/yarn.lock file and found 1 package
No issues found

---

[TestCommand/skip-dir_with_regex_pattern - 2]

---

[TestCommand/spdx_2.3_output - 1]
{
"spdxVersion": "SPDX-2.3",
Expand Down
5 changes: 5 additions & 0 deletions cmd/osv-scanner/scan/source/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ func Command(stdout, stderr io.Writer, client *http.Client) *cli.Command {
Usage: "include scanning git root (non-submoduled) repositories",
Value: false,
},
&cli.StringSliceFlag{
Name: "experimental-skip-dir",
Usage: "skip directories during scanning; use g:pattern for glob, r:pattern for regex, or just dirname for exact match (can be repeated)",
},
&cli.StringFlag{
Name: "data-source",
Usage: "source to fetch package information from; value can be: deps.dev, native",
Expand Down Expand Up @@ -123,6 +127,7 @@ func action(_ context.Context, cmd *cli.Command, stdout, stderr io.Writer, clien
scannerAction.SBOMPaths = cmd.StringSlice("sbom")
scannerAction.Recursive = cmd.Bool("recursive")
scannerAction.NoIgnore = cmd.Bool("no-ignore")
scannerAction.SkipDirPatterns = cmd.StringSlice("experimental-skip-dir")
scannerAction.DirectoryPaths = cmd.Args().Slice()
scannerAction.ExperimentalScannerActions = experimentalScannerActions

Expand Down
21 changes: 21 additions & 0 deletions cmd/osv-scanner/scan/source/command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,27 @@ func TestCommand(t *testing.T) {
Args: []string{"", "source", "--recursive", "--no-ignore", "./testdata/locks-gitignore"},
Exit: 0,
},
// experimental skip-dir flag tests
{
Name: "skip-dir with exact directory name",
Args: []string{"", "source", "--recursive", "--experimental-skip-dir=nested", "./testdata/locks-one-with-nested"},
Exit: 0,
},
{
Name: "skip-dir with glob pattern",
Args: []string{"", "source", "--recursive", "--experimental-skip-dir=g:**/nested/**", "./testdata/locks-one-with-nested"},
Exit: 0,
},
{
Name: "skip-dir with regex pattern",
Args: []string{"", "source", "--recursive", "--experimental-skip-dir=r:/nested$", "./testdata/locks-one-with-nested"},
Exit: 0,
},
{
Name: "skip-dir with invalid regex returns error",
Args: []string{"", "source", "--experimental-skip-dir=r:[invalid", "./testdata/locks-many"},
Exit: 127,
},
{
Name: "json output",
Args: []string{"", "source", "--format", "json", "./testdata/locks-many/composer.lock"},
Expand Down
155 changes: 153 additions & 2 deletions cmd/osv-scanner/scan/source/testdata/cassettes/TestCommand.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6698,6 +6698,157 @@ interactions:
code: 200
duration: 0s
- id: 41
request:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
content_length: 278
host: api.osv.dev
body: |
{
"queries": [
{
"package": {
"ecosystem": "Packagist",
"name": "sentry/sdk"
},
"version": "2.0.4"
},
{
"package": {
"ecosystem": "npm",
"name": "balanced-match"
},
"version": "1.0.2"
}
]
}
headers:
Content-Type:
- application/json
X-Test-Name:
- TestCommand/skip-dir_with_exact_directory_name
url: https://api.osv.dev/v1/querybatch
method: POST
response:
proto: HTTP/2.0
proto_major: 2
proto_minor: 0
content_length: 19
body: |
{
"results": [
{},
{}
]
}
headers:
Content-Length:
- "19"
Content-Type:
- application/json
status: 200 OK
code: 200
duration: 0s
- id: 42
request:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
content_length: 278
host: api.osv.dev
body: |
{
"queries": [
{
"package": {
"ecosystem": "Packagist",
"name": "sentry/sdk"
},
"version": "2.0.4"
},
{
"package": {
"ecosystem": "npm",
"name": "balanced-match"
},
"version": "1.0.2"
}
]
}
headers:
Content-Type:
- application/json
X-Test-Name:
- TestCommand/skip-dir_with_glob_pattern
url: https://api.osv.dev/v1/querybatch
method: POST
response:
proto: HTTP/2.0
proto_major: 2
proto_minor: 0
content_length: 19
body: |
{
"results": [
{},
{}
]
}
headers:
Content-Length:
- "19"
Content-Type:
- application/json
status: 200 OK
code: 200
duration: 0s
- id: 43
request:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
content_length: 149
host: api.osv.dev
body: |
{
"queries": [
{
"package": {
"ecosystem": "npm",
"name": "balanced-match"
},
"version": "1.0.2"
}
]
}
headers:
Content-Type:
- application/json
X-Test-Name:
- TestCommand/skip-dir_with_regex_pattern
url: https://api.osv.dev/v1/querybatch
method: POST
response:
proto: HTTP/2.0
proto_major: 2
proto_minor: 0
content_length: 16
body: |
{
"results": [
{}
]
}
headers:
Content-Length:
- "16"
Content-Type:
- application/json
status: 200 OK
code: 200
duration: 0s
- id: 44
request:
proto: HTTP/1.1
proto_major: 1
Expand Down Expand Up @@ -6781,7 +6932,7 @@ interactions:
status: 200 OK
code: 200
duration: 0s
- id: 42
- id: 45
request:
proto: HTTP/1.1
proto_major: 1
Expand Down Expand Up @@ -6826,7 +6977,7 @@ interactions:
status: 200 OK
code: 200
duration: 0s
- id: 43
- id: 46
request:
proto: HTTP/1.1
proto_major: 1
Expand Down
44 changes: 44 additions & 0 deletions docs/scan-source.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,50 @@ There is a [known issue](https://github.com/google/osv-scanner/issues/209) that

The `--no-ignore` flag can be used to force the scanner to scan ignored files.

## Skipping directories

Experimental
{: .label }

You can skip specific directories from scanning using the `--experimental-skip-dir` flag. This is useful for excluding test directories, documentation, or vendor directories from vulnerability scans.

**Note:** This flag only skips directories, not individual files. This is an experimental feature and the syntax may change in future versions.

### Syntax

The flag supports three pattern types, matching the `--lockfile` flag syntax:

- **Exact directory name** (no prefix or `:` prefix): Matches directories with the exact name
- **Glob pattern** (`g:` prefix): Matches using glob patterns
- **Regex pattern** (`r:` prefix): Matches using regular expressions

### Examples

```bash
# Skip directories named "test" or "docs" (exact match)
osv-scanner scan source -r --experimental-skip-dir=test --experimental-skip-dir=docs /path/to/your/dir

# Skip using glob patterns
osv-scanner scan source -r --experimental-skip-dir="g:**/test/**" --experimental-skip-dir="g:**/docs/**" /path/to/your/dir

# Skip using regex patterns
osv-scanner scan source -r --experimental-skip-dir="r:.*_test$" /path/to/your/dir

# Mix different pattern types
osv-scanner scan source -r --experimental-skip-dir=vendor --experimental-skip-dir="g:**/test/**" --experimental-skip-dir="r:\\.cache" /path/to/your/dir

# Escape directory names containing colons using : prefix
osv-scanner scan source -r --experimental-skip-dir=":my:project" /path/to/your/dir
```

### Common use cases

- Excluding test directories: `--experimental-skip-dir=test` or `--experimental-skip-dir="g:**/test/**"`
- Excluding documentation: `--experimental-skip-dir=docs`
- Excluding vendor directories: `--experimental-skip-dir=vendor`

Alternatively, you can use the `osv-scanner.toml` configuration file with `[[PackageOverrides]]` to ignore specific packages or directories. See [Configuration](./configuration.md) for more details.

## SBOM scanning

SBOMs will be automatically identified so long as their name follows the specification for the particular format:
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ require (
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834
github.com/gkampitakis/go-snaps v0.5.19
github.com/go-git/go-git/v5 v5.16.4
github.com/gobwas/glob v0.2.3
github.com/goccy/go-yaml v1.19.2
github.com/google/go-cmp v0.7.0
github.com/google/osv-scalibr v0.4.3-0.20260119170449-c743cb685a10
Expand Down Expand Up @@ -115,7 +116,6 @@ require (
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-restruct/restruct v1.2.0-alpha // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/google/go-containerregistry v0.20.6 // indirect
Expand Down
23 changes: 22 additions & 1 deletion internal/cachedregexp/regex.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Package cachedregexp provides a cached version of regexp.MustCompile.
// Package cachedregexp provides a cached version of regexp.MustCompile and regexp.Compile.
package cachedregexp

import (
Expand All @@ -8,6 +8,9 @@ import (

var cache sync.Map

// Regexp is an alias for regexp.Regexp, allowing packages to use this type without importing regexp directly.
type Regexp = regexp.Regexp

func MustCompile(exp string) *regexp.Regexp {
compiled, ok := cache.Load(exp)
if !ok {
Expand All @@ -16,3 +19,21 @@ func MustCompile(exp string) *regexp.Regexp {

return compiled.(*regexp.Regexp)
}

// Compile returns a compiled regexp or an error if the pattern is invalid.
// Results are cached for performance.
func Compile(exp string) (*regexp.Regexp, error) {
compiled, ok := cache.Load(exp)
if ok {
return compiled.(*regexp.Regexp), nil
}

r, err := regexp.Compile(exp)
if err != nil {
return nil, err
}

cache.LoadOrStore(exp, r)

return r, nil
}
Loading
Loading