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 @@ -716,6 +716,7 @@ message SecretData {
Pgpass pgpass = 43;
PyPIAPIToken pypi = 44;
CratesIOAPIToken crates_io_api_token = 45;
DeepSeekAPIKey deepseek_api_key = 46;
}

message GCPSAK {
Expand Down Expand Up @@ -827,6 +828,10 @@ message SecretData {
string token = 1;
}

message DeepSeekAPIKey {
string key = 1;
}

message GithubAppRefreshToken {
string token = 1;
}
Expand Down
359 changes: 213 additions & 146 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 @@ -28,6 +28,7 @@ import (
velesazurestorageaccountaccesskey "github.com/google/osv-scalibr/veles/secrets/azurestorageaccountaccesskey"
velesazuretoken "github.com/google/osv-scalibr/veles/secrets/azuretoken"
"github.com/google/osv-scalibr/veles/secrets/cratesioapitoken"
"github.com/google/osv-scalibr/veles/secrets/deepseekapikey"
velesdigitalocean "github.com/google/osv-scalibr/veles/secrets/digitaloceanapikey"
"github.com/google/osv-scalibr/veles/secrets/dockerhubpat"
velesgcpapikey "github.com/google/osv-scalibr/veles/secrets/gcpapikey"
Expand Down Expand Up @@ -121,6 +122,8 @@ func velesSecretToProto(s veles.Secret) (*spb.SecretData, error) {
return pypiAPITokenToProto(t), nil
case cratesioapitoken.CratesIOAPItoken:
return cratesioAPITokenToProto(t), nil
case deepseekapikey.APIKey:
return deepseekAPIKeyToProto(t), nil
case velesslacktoken.SlackAppConfigAccessToken:
return slackAppConfigAccessTokenToProto(t), nil
case velesslacktoken.SlackAppConfigRefreshToken:
Expand Down Expand Up @@ -273,6 +276,16 @@ func cratesioAPITokenToProto(s cratesioapitoken.CratesIOAPItoken) *spb.SecretDat
}
}

func deepseekAPIKeyToProto(s deepseekapikey.APIKey) *spb.SecretData {
return &spb.SecretData{
Secret: &spb.SecretData_DeepseekApiKey{
DeepseekApiKey: &spb.SecretData_DeepSeekAPIKey{
Key: s.Key,
},
},
}
}

func gcpsakToProto(sak velesgcpsak.GCPSAK) *spb.SecretData {
sakPB := &spb.SecretData_GCPSAK{
PrivateKeyId: sak.PrivateKeyID,
Expand Down Expand Up @@ -747,6 +760,8 @@ func velesSecretToStruct(s *spb.SecretData) (veles.Secret, error) {
return pypiAPITokenToStruct(s.GetPypi()), nil
case *spb.SecretData_CratesIoApiToken:
return cratesioAPITokenToStruct(s.GetCratesIoApiToken()), nil
case *spb.SecretData_DeepseekApiKey:
return deepseekAPIKeyToStruct(s.GetDeepseekApiKey()), nil
case *spb.SecretData_SlackAppConfigRefreshToken_:
return slackAppConfigRefreshTokenToStruct(s.GetSlackAppConfigRefreshToken()), nil
case *spb.SecretData_SlackAppConfigAccessToken_:
Expand Down Expand Up @@ -876,6 +891,12 @@ func cratesioAPITokenToStruct(kPB *spb.SecretData_CratesIOAPIToken) cratesioapit
}
}

func deepseekAPIKeyToStruct(kPB *spb.SecretData_DeepSeekAPIKey) deepseekapikey.APIKey {
return deepseekapikey.APIKey{
Key: kPB.GetKey(),
}
}

func slackAppLevelTokenToStruct(kPB *spb.SecretData_SlackAppLevelToken) velesslacktoken.SlackAppLevelToken {
return velesslacktoken.SlackAppLevelToken{
Token: kPB.GetToken(),
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 @@ -103,6 +103,7 @@ import (
"github.com/google/osv-scalibr/veles/secrets/azurestorageaccountaccesskey"
"github.com/google/osv-scalibr/veles/secrets/azuretoken"
"github.com/google/osv-scalibr/veles/secrets/cratesioapitoken"
"github.com/google/osv-scalibr/veles/secrets/deepseekapikey"
"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 @@ -281,6 +282,7 @@ var (
{anthropicapikey.NewDetector(), "secrets/anthropicapikey", 0},
{azuretoken.NewDetector(), "secrets/azuretoken", 0},
{azurestorageaccountaccesskey.NewDetector(), "secrets/azurestorageaccountaccesskey", 0},
{deepseekapikey.NewDetector(), "secrets/deepseekapikey", 0},
{digitaloceanapikey.NewDetector(), "secrets/digitaloceanapikey", 0},
{pypiapitoken.NewDetector(), "secrets/pypiapitoken", 0},
{cratesioapitoken.NewDetector(), "secrets/cratesioapitoken", 0},
Expand Down
23 changes: 23 additions & 0 deletions veles/secrets/deepseekapikey/deepseekapikey.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// 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 deepseekapikey contains Veles Secret types and Detectors for DeepSeek API keys.
package deepseekapikey

// APIKey is a Veles Secret that holds relevant information for a DeepSeek API
// key. DeepSeek API keys start with "sk-" followed by 32 alphanumeric
// characters and are used to authenticate with the DeepSeek AI API service.
type APIKey struct {
Key string
}
43 changes: 43 additions & 0 deletions veles/secrets/deepseekapikey/detector.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// 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 deepseekapikey

import (
"regexp"

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

// maxTokenLength is the maximum size of a DeepSeek API key.
const maxTokenLength = 100

// keyRe is a regular expression that matches a DeepSeek API key.
// DeepSeek API keys start with "sk-" followed by exactly 32 lowercase
// alphanumeric characters (a-z, 0-9). The pattern uses word boundaries (\b)
// to avoid matching substrings within larger tokens.
// Example: sk-15ac903f2e481u3d4f9g2u3ia8e2b73n
var keyRe = regexp.MustCompile(`\bsk-[a-z0-9]{32}\b`)

// NewDetector returns a new simpletoken.Detector that matches DeepSeek API keys.
func NewDetector() veles.Detector {
return simpletoken.Detector{
MaxLen: maxTokenLength,
Re: keyRe,
FromMatch: func(b []byte) (veles.Secret, bool) {
return APIKey{Key: string(b)}, true
},
}
}
122 changes: 122 additions & 0 deletions veles/secrets/deepseekapikey/detector_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// 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 deepseekapikey_test

import (
"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/deepseekapikey"
)

const (
testKey = "sk-15ac903f2e481u3d4f9g2u3ia8e2b73n"
anotherKey = "sk-abcd1234567890abcdef1234567890ab"
)

func TestDetector(t *testing.T) {
engine, err := veles.NewDetectionEngine([]veles.Detector{deepseekapikey.NewDetector()})
if err != nil {
t.Fatal(err)
}

cases := []struct {
name string
input string
want []veles.Secret
}{{
name: "valid_key",
input: testKey,
want: []veles.Secret{
deepseekapikey.APIKey{Key: testKey},
},
}, {
name: "another_valid_key",
input: anotherKey,
want: []veles.Secret{
deepseekapikey.APIKey{Key: anotherKey},
},
}, {
name: "multiple_keys",
input: testKey + " " + anotherKey,
want: []veles.Secret{
deepseekapikey.APIKey{Key: testKey},
deepseekapikey.APIKey{Key: anotherKey},
},
}, {
name: "key_in_text",
input: "My DeepSeek API key is: " + testKey + " please keep it safe",
want: []veles.Secret{
deepseekapikey.APIKey{Key: testKey},
},
}}

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_NoMatches(t *testing.T) {
engine, err := veles.NewDetectionEngine([]veles.Detector{deepseekapikey.NewDetector()})
if err != nil {
t.Fatal(err)
}

cases := []struct {
name string
input string
}{{
name: "empty_input",
input: "",
}, {
name: "wrong_prefix",
input: "sk-openai-15ac903f2e481u3d4f9g2u3ia8e2b73n",
}, {
name: "too_short",
input: "sk-15ac903f",
}, {
name: "too_long",
input: "sk-15ac903f2e481u3d4f9g2u3ia8e2b73n1234567890",
}, {
name: "invalid_characters",
input: "sk-15ac903f2e481u3d4f9g2u3ia8e2b73Z",
}, {
name: "uppercase_hex",
input: "sk-15AC903F2E481U3D4F9G2U3IA8E2B73N",
}}

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 len(got) != 0 {
t.Errorf("Detect() found %d secrets, want 0", len(got))
}
})
}
}
Loading