From 2343e03e9cfcf064202764e2759e937dc50d29b5 Mon Sep 17 00:00:00 2001 From: alessandro-Doyensec Date: Fri, 21 Nov 2025 12:33:55 +0100 Subject: [PATCH 01/14] add: specific filesystem converter --- .../filesystem/secrets/convert/specific.go | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 extractor/filesystem/secrets/convert/specific.go diff --git a/extractor/filesystem/secrets/convert/specific.go b/extractor/filesystem/secrets/convert/specific.go new file mode 100644 index 000000000..588bc0e81 --- /dev/null +++ b/extractor/filesystem/secrets/convert/specific.go @@ -0,0 +1,74 @@ +package convert + +import ( + "context" + "fmt" + + "github.com/google/osv-scalibr/extractor/filesystem" + "github.com/google/osv-scalibr/inventory" + "github.com/google/osv-scalibr/plugin" + "github.com/google/osv-scalibr/veles" +) + +type withRequire struct { + engine *veles.DetectionEngine + + detectors []veles.Detector + + name string + version int + fileRequired func(api filesystem.FileAPI) bool +} + +func (e *withRequire) Extract(ctx context.Context, input *filesystem.ScanInput) (inventory.Inventory, error) { + if e.engine == nil { + var err error + e.engine, err = veles.NewDetectionEngine(e.detectors) + if err != nil { + return inventory.Inventory{}, fmt.Errorf("error setting up the detection engine for %T: %w", e.detectors, err) + } + } + secrets, err := e.engine.Detect(ctx, input.Reader) + if err != nil { + return inventory.Inventory{}, fmt.Errorf("unable to scan for secrets: %w", err) + } + i := inventory.Inventory{} + for _, s := range secrets { + i.Secrets = append(i.Secrets, &inventory.Secret{ + Secret: s, + Location: input.Path, + }) + } + return i, nil +} + +// Name of the secret extractor. +func (e *withRequire) Name() string { + return e.name +} + +// Version of the secret extractor. +func (e *withRequire) Version() int { + return e.version +} + +// Requirements of the secret extractor. +func (e *withRequire) Requirements() *plugin.Capabilities { + // Veles plugins don't have any special requirements. + return &plugin.Capabilities{} +} + +// FileRequired returns true if the file is required by the extractor. +func (e *withRequire) FileRequired(api filesystem.FileAPI) bool { + return e.fileRequired(api) +} + +// FromVelesDetectorWithRequire returns a filesystem extractor from a veles detector. +func FromVelesDetectorWithRequire(ds []veles.Detector, name string, version int, fileRequired func(api filesystem.FileAPI) bool) filesystem.Extractor { + return &withRequire{ + detectors: ds, + name: name, + version: version, + fileRequired: fileRequired, + } +} From 5029446b29fdafed22c3ac9eae1a611d833597cc Mon Sep 17 00:00:00 2001 From: alessandro-Doyensec Date: Fri, 21 Nov 2025 12:34:09 +0100 Subject: [PATCH 02/14] add: aws credentials extractor --- .../secrets/awscredentials/awsacredentials.go | 39 +++++ .../awscredentials/awsacredentials_test.go | 133 ++++++++++++++++++ .../awscredentials/testdata/aws_credentials | 3 + .../secrets/awscredentials/testdata/empty | 0 .../awscredentials/testdata/gcs_credentials | 3 + .../awscredentials/testdata/random_content | 10 ++ 6 files changed, 188 insertions(+) create mode 100644 extractor/filesystem/secrets/awscredentials/awsacredentials.go create mode 100644 extractor/filesystem/secrets/awscredentials/awsacredentials_test.go create mode 100644 extractor/filesystem/secrets/awscredentials/testdata/aws_credentials create mode 100644 extractor/filesystem/secrets/awscredentials/testdata/empty create mode 100644 extractor/filesystem/secrets/awscredentials/testdata/gcs_credentials create mode 100644 extractor/filesystem/secrets/awscredentials/testdata/random_content diff --git a/extractor/filesystem/secrets/awscredentials/awsacredentials.go b/extractor/filesystem/secrets/awscredentials/awsacredentials.go new file mode 100644 index 000000000..4f65df3b5 --- /dev/null +++ b/extractor/filesystem/secrets/awscredentials/awsacredentials.go @@ -0,0 +1,39 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package awscredentials extracts credentials from the .aws/credentials file +package awscredentials + +import ( + "path/filepath" + "strings" + + "github.com/google/osv-scalibr/extractor/filesystem" + "github.com/google/osv-scalibr/extractor/filesystem/secrets/convert" + "github.com/google/osv-scalibr/veles" + "github.com/google/osv-scalibr/veles/secrets/awsaccesskey" + "github.com/google/osv-scalibr/veles/secrets/gcshmackey" +) + +func New() filesystem.Extractor { + return convert.FromVelesDetectorWithRequire( + []veles.Detector{awsaccesskey.NewDetector(), gcshmackey.NewDetector()}, + "secrets/awscredentials", + 0, + func(api filesystem.FileAPI) bool { + path := filepath.ToSlash(api.Path()) + return strings.HasSuffix(path, ".aws/credentials") + }, + ) +} diff --git a/extractor/filesystem/secrets/awscredentials/awsacredentials_test.go b/extractor/filesystem/secrets/awscredentials/awsacredentials_test.go new file mode 100644 index 000000000..d7cffc588 --- /dev/null +++ b/extractor/filesystem/secrets/awscredentials/awsacredentials_test.go @@ -0,0 +1,133 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package awscredentials_test + +import ( + "runtime" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/osv-scalibr/extractor/filesystem/secrets/awscredentials" + "github.com/google/osv-scalibr/extractor/filesystem/simplefileapi" + "github.com/google/osv-scalibr/inventory" + "github.com/google/osv-scalibr/testing/extracttest" + "github.com/google/osv-scalibr/veles/secrets/awsaccesskey" + "github.com/google/osv-scalibr/veles/secrets/gcshmackey" +) + +func TestExtractor_FileRequired(t *testing.T) { + tests := []struct { + inputPath string + want bool + isWindows bool + }{ + {inputPath: "", want: false}, + + // linux + {inputPath: `/Users/example-user/.aws/credentials`, want: true}, + {inputPath: `/Users/example-user/bad/path`, want: false}, + + // windows + {inputPath: `C:\Users\USERNAME\.aws\credentials`, isWindows: true, want: true}, + {inputPath: `C:\Users\USERNAME\another\bad\path`, isWindows: true, want: true}, + } + + for _, tt := range tests { + t.Run(tt.inputPath, func(t *testing.T) { + if tt.isWindows && runtime.GOOS != "windows" { + t.Skipf("Skipping test %q for %q", t.Name(), runtime.GOOS) + } + e := awscredentials.New() + got := e.FileRequired(simplefileapi.New(tt.inputPath, nil)) + if got != tt.want { + t.Errorf("FileRequired(%s) got = %v, want %v", tt.inputPath, got, tt.want) + } + }) + } +} + +func TestExtractor_Extract(t *testing.T) { + tests := []*struct { + Name string + Path string + WantSecrets []*inventory.Secret + WantErr error + }{ + { + Name: "empty", + Path: "empty", + WantSecrets: nil, + }, + { + Name: "aws_credentials", + Path: "aws_credentials", + WantSecrets: []*inventory.Secret{ + { + Secret: awsaccesskey.Credentials{ + AccessID: "AIKA1984R439T439HTH4", + Secret: "32r923jr023rk320rk2a3rkB34tj340r32Ckt433", + }, + Location: "aws_credentials", + }, + }, + }, + { + Name: "gcs_credentials", + Path: "gcs_credentials", + WantSecrets: []*inventory.Secret{ + { + Secret: gcshmackey.HMACKey{ + AccessID: "GOOG1984R439T439HTH439T403TJ430TK340TK43T430JT430TK430JT043JT", + Secret: "32r923jr023rk320rk2a3rkB34tj340r32Ckt433", + }, + Location: "gcs_credentials", + }, + }, + }, + { + Name: "random_content", + Path: "random_content", + WantSecrets: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + extr := awscredentials.New() + + inputCfg := extracttest.ScanInputMockConfig{ + Path: tt.Path, + FakeScanRoot: "testdata", + } + + scanInput := extracttest.GenerateScanInputMock(t, inputCfg) + defer extracttest.CloseTestScanInput(t, scanInput) + + got, err := extr.Extract(t.Context(), &scanInput) + + if diff := cmp.Diff(tt.WantErr, err, cmpopts.EquateErrors()); diff != "" { + t.Errorf("%s.Extract(%q) error diff (-want +got):\n%s", extr.Name(), tt.Path, diff) + return + } + + wantInv := inventory.Inventory{Secrets: tt.WantSecrets} + opts := []cmp.Option{cmpopts.SortSlices(extracttest.PackageCmpLess), cmpopts.EquateEmpty()} + if diff := cmp.Diff(wantInv, got, opts...); diff != "" { + t.Errorf("%s.Extract(%q) diff (-want +got):\n%s", extr.Name(), tt.Path, diff) + } + }) + } +} diff --git a/extractor/filesystem/secrets/awscredentials/testdata/aws_credentials b/extractor/filesystem/secrets/awscredentials/testdata/aws_credentials new file mode 100644 index 000000000..7ea6ade7a --- /dev/null +++ b/extractor/filesystem/secrets/awscredentials/testdata/aws_credentials @@ -0,0 +1,3 @@ +[default] +aws_access_key_id = AIKA1984R439T439HTH4 +aws_secret_access_key = 32r923jr023rk320rk2a3rkB34tj340r32Ckt433 diff --git a/extractor/filesystem/secrets/awscredentials/testdata/empty b/extractor/filesystem/secrets/awscredentials/testdata/empty new file mode 100644 index 000000000..e69de29bb diff --git a/extractor/filesystem/secrets/awscredentials/testdata/gcs_credentials b/extractor/filesystem/secrets/awscredentials/testdata/gcs_credentials new file mode 100644 index 000000000..c300ec21b --- /dev/null +++ b/extractor/filesystem/secrets/awscredentials/testdata/gcs_credentials @@ -0,0 +1,3 @@ +[default] +aws_access_key_id = GOOG1984R439T439HTH439T403TJ430TK340TK43T430JT430TK430JT043JT +aws_secret_access_key = 32r923jr023rk320rk2a3rkB34tj340r32Ckt433 diff --git a/extractor/filesystem/secrets/awscredentials/testdata/random_content b/extractor/filesystem/secrets/awscredentials/testdata/random_content new file mode 100644 index 000000000..e62a6bb7a --- /dev/null +++ b/extractor/filesystem/secrets/awscredentials/testdata/random_content @@ -0,0 +1,10 @@ +localhost:5432:mydb:myuser:mypassword +hostname:1234:testdb:testuser:testpass123 +hostname:1234:testdb:testuser:passw*ord +# space inside one group (except password) +hostname:1234:testdb:testuser:passw ord +hostname:1234:db name:testuser:password +# this is a comment and should be ignored +*:*:db:admin:supersecret +# valid with escaped : +prod.example.com:5432:db:admin:pass\:word From a21270b4fedc480440309b60490ee81c95b4bc47 Mon Sep 17 00:00:00 2001 From: alessandro-Doyensec Date: Fri, 21 Nov 2025 12:35:46 +0100 Subject: [PATCH 03/14] add: docs --- extractor/filesystem/secrets/awscredentials/awsacredentials.go | 1 + extractor/filesystem/secrets/convert/specific.go | 1 + 2 files changed, 2 insertions(+) diff --git a/extractor/filesystem/secrets/awscredentials/awsacredentials.go b/extractor/filesystem/secrets/awscredentials/awsacredentials.go index 4f65df3b5..5e3b7e0fc 100644 --- a/extractor/filesystem/secrets/awscredentials/awsacredentials.go +++ b/extractor/filesystem/secrets/awscredentials/awsacredentials.go @@ -26,6 +26,7 @@ import ( "github.com/google/osv-scalibr/veles/secrets/gcshmackey" ) +// New returns a new extractor which searches for credentials in the .aws/credentials file func New() filesystem.Extractor { return convert.FromVelesDetectorWithRequire( []veles.Detector{awsaccesskey.NewDetector(), gcshmackey.NewDetector()}, diff --git a/extractor/filesystem/secrets/convert/specific.go b/extractor/filesystem/secrets/convert/specific.go index 588bc0e81..d94809483 100644 --- a/extractor/filesystem/secrets/convert/specific.go +++ b/extractor/filesystem/secrets/convert/specific.go @@ -20,6 +20,7 @@ type withRequire struct { fileRequired func(api filesystem.FileAPI) bool } +// Extract extracts secrets from a file using the specified detectors. func (e *withRequire) Extract(ctx context.Context, input *filesystem.ScanInput) (inventory.Inventory, error) { if e.engine == nil { var err error From 1306cceaf7e7c4aba755788c3ab3afcee42029c1 Mon Sep 17 00:00:00 2001 From: alessandro-Doyensec Date: Fri, 21 Nov 2025 14:50:35 +0100 Subject: [PATCH 04/14] edit: register awscredential extractor --- docs/supported_inventory_types.md | 1 + extractor/filesystem/list/list.go | 2 ++ .../filesystem/secrets/awscredentials/awsacredentials.go | 7 ++++++- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/supported_inventory_types.md b/docs/supported_inventory_types.md index a5c0921dc..a877a25e5 100644 --- a/docs/supported_inventory_types.md +++ b/docs/supported_inventory_types.md @@ -132,6 +132,7 @@ See the docs on [how to add a new Extractor](/docs/new_extractor.md). | Hashicorp Vault AppRole token | `secrets/hashicorpvaultapprole` | | Hugging Face API key | `secrets/huggingfaceapikey` | | MariaDB Credentials | `secrets/mariadb` | +| AWS Credentials | `secrets/awscredentials` | | Mysql Mylogin | `secrets/mysqlmylogin` | | 1Password Secret Key | `secrets/onepasswordsecretkey` | | 1Password Service Token | `secrets/onepasswordservicetoken` | diff --git a/extractor/filesystem/list/list.go b/extractor/filesystem/list/list.go index cf7c76733..d3c4354b5 100644 --- a/extractor/filesystem/list/list.go +++ b/extractor/filesystem/list/list.go @@ -96,6 +96,7 @@ import ( "github.com/google/osv-scalibr/extractor/filesystem/runtime/nodejs/nvm" "github.com/google/osv-scalibr/extractor/filesystem/sbom/cdx" "github.com/google/osv-scalibr/extractor/filesystem/sbom/spdx" + "github.com/google/osv-scalibr/extractor/filesystem/secrets/awscredentials" "github.com/google/osv-scalibr/extractor/filesystem/secrets/convert" "github.com/google/osv-scalibr/extractor/filesystem/secrets/mariadb" "github.com/google/osv-scalibr/extractor/filesystem/secrets/mysqlmylogin" @@ -283,6 +284,7 @@ var ( pgpass.Name: {noCFG(pgpass.New)}, onepasswordconnecttoken.Name: {noCFG(onepasswordconnecttoken.New)}, mariadb.Name: {noCFG(mariadb.NewDefault)}, + awscredentials.Name: {noCFG(awscredentials.New)}, } // SecretDetectors for Detector interface. diff --git a/extractor/filesystem/secrets/awscredentials/awsacredentials.go b/extractor/filesystem/secrets/awscredentials/awsacredentials.go index 5e3b7e0fc..0f5adcdab 100644 --- a/extractor/filesystem/secrets/awscredentials/awsacredentials.go +++ b/extractor/filesystem/secrets/awscredentials/awsacredentials.go @@ -26,11 +26,16 @@ import ( "github.com/google/osv-scalibr/veles/secrets/gcshmackey" ) +const ( + // Name is the name of the extractor + Name = "secrets/awscredentials" +) + // New returns a new extractor which searches for credentials in the .aws/credentials file func New() filesystem.Extractor { return convert.FromVelesDetectorWithRequire( []veles.Detector{awsaccesskey.NewDetector(), gcshmackey.NewDetector()}, - "secrets/awscredentials", + Name, 0, func(api filesystem.FileAPI) bool { path := filepath.ToSlash(api.Path()) From 53d6e74e189e5aecb0a31cd430fedbd876a87baa Mon Sep 17 00:00:00 2001 From: alessandro-Doyensec Date: Thu, 27 Nov 2025 14:12:23 +0100 Subject: [PATCH 05/14] edit: extend the convert library to support extractors which implements both filesystem.Extractor and veles.Detector interfaces edit: remove awscredentials ad hoc extractor --- extractor/filesystem/list/list.go | 10 +- .../awsaccesskey.go} | 29 ++--- .../awsaccesskey_test.go} | 26 +---- .../testdata/aws_credentials | 0 .../testdata/empty | 0 .../testdata/random_content | 0 .../awscredentials/testdata/gcs_credentials | 3 - .../filesystem/secrets/convert/convert.go | 14 ++- .../filesystem/secrets/convert/specific.go | 104 +++++++++++------- 9 files changed, 101 insertions(+), 85 deletions(-) rename extractor/filesystem/secrets/{awscredentials/awsacredentials.go => awsaccesskey/awsaccesskey.go} (61%) rename extractor/filesystem/secrets/{awscredentials/awsacredentials_test.go => awsaccesskey/awsaccesskey_test.go} (84%) rename extractor/filesystem/secrets/{awscredentials => awsaccesskey}/testdata/aws_credentials (100%) rename extractor/filesystem/secrets/{awscredentials => awsaccesskey}/testdata/empty (100%) rename extractor/filesystem/secrets/{awscredentials => awsaccesskey}/testdata/random_content (100%) delete mode 100644 extractor/filesystem/secrets/awscredentials/testdata/gcs_credentials diff --git a/extractor/filesystem/list/list.go b/extractor/filesystem/list/list.go index d3c4354b5..f487880d6 100644 --- a/extractor/filesystem/list/list.go +++ b/extractor/filesystem/list/list.go @@ -96,7 +96,7 @@ import ( "github.com/google/osv-scalibr/extractor/filesystem/runtime/nodejs/nvm" "github.com/google/osv-scalibr/extractor/filesystem/sbom/cdx" "github.com/google/osv-scalibr/extractor/filesystem/sbom/spdx" - "github.com/google/osv-scalibr/extractor/filesystem/secrets/awscredentials" + "github.com/google/osv-scalibr/extractor/filesystem/secrets/awsaccesskey" "github.com/google/osv-scalibr/extractor/filesystem/secrets/convert" "github.com/google/osv-scalibr/extractor/filesystem/secrets/mariadb" "github.com/google/osv-scalibr/extractor/filesystem/secrets/mysqlmylogin" @@ -104,7 +104,6 @@ import ( "github.com/google/osv-scalibr/extractor/filesystem/secrets/pgpass" "github.com/google/osv-scalibr/veles" "github.com/google/osv-scalibr/veles/secrets/anthropicapikey" - "github.com/google/osv-scalibr/veles/secrets/awsaccesskey" "github.com/google/osv-scalibr/veles/secrets/azurestorageaccountaccesskey" "github.com/google/osv-scalibr/veles/secrets/azuretoken" "github.com/google/osv-scalibr/veles/secrets/cratesioapitoken" @@ -284,7 +283,7 @@ var ( pgpass.Name: {noCFG(pgpass.New)}, onepasswordconnecttoken.Name: {noCFG(onepasswordconnecttoken.New)}, mariadb.Name: {noCFG(mariadb.NewDefault)}, - awscredentials.Name: {noCFG(awscredentials.New)}, + awsaccesskey.Name: {noCFG(awsaccesskey.New)}, } // SecretDetectors for Detector interface. @@ -333,7 +332,6 @@ var ( {onepasswordkeys.NewRecoveryTokenDetector(), "secrets/onepasswordrecoverycode", 0}, {gcshmackey.NewDetector(), "secrets/gcshmackey", 0}, {vapid.NewDetector(), "secrets/vapidkey", 0}, - {awsaccesskey.NewDetector(), "secrets/awsaccesskey", 0}, {recaptchakey.NewDetector(), "secrets/recaptchakey", 0}, }) @@ -501,7 +499,9 @@ type velesPlugin struct { func initMapFromVelesPlugins(plugins []velesPlugin) InitMap { result := InitMap{} for _, p := range plugins { - result[p.name] = []InitFn{noCFG(convert.FromVelesDetector(p.detector, p.name, p.version))} + result[p.name] = []InitFn{noCFG(func() filesystem.Extractor { + return convert.FromVelesDetector(p.detector, p.name, p.version)() + })} } return result } diff --git a/extractor/filesystem/secrets/awscredentials/awsacredentials.go b/extractor/filesystem/secrets/awsaccesskey/awsaccesskey.go similarity index 61% rename from extractor/filesystem/secrets/awscredentials/awsacredentials.go rename to extractor/filesystem/secrets/awsaccesskey/awsaccesskey.go index 0f5adcdab..304c8f38a 100644 --- a/extractor/filesystem/secrets/awscredentials/awsacredentials.go +++ b/extractor/filesystem/secrets/awsaccesskey/awsaccesskey.go @@ -12,34 +12,35 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Package awscredentials extracts credentials from the .aws/credentials file -package awscredentials +// Package awsaccesskey extends the veles awsaccesskey.Detector to search inside the `~/.aws/credentials` file +package awsaccesskey import ( "path/filepath" "strings" "github.com/google/osv-scalibr/extractor/filesystem" - "github.com/google/osv-scalibr/extractor/filesystem/secrets/convert" - "github.com/google/osv-scalibr/veles" "github.com/google/osv-scalibr/veles/secrets/awsaccesskey" - "github.com/google/osv-scalibr/veles/secrets/gcshmackey" + + "github.com/google/osv-scalibr/extractor/filesystem/secrets/convert" ) const ( // Name is the name of the extractor - Name = "secrets/awscredentials" + Name = "secrets/awsaccesskey" + // Version is the version of the extractor + Version = 0 ) -// New returns a new extractor which searches for credentials in the .aws/credentials file +// New returns a filesystem.Extractor which extracts AWS Access Keys using the awsaccesskey.Detector func New() filesystem.Extractor { return convert.FromVelesDetectorWithRequire( - []veles.Detector{awsaccesskey.NewDetector(), gcshmackey.NewDetector()}, - Name, - 0, - func(api filesystem.FileAPI) bool { - path := filepath.ToSlash(api.Path()) - return strings.HasSuffix(path, ".aws/credentials") - }, + awsaccesskey.NewDetector(), Name, Version, FileRequired, ) } + +// FileRequired returns true if a file contains aws credentials. +func FileRequired(api filesystem.FileAPI) bool { + path := filepath.ToSlash(api.Path()) + return strings.HasSuffix(path, ".aws/credentials") +} diff --git a/extractor/filesystem/secrets/awscredentials/awsacredentials_test.go b/extractor/filesystem/secrets/awsaccesskey/awsaccesskey_test.go similarity index 84% rename from extractor/filesystem/secrets/awscredentials/awsacredentials_test.go rename to extractor/filesystem/secrets/awsaccesskey/awsaccesskey_test.go index d7cffc588..3c8f22e07 100644 --- a/extractor/filesystem/secrets/awscredentials/awsacredentials_test.go +++ b/extractor/filesystem/secrets/awsaccesskey/awsaccesskey_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package awscredentials_test +package awsaccesskey_test import ( "runtime" @@ -20,12 +20,11 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - "github.com/google/osv-scalibr/extractor/filesystem/secrets/awscredentials" + "github.com/google/osv-scalibr/extractor/filesystem/secrets/awsaccesskey" "github.com/google/osv-scalibr/extractor/filesystem/simplefileapi" "github.com/google/osv-scalibr/inventory" "github.com/google/osv-scalibr/testing/extracttest" - "github.com/google/osv-scalibr/veles/secrets/awsaccesskey" - "github.com/google/osv-scalibr/veles/secrets/gcshmackey" + awsaccesskeydetector "github.com/google/osv-scalibr/veles/secrets/awsaccesskey" ) func TestExtractor_FileRequired(t *testing.T) { @@ -50,7 +49,7 @@ func TestExtractor_FileRequired(t *testing.T) { if tt.isWindows && runtime.GOOS != "windows" { t.Skipf("Skipping test %q for %q", t.Name(), runtime.GOOS) } - e := awscredentials.New() + e := awsaccesskey.New() got := e.FileRequired(simplefileapi.New(tt.inputPath, nil)) if got != tt.want { t.Errorf("FileRequired(%s) got = %v, want %v", tt.inputPath, got, tt.want) @@ -76,7 +75,7 @@ func TestExtractor_Extract(t *testing.T) { Path: "aws_credentials", WantSecrets: []*inventory.Secret{ { - Secret: awsaccesskey.Credentials{ + Secret: awsaccesskeydetector.Credentials{ AccessID: "AIKA1984R439T439HTH4", Secret: "32r923jr023rk320rk2a3rkB34tj340r32Ckt433", }, @@ -84,19 +83,6 @@ func TestExtractor_Extract(t *testing.T) { }, }, }, - { - Name: "gcs_credentials", - Path: "gcs_credentials", - WantSecrets: []*inventory.Secret{ - { - Secret: gcshmackey.HMACKey{ - AccessID: "GOOG1984R439T439HTH439T403TJ430TK340TK43T430JT430TK430JT043JT", - Secret: "32r923jr023rk320rk2a3rkB34tj340r32Ckt433", - }, - Location: "gcs_credentials", - }, - }, - }, { Name: "random_content", Path: "random_content", @@ -106,7 +92,7 @@ func TestExtractor_Extract(t *testing.T) { for _, tt := range tests { t.Run(tt.Name, func(t *testing.T) { - extr := awscredentials.New() + extr := awsaccesskey.New() inputCfg := extracttest.ScanInputMockConfig{ Path: tt.Path, diff --git a/extractor/filesystem/secrets/awscredentials/testdata/aws_credentials b/extractor/filesystem/secrets/awsaccesskey/testdata/aws_credentials similarity index 100% rename from extractor/filesystem/secrets/awscredentials/testdata/aws_credentials rename to extractor/filesystem/secrets/awsaccesskey/testdata/aws_credentials diff --git a/extractor/filesystem/secrets/awscredentials/testdata/empty b/extractor/filesystem/secrets/awsaccesskey/testdata/empty similarity index 100% rename from extractor/filesystem/secrets/awscredentials/testdata/empty rename to extractor/filesystem/secrets/awsaccesskey/testdata/empty diff --git a/extractor/filesystem/secrets/awscredentials/testdata/random_content b/extractor/filesystem/secrets/awsaccesskey/testdata/random_content similarity index 100% rename from extractor/filesystem/secrets/awscredentials/testdata/random_content rename to extractor/filesystem/secrets/awsaccesskey/testdata/random_content diff --git a/extractor/filesystem/secrets/awscredentials/testdata/gcs_credentials b/extractor/filesystem/secrets/awscredentials/testdata/gcs_credentials deleted file mode 100644 index c300ec21b..000000000 --- a/extractor/filesystem/secrets/awscredentials/testdata/gcs_credentials +++ /dev/null @@ -1,3 +0,0 @@ -[default] -aws_access_key_id = GOOG1984R439T439HTH439T403TJ430TK340TK43T430JT430TK430JT043JT -aws_secret_access_key = 32r923jr023rk320rk2a3rkB34tj340r32Ckt433 diff --git a/extractor/filesystem/secrets/convert/convert.go b/extractor/filesystem/secrets/convert/convert.go index 3572b1610..13d12f073 100644 --- a/extractor/filesystem/secrets/convert/convert.go +++ b/extractor/filesystem/secrets/convert/convert.go @@ -37,6 +37,10 @@ func FromVelesDetector(velesDetector veles.Detector, name string, version int) f } } +type placeholder interface { + IsPlaceholder() bool +} + // detectorWrapper is a wrapper around the veles.Detector interface that // implements the additional functions of the filesystem Extractor interface. type detectorWrapper struct { @@ -45,6 +49,8 @@ type detectorWrapper struct { version int } +func (d *detectorWrapper) IsPlaceholder() bool { return true } + // MaxSecretLen returns the maximum length a secret from this Detector can have. func (d *detectorWrapper) MaxSecretLen() uint32 { return d.velesDetector.MaxSecretLen() @@ -95,9 +101,15 @@ var _ filesystem.Extractor = &detectorWrapper{} func SetupVelesExtractors(extractors []filesystem.Extractor) ([]filesystem.Extractor, error) { result := make([]filesystem.Extractor, 0, len(extractors)) detectors := []veles.Detector{} + for _, e := range extractors { - if d, ok := e.(veles.Detector); ok { + if d, isDetector := e.(veles.Detector); isDetector { detectors = append(detectors, d) + // if the detector is an actual filesystem.Extractor + // add it to the result list + if _, isPlaceholder := e.(placeholder); !isPlaceholder { + result = append(result, e) + } } else { result = append(result, e) } diff --git a/extractor/filesystem/secrets/convert/specific.go b/extractor/filesystem/secrets/convert/specific.go index d94809483..ac200c673 100644 --- a/extractor/filesystem/secrets/convert/specific.go +++ b/extractor/filesystem/secrets/convert/specific.go @@ -10,66 +10,86 @@ import ( "github.com/google/osv-scalibr/veles" ) -type withRequire struct { - engine *veles.DetectionEngine +// FromVelesDetectorWithRequire converts a Veles Detector into a SCALIBR FilesystemExtractor plugin. +// This allows: +// - Enabling Veles Detectors individually like regular SCALIBR plugins. +// - Using the provided detector in the detection engine with other detectors. +// - Using the detector as a standalone filesystem extractor. +func FromVelesDetectorWithRequire(velesDetector veles.Detector, name string, version int, fileRequired func(filesystem.FileAPI) bool) filesystem.Extractor { + return &withRequire{ + velesDetector: velesDetector, + name: name, + version: version, + fileRequired: fileRequired, + } +} - detectors []veles.Detector +// withRequire is a wrapper around the veles.Detector interface that +// implements the additional functions of the filesystem Extractor interface. +type withRequire struct { + velesDetector veles.Detector + name string + version int + fileRequired func(filesystem.FileAPI) bool + e *veles.DetectionEngine +} - name string - version int - fileRequired func(api filesystem.FileAPI) bool +// MaxSecretLen returns the maximum length a secret from this Detector can have. +func (w *withRequire) MaxSecretLen() uint32 { + return w.velesDetector.MaxSecretLen() } -// Extract extracts secrets from a file using the specified detectors. -func (e *withRequire) Extract(ctx context.Context, input *filesystem.ScanInput) (inventory.Inventory, error) { - if e.engine == nil { - var err error - e.engine, err = veles.NewDetectionEngine(e.detectors) - if err != nil { - return inventory.Inventory{}, fmt.Errorf("error setting up the detection engine for %T: %w", e.detectors, err) - } - } - secrets, err := e.engine.Detect(ctx, input.Reader) - if err != nil { - return inventory.Inventory{}, fmt.Errorf("unable to scan for secrets: %w", err) - } - i := inventory.Inventory{} - for _, s := range secrets { - i.Secrets = append(i.Secrets, &inventory.Secret{ - Secret: s, - Location: input.Path, - }) - } - return i, nil +// Detect finds candidate secrets in the data and returns them alongside their +// starting positions. +func (w *withRequire) Detect(data []byte) ([]veles.Secret, []int) { + return w.velesDetector.Detect(data) } // Name of the secret extractor. -func (e *withRequire) Name() string { - return e.name +func (w *withRequire) Name() string { + return w.name } // Version of the secret extractor. -func (e *withRequire) Version() int { - return e.version +func (w *withRequire) Version() int { + return w.version } // Requirements of the secret extractor. -func (e *withRequire) Requirements() *plugin.Capabilities { +func (w *withRequire) Requirements() *plugin.Capabilities { // Veles plugins don't have any special requirements. return &plugin.Capabilities{} } -// FileRequired returns true if the file is required by the extractor. -func (e *withRequire) FileRequired(api filesystem.FileAPI) bool { - return e.fileRequired(api) +// FileRequired returns the provided file required callback. +func (w *withRequire) FileRequired(api filesystem.FileAPI) bool { + return w.fileRequired(api) } -// FromVelesDetectorWithRequire returns a filesystem extractor from a veles detector. -func FromVelesDetectorWithRequire(ds []veles.Detector, name string, version int, fileRequired func(api filesystem.FileAPI) bool) filesystem.Extractor { - return &withRequire{ - detectors: ds, - name: name, - version: version, - fileRequired: fileRequired, +// IsRequirer implements the requirer interface. +func (w *withRequire) IsRequirer() bool { + return true +} + +// Extract extracts secret from the filesystem using the provided detector. +func (w *withRequire) Extract(ctx context.Context, input *filesystem.ScanInput) (inventory.Inventory, error) { + if w.e == nil { + var err error + w.e, err = veles.NewDetectionEngine([]veles.Detector{w.velesDetector}) + if err != nil { + return inventory.Inventory{}, err + } } + secrets, err := w.e.Detect(ctx, input.Reader) + if err != nil { + return inventory.Inventory{}, fmt.Errorf("unable to scan for secrets: %w", err) + } + i := inventory.Inventory{} + for _, s := range secrets { + i.Secrets = append(i.Secrets, &inventory.Secret{ + Secret: s, + Location: input.Path, + }) + } + return i, nil } From 770b4bfaf95616d2824379060116dff116fd62ea Mon Sep 17 00:00:00 2001 From: alessandro-Doyensec Date: Thu, 27 Nov 2025 14:12:36 +0100 Subject: [PATCH 06/14] edit: update docs/supported_inventory_types.md --- docs/supported_inventory_types.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/supported_inventory_types.md b/docs/supported_inventory_types.md index a877a25e5..a5c0921dc 100644 --- a/docs/supported_inventory_types.md +++ b/docs/supported_inventory_types.md @@ -132,7 +132,6 @@ See the docs on [how to add a new Extractor](/docs/new_extractor.md). | Hashicorp Vault AppRole token | `secrets/hashicorpvaultapprole` | | Hugging Face API key | `secrets/huggingfaceapikey` | | MariaDB Credentials | `secrets/mariadb` | -| AWS Credentials | `secrets/awscredentials` | | Mysql Mylogin | `secrets/mysqlmylogin` | | 1Password Secret Key | `secrets/onepasswordsecretkey` | | 1Password Service Token | `secrets/onepasswordservicetoken` | From 59e5bb09b9be2d7222aa313337705d49b26bdfa4 Mon Sep 17 00:00:00 2001 From: alessandro-Doyensec Date: Thu, 27 Nov 2025 14:14:20 +0100 Subject: [PATCH 07/14] edit: restore initMapFromVelesPlugins --- extractor/filesystem/list/list.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/extractor/filesystem/list/list.go b/extractor/filesystem/list/list.go index f487880d6..451e6ea4b 100644 --- a/extractor/filesystem/list/list.go +++ b/extractor/filesystem/list/list.go @@ -499,9 +499,7 @@ type velesPlugin struct { func initMapFromVelesPlugins(plugins []velesPlugin) InitMap { result := InitMap{} for _, p := range plugins { - result[p.name] = []InitFn{noCFG(func() filesystem.Extractor { - return convert.FromVelesDetector(p.detector, p.name, p.version)() - })} + result[p.name] = []InitFn{noCFG(convert.FromVelesDetector(p.detector, p.name, p.version))} } return result } From bb8c33cc67433ecfc28c62bbcaafb4f9baa685da Mon Sep 17 00:00:00 2001 From: alessandro-Doyensec Date: Thu, 27 Nov 2025 14:17:45 +0100 Subject: [PATCH 08/14] refactor: better placeholder naming --- extractor/filesystem/secrets/convert/convert.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/extractor/filesystem/secrets/convert/convert.go b/extractor/filesystem/secrets/convert/convert.go index 13d12f073..9fd6d5237 100644 --- a/extractor/filesystem/secrets/convert/convert.go +++ b/extractor/filesystem/secrets/convert/convert.go @@ -37,7 +37,7 @@ func FromVelesDetector(velesDetector veles.Detector, name string, version int) f } } -type placeholder interface { +type extractorPlaceHolder interface { IsPlaceholder() bool } @@ -105,9 +105,7 @@ func SetupVelesExtractors(extractors []filesystem.Extractor) ([]filesystem.Extra for _, e := range extractors { if d, isDetector := e.(veles.Detector); isDetector { detectors = append(detectors, d) - // if the detector is an actual filesystem.Extractor - // add it to the result list - if _, isPlaceholder := e.(placeholder); !isPlaceholder { + if _, isExtractorPlaceHolder := e.(extractorPlaceHolder); !isExtractorPlaceHolder { result = append(result, e) } } else { From e13727f50b999d1a1fe037f27f38f0b1f253bec4 Mon Sep 17 00:00:00 2001 From: alessandro-Doyensec Date: Thu, 27 Nov 2025 14:25:00 +0100 Subject: [PATCH 09/14] fix: remove unused function --- extractor/filesystem/secrets/convert/specific.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/extractor/filesystem/secrets/convert/specific.go b/extractor/filesystem/secrets/convert/specific.go index ac200c673..5d59a1ce2 100644 --- a/extractor/filesystem/secrets/convert/specific.go +++ b/extractor/filesystem/secrets/convert/specific.go @@ -66,11 +66,6 @@ func (w *withRequire) FileRequired(api filesystem.FileAPI) bool { return w.fileRequired(api) } -// IsRequirer implements the requirer interface. -func (w *withRequire) IsRequirer() bool { - return true -} - // Extract extracts secret from the filesystem using the provided detector. func (w *withRequire) Extract(ctx context.Context, input *filesystem.ScanInput) (inventory.Inventory, error) { if w.e == nil { From 0567d569e5795ad9222f78887e16c55fdc98fe0d Mon Sep 17 00:00:00 2001 From: alessandro-Doyensec Date: Thu, 27 Nov 2025 14:32:07 +0100 Subject: [PATCH 10/14] add: type assertion for detectorWithRequire --- .../filesystem/secrets/convert/specific.go | 40 ++++++++++--------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/extractor/filesystem/secrets/convert/specific.go b/extractor/filesystem/secrets/convert/specific.go index 5d59a1ce2..7f006394d 100644 --- a/extractor/filesystem/secrets/convert/specific.go +++ b/extractor/filesystem/secrets/convert/specific.go @@ -16,7 +16,7 @@ import ( // - Using the provided detector in the detection engine with other detectors. // - Using the detector as a standalone filesystem extractor. func FromVelesDetectorWithRequire(velesDetector veles.Detector, name string, version int, fileRequired func(filesystem.FileAPI) bool) filesystem.Extractor { - return &withRequire{ + return &detectorWithRequire{ velesDetector: velesDetector, name: name, version: version, @@ -24,9 +24,13 @@ func FromVelesDetectorWithRequire(velesDetector veles.Detector, name string, ver } } -// withRequire is a wrapper around the veles.Detector interface that +// Assert that detectorWithRequire implements the required interfaces. +var _ veles.Detector = &detectorWithRequire{} +var _ filesystem.Extractor = &detectorWithRequire{} + +// detectorWithRequire is a wrapper around the veles.Detector interface that // implements the additional functions of the filesystem Extractor interface. -type withRequire struct { +type detectorWithRequire struct { velesDetector veles.Detector name string version int @@ -35,47 +39,47 @@ type withRequire struct { } // MaxSecretLen returns the maximum length a secret from this Detector can have. -func (w *withRequire) MaxSecretLen() uint32 { - return w.velesDetector.MaxSecretLen() +func (d *detectorWithRequire) MaxSecretLen() uint32 { + return d.velesDetector.MaxSecretLen() } // Detect finds candidate secrets in the data and returns them alongside their // starting positions. -func (w *withRequire) Detect(data []byte) ([]veles.Secret, []int) { - return w.velesDetector.Detect(data) +func (d *detectorWithRequire) Detect(data []byte) ([]veles.Secret, []int) { + return d.velesDetector.Detect(data) } // Name of the secret extractor. -func (w *withRequire) Name() string { - return w.name +func (d *detectorWithRequire) Name() string { + return d.name } // Version of the secret extractor. -func (w *withRequire) Version() int { - return w.version +func (d *detectorWithRequire) Version() int { + return d.version } // Requirements of the secret extractor. -func (w *withRequire) Requirements() *plugin.Capabilities { +func (d *detectorWithRequire) Requirements() *plugin.Capabilities { // Veles plugins don't have any special requirements. return &plugin.Capabilities{} } // FileRequired returns the provided file required callback. -func (w *withRequire) FileRequired(api filesystem.FileAPI) bool { - return w.fileRequired(api) +func (d *detectorWithRequire) FileRequired(api filesystem.FileAPI) bool { + return d.fileRequired(api) } // Extract extracts secret from the filesystem using the provided detector. -func (w *withRequire) Extract(ctx context.Context, input *filesystem.ScanInput) (inventory.Inventory, error) { - if w.e == nil { +func (d *detectorWithRequire) Extract(ctx context.Context, input *filesystem.ScanInput) (inventory.Inventory, error) { + if d.e == nil { var err error - w.e, err = veles.NewDetectionEngine([]veles.Detector{w.velesDetector}) + d.e, err = veles.NewDetectionEngine([]veles.Detector{d.velesDetector}) if err != nil { return inventory.Inventory{}, err } } - secrets, err := w.e.Detect(ctx, input.Reader) + secrets, err := d.e.Detect(ctx, input.Reader) if err != nil { return inventory.Inventory{}, fmt.Errorf("unable to scan for secrets: %w", err) } From 50e75e6026f2670be3ede3980b47932d68650cdf Mon Sep 17 00:00:00 2001 From: alessandro-Doyensec Date: Fri, 28 Nov 2025 18:34:23 +0100 Subject: [PATCH 11/14] edit: handle extractor keepers add: test --- .../filesystem/secrets/convert/convert.go | 8 +---- .../filesystem/secrets/convert/specific.go | 13 +++++--- scalibr_test.go | 31 +++++++++++++++++++ 3 files changed, 40 insertions(+), 12 deletions(-) diff --git a/extractor/filesystem/secrets/convert/convert.go b/extractor/filesystem/secrets/convert/convert.go index 9fd6d5237..e5f90978a 100644 --- a/extractor/filesystem/secrets/convert/convert.go +++ b/extractor/filesystem/secrets/convert/convert.go @@ -37,10 +37,6 @@ func FromVelesDetector(velesDetector veles.Detector, name string, version int) f } } -type extractorPlaceHolder interface { - IsPlaceholder() bool -} - // detectorWrapper is a wrapper around the veles.Detector interface that // implements the additional functions of the filesystem Extractor interface. type detectorWrapper struct { @@ -49,8 +45,6 @@ type detectorWrapper struct { version int } -func (d *detectorWrapper) IsPlaceholder() bool { return true } - // MaxSecretLen returns the maximum length a secret from this Detector can have. func (d *detectorWrapper) MaxSecretLen() uint32 { return d.velesDetector.MaxSecretLen() @@ -105,7 +99,7 @@ func SetupVelesExtractors(extractors []filesystem.Extractor) ([]filesystem.Extra for _, e := range extractors { if d, isDetector := e.(veles.Detector); isDetector { detectors = append(detectors, d) - if _, isExtractorPlaceHolder := e.(extractorPlaceHolder); !isExtractorPlaceHolder { + if _, keepExtractor := e.(extractorKeeper); keepExtractor { result = append(result, e) } } else { diff --git a/extractor/filesystem/secrets/convert/specific.go b/extractor/filesystem/secrets/convert/specific.go index 7f006394d..415149e79 100644 --- a/extractor/filesystem/secrets/convert/specific.go +++ b/extractor/filesystem/secrets/convert/specific.go @@ -10,11 +10,7 @@ import ( "github.com/google/osv-scalibr/veles" ) -// FromVelesDetectorWithRequire converts a Veles Detector into a SCALIBR FilesystemExtractor plugin. -// This allows: -// - Enabling Veles Detectors individually like regular SCALIBR plugins. -// - Using the provided detector in the detection engine with other detectors. -// - Using the detector as a standalone filesystem extractor. +// FromVelesDetectorWithRequire works similar to FromVelesDetector but allows specifying additional files to look at on top of the default ones. func FromVelesDetectorWithRequire(velesDetector veles.Detector, name string, version int, fileRequired func(filesystem.FileAPI) bool) filesystem.Extractor { return &detectorWithRequire{ velesDetector: velesDetector, @@ -24,9 +20,14 @@ func FromVelesDetectorWithRequire(velesDetector veles.Detector, name string, ver } } +type extractorKeeper interface { + KeepExtractor() bool +} + // Assert that detectorWithRequire implements the required interfaces. var _ veles.Detector = &detectorWithRequire{} var _ filesystem.Extractor = &detectorWithRequire{} +var _ extractorKeeper = &detectorWithRequire{} // detectorWithRequire is a wrapper around the veles.Detector interface that // implements the additional functions of the filesystem Extractor interface. @@ -38,6 +39,8 @@ type detectorWithRequire struct { e *veles.DetectionEngine } +func (d *detectorWithRequire) KeepExtractor() bool { return true } + // MaxSecretLen returns the maximum length a secret from this Detector can have. func (d *detectorWithRequire) MaxSecretLen() uint32 { return d.velesDetector.MaxSecretLen() diff --git a/scalibr_test.go b/scalibr_test.go index 29d5352e3..ae8288bcb 100644 --- a/scalibr_test.go +++ b/scalibr_test.go @@ -21,6 +21,7 @@ import ( "io/fs" "os" "path/filepath" + "strings" "testing" "github.com/google/go-cmp/cmp" @@ -78,6 +79,7 @@ func TestScan(t *testing.T) { fs := scalibrfs.DirFS(tmp) tmpRoot := []*scalibrfs.ScanRoot{{FS: fs, Path: tmp}} _ = os.WriteFile(filepath.Join(tmp, "file.txt"), []byte("Content"), 0644) + _ = os.WriteFile(filepath.Join(tmp, "config"), []byte("Content"), 0644) pkgName := "software" fakeExtractor := fe.New( @@ -366,6 +368,35 @@ func TestScan(t *testing.T) { }, }, }, + { + desc: "Veles_secret_detector_with_extractor", + cfg: &scalibr.ScanConfig{ + Plugins: []plugin.Plugin{ + // use the fakeSecretDetector1 also on config files + cf.FromVelesDetectorWithRequire( + fakeSecretDetector1, "secret-detector", 1, + func(fa filesystem.FileAPI) bool { + return strings.HasSuffix(fa.Path(), "config") + }, + ), + }, + ScanRoots: tmpRoot, + }, + want: &scalibr.ScanResult{ + Version: version.ScannerVersion, + Status: success, + PluginStatus: []*plugin.Status{ + {Name: "secret-detector", Version: 1, Status: success}, + {Name: "secrets/veles", Version: 1, Status: success}, + }, + Inventory: inventory.Inventory{ + Secrets: []*inventory.Secret{ + {Secret: velestest.NewFakeStringSecret("Con"), Location: "file.txt"}, + {Secret: velestest.NewFakeStringSecret("Con"), Location: "config"}, + }, + }, + }, + }, } for _, tc := range testCases { From 04f380aa4a9955a8b677e6b44f85842f45be90b9 Mon Sep 17 00:00:00 2001 From: alessandro-Doyensec Date: Fri, 28 Nov 2025 18:35:03 +0100 Subject: [PATCH 12/14] fix: test want --- extractor/filesystem/secrets/awsaccesskey/awsaccesskey_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extractor/filesystem/secrets/awsaccesskey/awsaccesskey_test.go b/extractor/filesystem/secrets/awsaccesskey/awsaccesskey_test.go index 3c8f22e07..e28199099 100644 --- a/extractor/filesystem/secrets/awsaccesskey/awsaccesskey_test.go +++ b/extractor/filesystem/secrets/awsaccesskey/awsaccesskey_test.go @@ -41,7 +41,7 @@ func TestExtractor_FileRequired(t *testing.T) { // windows {inputPath: `C:\Users\USERNAME\.aws\credentials`, isWindows: true, want: true}, - {inputPath: `C:\Users\USERNAME\another\bad\path`, isWindows: true, want: true}, + {inputPath: `C:\Users\USERNAME\another\bad\path`, isWindows: true, want: false}, } for _, tt := range tests { From a664e2599810f6f8a20ff131166dba712173766c Mon Sep 17 00:00:00 2001 From: alessandro-Doyensec Date: Fri, 28 Nov 2025 18:49:25 +0100 Subject: [PATCH 13/14] add: docs to extractorKeeper --- extractor/filesystem/secrets/convert/specific.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/extractor/filesystem/secrets/convert/specific.go b/extractor/filesystem/secrets/convert/specific.go index 415149e79..e1c179958 100644 --- a/extractor/filesystem/secrets/convert/specific.go +++ b/extractor/filesystem/secrets/convert/specific.go @@ -20,6 +20,7 @@ func FromVelesDetectorWithRequire(velesDetector veles.Detector, name string, ver } } +// extractorKeeper signals that a Detector also functions as a standalone filesystem.Extractor. type extractorKeeper interface { KeepExtractor() bool } @@ -39,6 +40,8 @@ type detectorWithRequire struct { e *veles.DetectionEngine } +// KeepExtractor signals that this detector must also be registered as a standalone +// filesystem.Extractor to handle the additional files specified in the fileRequired callback. func (d *detectorWithRequire) KeepExtractor() bool { return true } // MaxSecretLen returns the maximum length a secret from this Detector can have. From 462f138550d0fd3e5fadc8b153b44a1dc20bc9d4 Mon Sep 17 00:00:00 2001 From: alessandro-Doyensec Date: Mon, 1 Dec 2025 17:13:24 +0100 Subject: [PATCH 14/14] add: cmpopts.SortSlices to cmp.Diff in TestScan --- scalibr_test.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/scalibr_test.go b/scalibr_test.go index ae8288bcb..2a9893296 100644 --- a/scalibr_test.go +++ b/scalibr_test.go @@ -407,10 +407,15 @@ func TestScan(t *testing.T) { tc.want.StartTime = got.StartTime tc.want.EndTime = got.EndTime - // Ignore timestamps. - ignoreFields := cmpopts.IgnoreFields(inventory.SecretValidationResult{}, "At") + opts := []cmp.Option{ + fe.AllowUnexported, + // Ignore timestamps. + cmpopts.IgnoreFields(inventory.SecretValidationResult{}, "At"), + // Sort secrets. + cmpopts.SortSlices(func(a, b *inventory.Secret) bool { return a.Location < b.Location }), + } - if diff := cmp.Diff(tc.want, got, fe.AllowUnexported, ignoreFields); diff != "" { + if diff := cmp.Diff(tc.want, got, opts...); diff != "" { t.Errorf("scalibr.New().Scan(%v): unexpected diff (-want +got):\n%s", tc.cfg, diff) } })