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
5 changes: 5 additions & 0 deletions binary/proto/scan_result.proto
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,7 @@ message SecretData {
HashiCorpVaultToken hashicorp_vault_token = 17;
HashiCorpVaultAppRoleCredentials hashicorp_vault_app_role_credentials = 18;
GCPAPIKey gcp_api_key = 19;
DenoPat deno_pat = 20;
}

message GCPSAK {
Expand Down Expand Up @@ -724,6 +725,10 @@ message SecretData {
string username = 2;
}

message DenoPat {
string pat = 1;
}

message GitlabPat {
string pat = 1;
}
Expand Down
194 changes: 130 additions & 64 deletions binary/proto/scan_result_go_proto/scan_result.pb.go

Large diffs are not rendered by default.

21 changes: 21 additions & 0 deletions binary/proto/secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/google/osv-scalibr/veles"
velesanthropicapikey "github.com/google/osv-scalibr/veles/secrets/anthropicapikey"
velesazuretoken "github.com/google/osv-scalibr/veles/secrets/azuretoken"
"github.com/google/osv-scalibr/veles/secrets/denopat"
"github.com/google/osv-scalibr/veles/secrets/dockerhubpat"
velesgcpapikey "github.com/google/osv-scalibr/veles/secrets/gcpapikey"
velesgcpsak "github.com/google/osv-scalibr/veles/secrets/gcpsak"
Expand Down Expand Up @@ -103,6 +104,8 @@ func velesSecretToProto(s veles.Secret) (*spb.SecretData, error) {
return gcpsakToProto(t), nil
case dockerhubpat.DockerHubPAT:
return dockerHubPATToProto(t), nil
case denopat.DenoPAT:
return denoPATToProto(t), nil
case velesdigitalocean.DigitaloceanAPIToken:
return digitaloceanAPIKeyToProto(t), nil
case velesanthropicapikey.WorkspaceAPIKey:
Expand Down Expand Up @@ -150,6 +153,15 @@ func dockerHubPATToProto(s dockerhubpat.DockerHubPAT) *spb.SecretData {
},
}
}
func denoPATToProto(s denopat.DenoPAT) *spb.SecretData {
return &spb.SecretData{
Secret: &spb.SecretData_DenoPat_{
DenoPat: &spb.SecretData_DenoPat{
Pat: s.Pat,
},
},
}
}

func digitaloceanAPIKeyToProto(s velesdigitalocean.DigitaloceanAPIToken) *spb.SecretData {
return &spb.SecretData{
Expand Down Expand Up @@ -429,6 +441,8 @@ func velesSecretToStruct(s *spb.SecretData) (veles.Secret, error) {
return gcpsakToStruct(s.GetGcpsak()), nil
case *spb.SecretData_DockerHubPat_:
return dockerHubPATToStruct(s.GetDockerHubPat()), nil
case *spb.SecretData_DenoPat_:
return denoPATToStruct(s.GetDenoPat()), nil
case *spb.SecretData_GitlabPat_:
return gitlabPATToStruct(s.GetGitlabPat()), nil
case *spb.SecretData_Digitalocean:
Expand Down Expand Up @@ -480,6 +494,13 @@ func dockerHubPATToStruct(kPB *spb.SecretData_DockerHubPat) dockerhubpat.DockerH
Username: kPB.GetUsername(),
}
}

func denoPATToStruct(kPB *spb.SecretData_DenoPat) denopat.DenoPAT {
return denopat.DenoPAT{
Pat: kPB.GetPat(),
}
}

func gitlabPATToStruct(kPB *spb.SecretData_GitlabPat) gitlabpat.GitlabPAT {
return gitlabpat.GitlabPAT{
Pat: kPB.GetPat(),
Expand Down
1 change: 1 addition & 0 deletions docs/supported_inventory_types.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ See the docs on [how to add a new Extractor](/docs/new_extractor.md).
| Azure Token | `secrets/azuretoken` |
| DigitalOcean API key | `secrets/digitaloceanapikey` |
| Docker hub PAT | `secrets/dockerhubpat` |
| Deno PAT | `secrets/denopat` |
| GCP API key | `secrets/gcpapikey` |
| GCP Express Mode API key | `secrets/gcpexpressmode` |
| GCP service account key | `secrets/gcpsak` |
Expand Down
2 changes: 2 additions & 0 deletions enricher/enricherlist/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"github.com/google/osv-scalibr/enricher/vulnmatch/osvdev"
"github.com/google/osv-scalibr/veles"
"github.com/google/osv-scalibr/veles/secrets/anthropicapikey"
"github.com/google/osv-scalibr/veles/secrets/denopat"
"github.com/google/osv-scalibr/veles/secrets/digitaloceanapikey"
"github.com/google/osv-scalibr/veles/secrets/dockerhubpat"
"github.com/google/osv-scalibr/veles/secrets/gcpsak"
Expand Down Expand Up @@ -75,6 +76,7 @@ var (
fromVeles(anthropicapikey.NewModelValidator(), "secrets/anthropicapikeymodelvalidate", 0),
fromVeles(digitaloceanapikey.NewValidator(), "secrets/digitaloceanapikeyvalidate", 0),
fromVeles(dockerhubpat.NewValidator(), "secrets/dockerhubpatvalidate", 0),
fromVeles(denopat.NewValidator(), "secrets/denopat", 0),
fromVeles(gcpsak.NewValidator(), "secrets/gcpsakvalidate", 0),
fromVeles(gitlabpat.NewValidator(), "secrets/gitlabpatvalidate", 0),
fromVeles(grokxaiapikey.NewAPIValidator(), "secrets/grokxaiapikeyvalidate", 0),
Expand Down
2 changes: 2 additions & 0 deletions extractor/filesystem/list/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ import (
"github.com/google/osv-scalibr/veles"
"github.com/google/osv-scalibr/veles/secrets/anthropicapikey"
"github.com/google/osv-scalibr/veles/secrets/azuretoken"
"github.com/google/osv-scalibr/veles/secrets/denopat"
"github.com/google/osv-scalibr/veles/secrets/digitaloceanapikey"
"github.com/google/osv-scalibr/veles/secrets/dockerhubpat"
"github.com/google/osv-scalibr/veles/secrets/gcpapikey"
Expand Down Expand Up @@ -251,6 +252,7 @@ var (
{azuretoken.NewDetector(), "secrets/azuretoken", 0},
{digitaloceanapikey.NewDetector(), "secrets/digitaloceanapikey", 0},
{dockerhubpat.NewDetector(), "secrets/dockerhubpat", 0},
{denopat.NewDetector(), "secrets/denopat", 0},
{gcpapikey.NewDetector(), "secrets/gcpapikey", 0},
{gcpexpressmode.NewDetector(), "secrets/gcpexpressmode", 0},
{gcpsak.NewDetector(), "secrets/gcpsak", 0},
Expand Down
22 changes: 22 additions & 0 deletions veles/secrets/denopat/denopat.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// 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 denopat

// DenoPAT is a Veles Secret that holds relevant information for a
// Deno Hub Personal Access Tokens (prefix `ddo_` and `ddp_`).
// DenoPAT represents PAT used to authenticate requests
type DenoPAT struct {
Pat string
}
59 changes: 59 additions & 0 deletions veles/secrets/denopat/detector.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// 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 denopat contains a Veles Secret type and a Detector for
// Deno Personal Access Tokens (prefix `ddo_` and `ddp_`).
package denopat

import (
"bytes"
"regexp"

"github.com/google/osv-scalibr/veles"
)

// maxTokenLength is the maximum size of a Deno PAT.
const maxTokenLength = 40

// patRe is a regular expression that matches a Deno Personal Access Token.
// Deno Personal Access Tokens have the form: `ddo_` and `ddp_` followed by 36
// alphanumeric characters.
var patRe = regexp.MustCompile(`(?:ddp|ddo)_[A-Za-z0-9]{36}`)

var _ veles.Detector = NewDetector()

// detector is a Veles Detector.
type detector struct{}

// NewDetector returns a new Detector that matches
// Deno PAT.
func NewDetector() veles.Detector {
return &detector{}
}

func (d *detector) MaxSecretLen() uint32 {
return maxTokenLength
}
func (d *detector) Detect(content []byte) ([]veles.Secret, []int) {
var secrets []veles.Secret
var offsets []int
patReMatches := patRe.FindAll(content, -1)
for _, m := range patReMatches {
newPat := string(m)
secrets = append(secrets, DenoPAT{Pat: newPat})
offsets = append(offsets, bytes.Index(content, m))
}

return secrets, offsets
}
152 changes: 152 additions & 0 deletions veles/secrets/denopat/detector_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
// 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 denopat_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/denopat"
)

const testKeyDdp = `ddp_qz538MNyqwfETb1ikqeqHiqA9Aa9Pv22yzmw`

const testKeyDdo = `ddo_qz538MNyqwfETb1ikqeqHiqA9Aa9Pv22yzmw`

// TestDetector_truePositives tests for cases where we know the Detector
// will find a Deno PAT/s.
func TestDetector_truePositives(t *testing.T) {
engine, err := veles.NewDetectionEngine([]veles.Detector{denopat.NewDetector()})
if err != nil {
t.Fatal(err)
}
cases := []struct {
name string
input string
want []veles.Secret
}{
{
name: "simple matching string with ddp_ prefix",
input: testKeyDdp,
want: []veles.Secret{
denopat.DenoPAT{Pat: testKeyDdp},
},
},
{
name: "simple matching string with ddo_ prefix",
input: testKeyDdo,
want: []veles.Secret{
denopat.DenoPAT{Pat: testKeyDdo},
},
},
{
name: "match in middle of string",
input: `DENO_PAT="` + testKeyDdp + `"`,
want: []veles.Secret{
denopat.DenoPAT{Pat: testKeyDdp},
},
},
{
name: "multiple matches",
input: testKeyDdp + testKeyDdo + testKeyDdp,
want: []veles.Secret{
denopat.DenoPAT{Pat: testKeyDdp},
denopat.DenoPAT{Pat: testKeyDdo},
denopat.DenoPAT{Pat: testKeyDdp},
},
},
{
name: "multiple distinct matches with different prefixes",
input: testKeyDdp + "\n" + testKeyDdo,
want: []veles.Secret{
denopat.DenoPAT{Pat: testKeyDdp},
denopat.DenoPAT{Pat: testKeyDdo},
},
},
{
name: "larger input containing key",
input: fmt.Sprintf(`
:test_api_key: pat-test
:deno_pat: %s
`, testKeyDdp),
want: []veles.Secret{
denopat.DenoPAT{Pat: testKeyDdp},
},
},
{
name: "potential match longer than max key length",
input: testKeyDdp + `extra`,
want: []veles.Secret{
denopat.DenoPAT{Pat: testKeyDdp},
},
},
}
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)
}
fmt.Printf("got = %+v\n", got)
if diff := cmp.Diff(tc.want, got, cmpopts.EquateEmpty()); diff != "" {
t.Errorf("Detect() diff (-want +got):\n%s", diff)
}
})
}
}

// TestDetector_trueNegatives tests for cases where we know the Detector
// will not find a Deno PAT.
func TestDetector_trueNegatives(t *testing.T) {
engine, err := veles.NewDetectionEngine([]veles.Detector{denopat.NewDetector()})
if err != nil {
t.Fatal(err)
}
cases := []struct {
name string
input string
want []veles.Secret
}{{
name: "empty input",
input: "",
}, {
name: "short key should not match",
input: testKeyDdp[:len(testKeyDdp)-1],
}, {
name: "invalid character in key should not match",
input: testKeyDdp[:len(testKeyDdp)-1] + "!",
}, {
name: "incorrect prefix should not match",
input: "aaa_" + testKeyDdp[4:],
}, {
name: "prefix missing dash should not match",
input: testKeyDdp[4:],
}}
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)
}
})
}
}
Loading
Loading