Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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 extractor/filesystem/list/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,10 @@ import (
"github.com/google/osv-scalibr/veles/secrets/azurestorageaccountaccesskey"
"github.com/google/osv-scalibr/veles/secrets/azuretoken"
"github.com/google/osv-scalibr/veles/secrets/circleci"
"github.com/google/osv-scalibr/veles/secrets/clojars"
"github.com/google/osv-scalibr/veles/secrets/cratesioapitoken"
"github.com/google/osv-scalibr/veles/secrets/cursorapikey"
"github.com/google/osv-scalibr/veles/secrets/databrickspat"
"github.com/google/osv-scalibr/veles/secrets/denopat"
"github.com/google/osv-scalibr/veles/secrets/digitaloceanapikey"
"github.com/google/osv-scalibr/veles/secrets/discordbottoken"
Expand Down Expand Up @@ -372,7 +374,9 @@ var (
{azurestorageaccountaccesskey.NewDetector(), "secrets/azurestorageaccountaccesskey", 0},
{circleci.NewPersonalAccessTokenDetector(), "secrets/circlecipat", 0},
{circleci.NewProjectTokenDetector(), "secrets/circleciproject", 0},
{clojars.NewDetector(), "secrets/clojars", 0},
{cursorapikey.NewDetector(), "secrets/cursorapikey", 0},
{databrickspat.NewDetector(), "secrets/databrickspat", 0},
{digitaloceanapikey.NewDetector(), "secrets/digitaloceanapikey", 0},
{pypiapitoken.NewDetector(), "secrets/pypiapitoken", 0},
{cratesioapitoken.NewDetector(), "secrets/cratesioapitoken", 0},
Expand Down
21 changes: 21 additions & 0 deletions veles/secrets/clojars/clojars.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright 2026 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 clojars

// DeployToken is a Veles Secret that holds relevant information for a
// Clojars Deploy Token (prefix `CLOJARS_`).
type DeployToken struct {
Token string
}
44 changes: 44 additions & 0 deletions veles/secrets/clojars/detector.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright 2026 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 clojars contains a Veles Secret type and Detector for Clojars Deploy
// Tokens (prefix `CLOJARS_`).
package clojars

import (
"regexp"

"github.com/google/osv-scalibr/veles"
"github.com/google/osv-scalibr/veles/secrets/common/simpletoken"
)

// maxTokenLength is the maximum size of a Clojars Deploy Token.
const maxTokenLength = 68

// deployTokenRe is a regular expression that matches a Clojars Deploy Token.
// Deploy tokens have the form: `CLOJARS_` followed by exactly 60 hexadecimal
// characters.
var deployTokenRe = regexp.MustCompile(`\bCLOJARS_[A-Fa-f0-9]{60}\b`)

// NewDetector returns a new simpletoken.Detector that matches Clojars Deploy
// Tokens.
func NewDetector() veles.Detector {
return simpletoken.Detector{
MaxLen: maxTokenLength,
Re: deployTokenRe,
FromMatch: func(b []byte) (veles.Secret, bool) {
return DeployToken{Token: string(b)}, true
},
}
}
137 changes: 137 additions & 0 deletions veles/secrets/clojars/detector_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// Copyright 2026 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 clojars_test

import (
"fmt"
"strings"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/google/osv-scalibr/veles"
"github.com/google/osv-scalibr/veles/secrets/clojars"
"github.com/google/osv-scalibr/veles/velestest"
)

const testDeployToken = `CLOJARS_0123456789abcdefABCDEF0123456789abcdefABCDEF0123456789abcdef`

func TestDetectorAcceptance(t *testing.T) {
velestest.AcceptDetector(
t,
clojars.NewDetector(),
testDeployToken,
clojars.DeployToken{Token: testDeployToken},
)
}

func TestDetector_truePositives(t *testing.T) {
engine, err := veles.NewDetectionEngine([]veles.Detector{clojars.NewDetector()})
if err != nil {
t.Fatal(err)
}
otherToken := testDeployToken[:len(testDeployToken)-1] + "0"
cases := []struct {
name string
input string
want []veles.Secret
}{{
name: "simple matching string",
input: testDeployToken,
want: []veles.Secret{
clojars.DeployToken{Token: testDeployToken},
},
}, {
name: "match at end of env assignment",
input: `CLOJARS_DEPLOY_TOKEN=` + testDeployToken,
want: []veles.Secret{
clojars.DeployToken{Token: testDeployToken},
},
}, {
name: "match in quoted config value",
input: `:deploy-token "` + testDeployToken + `"`,
want: []veles.Secret{
clojars.DeployToken{Token: testDeployToken},
},
}, {
name: "multiple distinct matches",
input: testDeployToken + "\n" + otherToken,
want: []veles.Secret{
clojars.DeployToken{Token: testDeployToken},
clojars.DeployToken{Token: otherToken},
},
}, {
name: "larger input containing token",
input: fmt.Sprintf(`
:username "example"
:password "%s"
`, testDeployToken),
want: []veles.Secret{
clojars.DeployToken{Token: testDeployToken},
},
}}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got, err := engine.Detect(t.Context(), strings.NewReader(tc.input))
if err != nil {
t.Errorf("Detect() error: %v, want nil", err)
}
if diff := cmp.Diff(tc.want, got, cmpopts.EquateEmpty()); diff != "" {
t.Errorf("Detect() diff (-want +got):\n%s", diff)
}
})
}
}

func TestDetector_trueNegatives(t *testing.T) {
engine, err := veles.NewDetectionEngine([]veles.Detector{clojars.NewDetector()})
if err != nil {
t.Fatal(err)
}
cases := []struct {
name string
input string
want []veles.Secret
}{{
name: "empty input",
input: "",
}, {
name: "short token should not match",
input: testDeployToken[:len(testDeployToken)-1],
}, {
name: "long token should not match",
input: testDeployToken + "0",
}, {
name: "invalid hex character should not match",
input: testDeployToken[:len(testDeployToken)-1] + "g",
}, {
name: "incorrect prefix should not match",
input: "CLOJURE_" + testDeployToken[len("CLOJARS_"):],
}, {
name: "lowercase prefix should not match",
input: strings.ToLower(testDeployToken[:len("CLOJARS_")]) + testDeployToken[len("CLOJARS_"):],
}}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got, err := engine.Detect(t.Context(), strings.NewReader(tc.input))
if err != nil {
t.Errorf("Detect() error: %v, want nil", err)
}
if diff := cmp.Diff(tc.want, got, cmpopts.EquateEmpty()); diff != "" {
t.Errorf("Detect() diff (-want +got):\n%s", diff)
}
})
}
}
21 changes: 21 additions & 0 deletions veles/secrets/databrickspat/databrickspat.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright 2026 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 databrickspat

// UserAccountPAT is a Veles Secret that holds relevant information for a
// Databricks User Account Personal Access Token (prefix `dapi`).
type UserAccountPAT struct {
Token string
}
52 changes: 52 additions & 0 deletions veles/secrets/databrickspat/detector.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright 2026 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 databrickspat contains a Veles Secret type and Detector for
// Databricks User Account Personal Access Tokens (prefix `dapi`).
package databrickspat

import (
"regexp"

"github.com/google/osv-scalibr/veles"
"github.com/google/osv-scalibr/veles/secrets/common/simpletoken"
)

// maxTokenLength is the maximum size of a Databricks User Account PAT, plus
// one optional terminating delimiter consumed by patRe.
const maxTokenLength = 40

// patRe is a regular expression that matches a Databricks User Account PAT.
// Databricks API tokens have the form: `dapi` followed by 32 lowercase
// hexadecimal characters, with an optional single digit suffix.
var patRe = regexp.MustCompile(`\bdapi[a-f0-9]{32}(?:-\d)?(?:[^A-Za-z0-9-]|$)`)

// NewDetector returns a new simpletoken.Detector that matches Databricks User
// Account Personal Access Tokens.
func NewDetector() veles.Detector {
return simpletoken.Detector{
MaxLen: maxTokenLength,
Re: patRe,
FromMatch: func(b []byte) (veles.Secret, bool) {
if len(b) > 0 && !isHexChar(b[len(b)-1]) {
b = b[:len(b)-1]
}
return UserAccountPAT{Token: string(b)}, true
},
}
}

func isHexChar(b byte) bool {
return (b >= 'a' && b <= 'f') || (b >= '0' && b <= '9')
}
Loading
Loading