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 }