github.com/google/osv-scalibr@v0.4.1/veles/secrets/dockerhubpat/detector.go (about)

     1  // Copyright 2025 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package dockerhubpat contains a Veles Secret type and a Detector for
    16  // Docker Hub Personal Access Tokens (prefix `dckr_pat_`).
    17  package dockerhubpat
    18  
    19  import (
    20  	"bytes"
    21  	"regexp"
    22  
    23  	"github.com/google/osv-scalibr/veles"
    24  )
    25  
    26  // maxTokenLength is the maximum size of a Docker Hub API key.
    27  const maxTokenLength = 36
    28  
    29  // patRe is a regular expression that matches a Docker Hub API key.
    30  // Docker Hub Personal Access Tokens have the form: `dckr_pat_` followed by 27
    31  // alphanumeric characters.
    32  var patRe = regexp.MustCompile(`dckr_pat_[A-Za-z0-9-_-]{27}`)
    33  
    34  // dockerLoginCmdRe is a regular expression that matches a Docker Hub API key with an email or username used in switches of docker login command.
    35  // for example, `docker login -u username -p dckr_pat_{27}`.
    36  var dockerLoginCmdRe = regexp.MustCompile(`docker\s+login\s+(?:(?:(?:-u|--username)[=\s]+(\S+))|(?:(?:-p|--password)[=\s]+(dckr_pat_[a-zA-Z0-9_-]{27})))\s+(?:(?:(?:-u|--username)[=\s]+(\S+))|(?:(?:-p|--password)[=\s]+(dckr_pat_[a-zA-Z0-9_-]{27})))`)
    37  
    38  var _ veles.Detector = NewDetector()
    39  
    40  // detector is a Veles Detector.
    41  type detector struct{}
    42  
    43  // NewDetector returns a new Detector that matches
    44  // Docker Hub Personal Access Tokens.
    45  func NewDetector() veles.Detector {
    46  	return &detector{}
    47  }
    48  
    49  func (d *detector) MaxSecretLen() uint32 {
    50  	return maxTokenLength
    51  }
    52  func (d *detector) Detect(content []byte) ([]veles.Secret, []int) {
    53  	var secrets []veles.Secret
    54  	var offsets []int
    55  
    56  	// 1. docker login command username and pat detection
    57  	dockerLoginCmdMatches := dockerLoginCmdRe.FindAllSubmatch(content, -1)
    58  	for _, m := range dockerLoginCmdMatches {
    59  		// Case 1: Username in the first position, PAT in the second position
    60  		if len(m[1]) > 0 && len(m[4]) > 0 {
    61  			secrets = append(secrets, DockerHubPAT{
    62  				Username: string(m[1]),
    63  				Pat:      string(m[4]),
    64  			})
    65  			// Find the offset of this PAT in the content
    66  			patStart := bytes.Index(content, m[0]) + bytes.LastIndex(m[0], m[4])
    67  			offsets = append(offsets, patStart)
    68  		}
    69  
    70  		// Case 2: PAT in first position, Username in second position
    71  		if len(m[2]) > 0 && len(m[3]) > 0 {
    72  			secrets = append(secrets, DockerHubPAT{
    73  				Username: string(m[3]),
    74  				Pat:      string(m[2]),
    75  			})
    76  			// Find the offset of this PAT in the content
    77  			patStart := bytes.Index(content, m[0]) + bytes.Index(m[0], m[2])
    78  			offsets = append(offsets, patStart)
    79  		}
    80  	}
    81  
    82  	// 2. only pat detection, don't add duplicates from the docker login command
    83  	patReMatches := patRe.FindAll(content, -1)
    84  	for _, m := range patReMatches {
    85  		newPat := string(m)
    86  		isDuplicate := false
    87  
    88  		// Check if this PAT already exists in the secret slice and mapped to a username
    89  		for _, existingSecret := range secrets {
    90  			if dhPat, ok := existingSecret.(DockerHubPAT); ok {
    91  				if dhPat.Username != "" && dhPat.Pat == newPat {
    92  					isDuplicate = true
    93  					break
    94  				}
    95  			}
    96  		}
    97  
    98  		// Only add if not a duplicate
    99  		if !isDuplicate {
   100  			secrets = append(secrets, DockerHubPAT{Username: "", Pat: newPat})
   101  			offsets = append(offsets, bytes.Index(content, m))
   102  		}
   103  	}
   104  
   105  	return secrets, offsets
   106  }