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 @@ -698,6 +698,7 @@ message SecretData {
OnePasswordRecoveryCode onepassword_recovery_code = 41;
OnePasswordConnectToken onepassword_connect_token = 42;
Pgpass pgpass = 43;
CloudflareAPIToken cloudflare_api_token = 44;
}

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

message CloudflareAPIToken {
string token = 1;
}

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

Large diffs are not rendered by default.

22 changes: 22 additions & 0 deletions binary/proto/secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
velesanthropicapikey "github.com/google/osv-scalibr/veles/secrets/anthropicapikey"
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/cloudflareapitoken"
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 @@ -113,6 +114,8 @@ func velesSecretToProto(s veles.Secret) (*spb.SecretData, error) {
return gcpsakToProto(t), nil
case dockerhubpat.DockerHubPAT:
return dockerHubPATToProto(t), nil
case cloudflareapitoken.CloudflareAPIToken:
return cloudflareAPITokenToProto(t), nil
case velesdigitalocean.DigitaloceanAPIToken:
return digitaloceanAPIKeyToProto(t), nil
case velesslacktoken.SlackAppConfigAccessToken:
Expand Down Expand Up @@ -207,6 +210,16 @@ func dockerHubPATToProto(s dockerhubpat.DockerHubPAT) *spb.SecretData {
}
}

func cloudflareAPITokenToProto(s cloudflareapitoken.CloudflareAPIToken) *spb.SecretData {
return &spb.SecretData{
Secret: &spb.SecretData_CloudflareApiToken{
CloudflareApiToken: &spb.SecretData_CloudflareAPIToken{
Token: s.Token,
},
},
}
}

func digitaloceanAPIKeyToProto(s velesdigitalocean.DigitaloceanAPIToken) *spb.SecretData {
return &spb.SecretData{
Secret: &spb.SecretData_Digitalocean{
Expand Down Expand Up @@ -713,6 +726,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_CloudflareApiToken:
return cloudflareAPITokenToStruct(s.GetCloudflareApiToken()), nil
case *spb.SecretData_GitlabPat_:
return gitlabPATToStruct(s.GetGitlabPat()), nil
case *spb.SecretData_Digitalocean:
Expand Down Expand Up @@ -858,6 +873,13 @@ func dockerHubPATToStruct(kPB *spb.SecretData_DockerHubPat) dockerhubpat.DockerH
Username: kPB.GetUsername(),
}
}

func cloudflareAPITokenToStruct(kPB *spb.SecretData_CloudflareAPIToken) cloudflareapitoken.CloudflareAPIToken {
return cloudflareapitoken.CloudflareAPIToken{
Token: kPB.GetToken(),
}
}

func gitlabPATToStruct(kPB *spb.SecretData_GitlabPat) gitlabpat.GitlabPAT {
return gitlabpat.GitlabPAT{
Pat: kPB.GetPat(),
Expand Down
5 changes: 3 additions & 2 deletions docs/supported_inventory_types.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,11 @@ See the docs on [how to add a new Extractor](/docs/new_extractor.md).

### Secrets

| Type | Extractor Plugin |
|-----------------------------------|--------------------------------------|
| Type | Extractor Plugin |
|---------------------------------------------|--------------------------------------|
| Anthropic API key | `secrets/anthropicapikey` |
| Azure Token | `secrets/azuretoken` |
| Cloudflare API Token | `secrets/cloudflareapitoken` |
| DigitalOcean API key | `secrets/digitaloceanapikey` |
| Docker hub PAT | `secrets/dockerhubpat` |
| GCP API key | `secrets/gcpapikey` |
Expand Down
2 changes: 2 additions & 0 deletions enricher/enricherlist/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,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/cloudflareapitoken"
"github.com/google/osv-scalibr/veles/secrets/digitaloceanapikey"
"github.com/google/osv-scalibr/veles/secrets/dockerhubpat"
"github.com/google/osv-scalibr/veles/secrets/gcpoauth2access"
Expand Down Expand Up @@ -87,6 +88,7 @@ var (
fromVeles(slacktoken.NewAppConfigRefreshTokenValidator(), "secrets/slackconfigrefreshtokenvalidate", 0),
fromVeles(slacktoken.NewAppConfigAccessTokenValidator(), "secrets/slackconfigaccesstokenvalidate", 0),
fromVeles(dockerhubpat.NewValidator(), "secrets/dockerhubpatvalidate", 0),
fromVeles(cloudflareapitoken.NewValidator(), "secrets/cloudflareapitoken", 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 @@ -97,6 +97,7 @@ import (
"github.com/google/osv-scalibr/veles/secrets/anthropicapikey"
"github.com/google/osv-scalibr/veles/secrets/azurestorageaccountaccesskey"
"github.com/google/osv-scalibr/veles/secrets/azuretoken"
"github.com/google/osv-scalibr/veles/secrets/cloudflareapitoken"
"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 @@ -278,6 +279,7 @@ var (
{slacktoken.NewAppConfigRefreshTokenDetector(), "secrets/slackappconfigrefreshtoken", 0},
{slacktoken.NewAppLevelTokenDetector(), "secrets/slackappleveltoken", 0},
{dockerhubpat.NewDetector(), "secrets/dockerhubpat", 0},
{cloudflareapitoken.NewDetector(), "secrets/cloudflareapitoken", 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/cloudflareapitoken/cloudflareapitoken.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 cloudflareapitoken

// CloudflareAPIToken is a Veles Secret that holds relevant information for a
// Docker Hub Personal Access Tokens (prefix `dckr_pat_`).
// DockerHubPAT represents PAT and Username used to authenticate requests
type CloudflareAPIToken struct {
Token string
}
95 changes: 95 additions & 0 deletions veles/secrets/cloudflareapitoken/detector.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// 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 cloudflareapitoken contains a Veles Secret type and a Detector for
// Cloudflare API Tokens.
package cloudflareapitoken

import (
"regexp"

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

// maxTokenLength is the maximum size of a Cloudflare API Token.
const maxTokenLength = 40

// envVarRe matches environment variable style assignments.
// Examples: CLOUDFLARE_API_TOKEN=token, CF_API_KEY="token"
var envVarRe = regexp.MustCompile(
`(?i)(CLOUDFLARE_API_TOKEN|CLOUDFLARE_API_KEY|CF_API_TOKEN|CF_API_KEY|CLOUDFLARE_TOKEN|CF_TOKEN|CLOUDFLARE_AUTH_KEY|CF_ACCOUNT_ID)\s*=\s*['"]?([A-Za-z0-9_-]{40})\b['"]?`,
)

// jsonRe matches JSON key-value pairs.
// Examples: "CLOUDFLARE_API_TOKEN": "token", "cloudflare_api_token": "token"
var jsonRe = regexp.MustCompile(
`(?i)"(cloudflare_api_token|cloudflare_api_key|cf_api_token|cf_api_key|cloudflare_token|cf_token|cloudflare_auth_key|cf_account_id)"\s*:\s*"([A-Za-z0-9_-]{40})\b"`,
)

// yamlRe matches YAML key-value pairs.
// Examples: cloudflare_api_token: token, api_token: "token"
var yamlRe = regexp.MustCompile(
`(?i)(cloudflare_api_token|cloudflare_api_key|cf_api_token|cf_api_key|cloudflare_token|cf_token|cloudflare_auth_key|cf_account_id|api_token)\s*:\s*['"]?([A-Za-z0-9_-]{40})\b['"]?`,
)

var _ veles.Detector = NewDetector()

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

// NewDetector returns a new Detector that matches
// Cloudflare API Tokens.
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
seenTokens := make(map[string]bool)

// Define all regex patterns to check
patterns := []*regexp.Regexp{
envVarRe,
jsonRe,
yamlRe,
}

// Check each pattern
for _, pattern := range patterns {
matches := pattern.FindAllSubmatchIndex(content, -1)
for _, match := range matches {
// match[4] and match[5] contain the start and end indices of the second capture group (the token)
if len(match) >= 6 {
tokenStart := match[4]
tokenEnd := match[5]
token := string(content[tokenStart:tokenEnd])

if !seenTokens[token] {
secrets = append(secrets, CloudflareAPIToken{
Token: token,
})
offsets = append(offsets, tokenStart)
seenTokens[token] = true
}
}
}
}

return secrets, offsets
}
Loading