├── run_tests.sh ├── bootstrap.sh ├── docs ├── yara-rules │ └── listing.json ├── static │ ├── img │ │ ├── deepfence.png │ │ ├── deepfence-logo-black.svg │ │ └── deepfence-logo-white.svg │ └── css │ │ └── deepfence.css ├── babel.config.js ├── .gitignore ├── README.md ├── docs │ └── secretscanner │ │ ├── using │ │ ├── build.md │ │ ├── standalone.md │ │ └── scan.md │ │ ├── configure │ │ ├── output.md │ │ └── cli.md │ │ ├── index.md │ │ └── quickstart.md ├── sidebars.js ├── package.json ├── src │ └── pages │ │ └── index.md └── docusaurus.config.js ├── images ├── Deepfence-Logo_Black.jpg ├── SampleSecretsOutput.png └── SampleJsonSecretsOutput.png ├── .gitmodules ├── SECURITY.md ├── .golangci.yml ├── rules └── yara.rules ├── .dockerignore ├── .gitignore ├── config.yaml ├── test └── keys.txt ├── itests └── check_server_image_scan.sh ├── .github └── workflows │ └── golang-linter.yaml ├── Makefile ├── jobs ├── common.go ├── status.go └── scan.go ├── LICENSE ├── Install.Ubuntu ├── core ├── session.go ├── match.go ├── config.go ├── config_test.go ├── util.go └── options.go ├── MAINTAINERS.md ├── server └── grpc.go ├── Dockerfile ├── scan └── scanner.go ├── go.mod ├── README.md ├── main.go ├── output ├── publish-to-console.go └── output.go ├── signature └── signatures.go └── go.sum /run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | bash ./itests/check_server_image_scan.sh 4 | -------------------------------------------------------------------------------- /bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | git submodule update --init --remote --recursive ./agent-plugins-grpc 3 | -------------------------------------------------------------------------------- /docs/yara-rules/listing.json: -------------------------------------------------------------------------------- 1 | { 2 | "available": { 3 | "3": [], 4 | "5": [] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /docs/static/img/deepfence.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/deepfence/SecretScanner/HEAD/docs/static/img/deepfence.png -------------------------------------------------------------------------------- /docs/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /images/Deepfence-Logo_Black.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/deepfence/SecretScanner/HEAD/images/Deepfence-Logo_Black.jpg -------------------------------------------------------------------------------- /images/SampleSecretsOutput.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/deepfence/SecretScanner/HEAD/images/SampleSecretsOutput.png -------------------------------------------------------------------------------- /images/SampleJsonSecretsOutput.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/deepfence/SecretScanner/HEAD/images/SampleJsonSecretsOutput.png -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "agent-plugins-grpc"] 2 | path = agent-plugins-grpc 3 | url = https://github.com/deepfence/agent-plugins-grpc 4 | branch = release-2.5 5 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Vulnerabilities 2 | 3 | For any security-related issues in this project, please contact us confidentially at **productsecurity (at) deepfence (dot) io**. 4 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | linters: 2 | enable: 3 | - stylecheck 4 | - gocritic 5 | # - dupl 6 | - durationcheck 7 | # - goconst 8 | - gofmt 9 | - goimports 10 | # - misspell 11 | # - nestif 12 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /rules/yara.rules: -------------------------------------------------------------------------------- 1 | rule github_personal_access_token { 2 | meta: 3 | description = "Rule to match GitHub Personal Access Tokens (classic), Fine-grained & Github Actions Token" 4 | author = "deepfence.io" 5 | 6 | strings: 7 | $github_pat = /^gh[ps]_[a-zA-Z0-9]{36}|github_pat_[a-zA-Z0-9]{22}_[a-zA-Z0-9]{59}/ 8 | 9 | condition: 10 | $github_pat 11 | } 12 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .git 3 | Dockerfile 4 | 5 | ### Go template 6 | # Binaries for programs and plugins 7 | *.exe 8 | *.exe~ 9 | *.dll 10 | *.so 11 | *.dylib 12 | 13 | # Test binary, built with `go test -c` 14 | *.test 15 | 16 | # Output of the go coverage tool, specifically when used with LiteIDE 17 | *.out 18 | 19 | # Dependency directories (remove the comment below to include it) 20 | # vendor/ 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | proto 3 | 4 | ### Go template 5 | # Binaries for programs and plugins 6 | *.exe 7 | *.exe~ 8 | *.dll 9 | *.so 10 | *.dylib 11 | 12 | # Test binary, built with `go test -c` 13 | *.test 14 | 15 | # Output of the go coverage tool, specifically when used with LiteIDE 16 | *.out 17 | 18 | # Dependency directories (remove the comment below to include it) 19 | vendor/ 20 | /SecretScanner 21 | 22 | 23 | 24 | .DS_Store 25 | -------------------------------------------------------------------------------- /config.yaml: -------------------------------------------------------------------------------- 1 | # Secret Scanner Configuration File 2 | exclude_extensions: [ ".exe", ".jpg", ".jpeg", ".png", ".gif", ".bmp", ".tiff", ".tif", ".psd", ".xcf", ".zip", ".tar", ".tar.gz", ".ttf", ".lock", ".so", ".gz" ] 3 | exclude_paths: ["/var/lib/docker", "/var/lib/containerd", "/dev", "/proc", "/usr/lib", "/sys", "/boot", "/run", ".home/kubernetes", "/snap", "/usr/bin", "/usr/local", "/opt/deepfence", "/snap"] 4 | max_file_size: 1073741824 5 | skip_non_executable: false -------------------------------------------------------------------------------- /test/keys.txt: -------------------------------------------------------------------------------- 1 | stripe key sk_live_012345678901234567890123 2 | Google Oauth key 092625-01234567890123456789abcdefghijKL.apps.googleusercontent.com 3 | 092625-01234567890123456789abcdefghijKL.apps.googleusercontent.com 4 | AWS Access Key ID AKIAIOSFODNN7EXAMPLE 5 | Access key ID,Secret access key 6 | AKIAIOSFODNN7EXAMPLE,o2/+wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY 7 | AccesskeyID=>AKIAIOSFODNN7EXAMPLE 8 | AccesskeyID=>AKIAIOSFODNN7EXAMPLE1 9 | AccesskeyID\=>\AKIADEEPFENCEEXAMPLE 10 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Website 2 | 3 | This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator. 4 | 5 | ### Installation 6 | 7 | ``` 8 | $ yarn 9 | ``` 10 | 11 | ### Local Development 12 | 13 | ``` 14 | $ yarn start 15 | ``` 16 | 17 | This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. Use `--port` to select a different port to the default (:3000). -------------------------------------------------------------------------------- /itests/check_server_image_scan.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | RET=0 4 | 5 | _term() { 6 | kill $PID 7 | exit $RET 8 | } 9 | trap _term TERM INT 10 | 11 | ./SecretScanner --socket-path /tmp/test.sock& 12 | PID=$! 13 | 14 | sleep 2 15 | 16 | COUNT=`grpcurl -plaintext -import-path ./agent-plugins-grpc/proto -proto secret_scanner.proto -d '{"image": {"name": "node:8.11"}}' -unix '/tmp/test.sock' secret_scanner.SecretScanner/FindSecretInfo | jq '.secrets[].imageLayerId' | wc -l` 17 | 18 | if [ "$COUNT" == "47" ]; then 19 | RET=0 20 | else 21 | RET=1 22 | fi 23 | 24 | _term 25 | -------------------------------------------------------------------------------- /docs/docs/secretscanner/using/build.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Build SecretScanner 3 | --- 4 | 5 | # Build SecretScanner 6 | 7 | SecretScanner is a self-contained docker-based tool. Clone the [SecretScanner repository](https://github.com/deepfence/SecretScanner), then build: 8 | 9 | ```bash 10 | ./bootstrap.sh 11 | docker build --rm=true --tag=quay.io/deepfenceio/deepfence_secret_scanner_ce:2.5.7 -f Dockerfile . 12 | ``` 13 | 14 | Alternatively, you can pull the official Deepfence image at `quay.io/deepfenceio/deepfence_secret_scanner_ce:2.5.7`: 15 | 16 | ```bash 17 | docker pull quay.io/deepfenceio/deepfence_secret_scanner_ce:2.5.7 18 | ``` -------------------------------------------------------------------------------- /.github/workflows/golang-linter.yaml: -------------------------------------------------------------------------------- 1 | name: golangci-lint 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | 8 | permissions: 9 | contents: read 10 | pull-requests: read 11 | 12 | jobs: 13 | lint: 14 | runs-on: ubuntu-22.04 15 | steps: 16 | - uses: actions/checkout@v3 17 | with: 18 | submodules: recursive 19 | fetch-depth: "0" 20 | - uses: actions/setup-go@v4 21 | with: 22 | go-version: '1.21' 23 | cache: false 24 | - name: golangci-lint 25 | uses: golangci/golangci-lint-action@v3 26 | with: 27 | version: v1.55 28 | only-new-issues: true 29 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | export IMAGE_REPOSITORY?=quay.io/deepfenceio 2 | export DF_IMG_TAG?=2.5.7 3 | 4 | all: SecretScanner 5 | 6 | bootstrap: 7 | $(PWD)/bootstrap.sh 8 | 9 | clean: 10 | -rm ./SecretScanner 11 | 12 | vendor: go.mod 13 | go mod tidy -v 14 | go mod vendor 15 | 16 | SecretScanner: vendor $(PWD)/**/*.go $(PWD)/agent-plugins-grpc/**/*.go 17 | CGO_LDFLAGS="-ljansson -lcrypto -lmagic" PKG_CONFIG_PATH=/usr/local/yara/lib/pkgconfig:$(PKG_CONFIG_PATH) go build -buildmode=pie -ldflags="-s -w -extldflags=-static -X 'main.version=$(DF_IMG_TAG)'" -buildvcs=false -v . 18 | 19 | .PHONY: clean bootstrap 20 | 21 | .PHONY: docker 22 | docker: 23 | docker build -t $(IMAGE_REPOSITORY)/deepfence_secret_scanner_ce:$(DF_IMG_TAG) . 24 | -------------------------------------------------------------------------------- /docs/docs/secretscanner/configure/output.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Configure Output 3 | --- 4 | 5 | # Configure Output 6 | 7 | SecretScanner can writes output to `stdout` it can redirected to a file for further analysis. 8 | 9 | ```bash 10 | # Write output to ./tmp/node-secret-scan.json 11 | 12 | docker run -it --rm --name=deepfence_secret_scanner \ 13 | -e DEEPFENCE_PRODUCT= \ 14 | -e DEEPFENCE_LICENSE= \ 15 | -v /var/run/docker.sock:/var/run/docker.sock \ 16 | quay.io/deepfenceio/deepfence_secret_scanner_ce:2.5.7 \ 17 | --image-name node:latest \ 18 | # highlight-next-line 19 | --output json > ./tmp/node-secret-scan.json 20 | ``` 21 | 22 | -------------------------------------------------------------------------------- /jobs/common.go: -------------------------------------------------------------------------------- 1 | package jobs 2 | 3 | import ( 4 | "os" 5 | "sync/atomic" 6 | ) 7 | 8 | var ( 9 | scanStatusFilename = GetDfInstallDir() + "/var/log/fenced/secret-scan-log/secret_scan_log.log" 10 | scanFilename = GetDfInstallDir() + "/var/log/fenced/secret-scan/secret_scan.log" 11 | SecretScanDir = "/" 12 | ) 13 | 14 | const ( 15 | HostMountDir = "/fenced/mnt/host" 16 | ) 17 | 18 | func init() { 19 | if os.Getenv("DF_SERVERLESS") == "true" { 20 | SecretScanDir = "/" 21 | } else { 22 | SecretScanDir = HostMountDir 23 | } 24 | } 25 | 26 | var ( 27 | running_jobs_num atomic.Int32 28 | ) 29 | 30 | func startScanJob() { 31 | running_jobs_num.Add(1) 32 | } 33 | 34 | func stopScanJob() { 35 | running_jobs_num.Add(-1) 36 | } 37 | 38 | func GetRunningJobCount() int32 { 39 | return running_jobs_num.Load() 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 deepfence 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Install.Ubuntu: -------------------------------------------------------------------------------- 1 | apt-get update && apt-get install -y git gcc cmake make build-essential python2.7 pkg-config ragel libboost-dev wget nano 2 | mkdir -p /usr/local/include/ && \ 3 | cd /usr/local/include/ && \ 4 | git clone https://github.com/intel/hyperscan.git && \ 5 | mkdir /usr/local/include/hs && \ 6 | cd /usr/local/include/hs && \ 7 | cmake -DBUILD_STATIC_AND_SHARED=1 /usr/local/include/hyperscan && \ 8 | echo "/usr/local/lib" | tee --append /etc/ld.so.conf.d/usrlocal.conf && \ 9 | cd /usr/local/include/hs && make && make install 10 | wget https://dl.google.com/go/go1.14.2.linux-amd64.tar.gz && \ 11 | tar -C /usr/local -zxvf go1.14.2.linux-amd64.tar.gz && \ 12 | mv /usr/local/go /usr/local/go-1.14.2 && \ 13 | rm go1.14.2.linux-amd64.tar.gz && \ 14 | mkdir ~/.go 15 | export GOPATH=~/.go \ 16 | PKG_CONFIG_PATH=/usr/local/include/hs/ \ 17 | CGO_CFLAGS="-I/usr/local/include/hyperscan/src" \ 18 | LD_LIBRARY_PATH=/usr/local/lib:/usr/local/include/hs/lib:$LD_LIBRARY_PATH \ 19 | PATH=/usr/local/go-1.14.2/bin:~/.go/bin:$PATH 20 | go get "github.com/flier/gohs/hyperscan" "gopkg.in/yaml.v3" "github.com/fatih/color" 21 | go get "github.com/deepfence/SecretScanner" 22 | -------------------------------------------------------------------------------- /docs/sidebars.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creating a sidebar enables you to: 3 | - create an ordered group of docs 4 | - render a sidebar for each doc of that group 5 | - provide next/previous navigation 6 | 7 | The sidebars can be generated from the filesystem, or explicitly defined here. 8 | 9 | Create as many sidebars as you want. 10 | */ 11 | 12 | // @ts-check 13 | 14 | /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ 15 | const sidebars = { 16 | secretscanner: [ 17 | { 18 | type: 'html', 19 | value: 'Deepfence SecretScanner', 20 | className: 'sidebar-title', 21 | }, 22 | 23 | "secretscanner/index", 24 | "secretscanner/quickstart", 25 | 26 | { 27 | type: 'category', 28 | label: 'Using SecretScanner', 29 | 30 | items: [ 31 | 'secretscanner/using/build', 32 | 'secretscanner/using/scan', 33 | 'secretscanner/using/standalone' 34 | ] 35 | }, 36 | 37 | { 38 | type: 'category', 39 | label: 'Configuration', 40 | items: [ 41 | 'secretscanner/configure/cli', 42 | 'secretscanner/configure/output', 43 | ] 44 | }, 45 | ], 46 | }; 47 | 48 | module.exports = sidebars; 49 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-site", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "docusaurus": "docusaurus", 7 | "start": "docusaurus start", 8 | "build": "docusaurus build", 9 | "swizzle": "docusaurus swizzle", 10 | "deploy": "docusaurus deploy", 11 | "clear": "docusaurus clear", 12 | "serve": "docusaurus serve", 13 | "write-translations": "docusaurus write-translations", 14 | "write-heading-ids": "docusaurus write-heading-ids" 15 | }, 16 | "dependencies": { 17 | "@docusaurus/core": "^2.0.0-rc.1", 18 | "@docusaurus/preset-classic": "^2.0.0-rc.1", 19 | "@mdx-js/react": "^1.6.22", 20 | "clsx": "^1.2.0", 21 | "prism-react-renderer": "^1.3.5", 22 | "react": "^17.0.2", 23 | "react-dom": "^17.0.2" 24 | }, 25 | "devDependencies": { 26 | "@docusaurus/module-type-aliases": "^2.0.0-rc.1" 27 | }, 28 | "browserslist": { 29 | "production": [ 30 | ">0.5%", 31 | "not dead", 32 | "not op_mini all" 33 | ], 34 | "development": [ 35 | "last 1 chrome version", 36 | "last 1 firefox version", 37 | "last 1 safari version" 38 | ] 39 | }, 40 | "engines": { 41 | "node": ">=16.14" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /core/session.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "runtime" 7 | "sync" 8 | 9 | "github.com/deepfence/match-scanner/pkg/config" 10 | log "github.com/sirupsen/logrus" 11 | ) 12 | 13 | type Session struct { 14 | sync.Mutex 15 | Version string 16 | Options *Options 17 | Config *Config 18 | Context context.Context 19 | ExtractorConfig config.Config 20 | } 21 | 22 | var ( 23 | session *Session 24 | sessionSync sync.Once 25 | err error 26 | ) 27 | 28 | func (s *Session) Start() { 29 | s.InitThreads() 30 | } 31 | 32 | func (s *Session) InitThreads() { 33 | if *s.Options.Threads == 0 { 34 | numCPUs := runtime.NumCPU() 35 | s.Options.Threads = &numCPUs 36 | } 37 | 38 | runtime.GOMAXPROCS(*s.Options.Threads + 1) 39 | } 40 | 41 | func GetSession() *Session { 42 | sessionSync.Do(func() { 43 | session = &Session{ 44 | Context: context.Background(), 45 | } 46 | 47 | if session.Options, err = ParseOptions(); err != nil { 48 | log.Error(err) 49 | os.Exit(1) 50 | } 51 | 52 | if session.ExtractorConfig, err = loadExtractorConfigFile(session.Options); err != nil { 53 | log.Error(err) 54 | os.Exit(1) 55 | } 56 | 57 | session.Start() 58 | }) 59 | 60 | return session 61 | } 62 | -------------------------------------------------------------------------------- /jobs/status.go: -------------------------------------------------------------------------------- 1 | package jobs 2 | 3 | import ( 4 | "encoding/json" 5 | "os" 6 | "path/filepath" 7 | "strings" 8 | ) 9 | 10 | func writeSecretScanStatus(status, scan_id, scan_message string) error { 11 | var secretScanLogDoc = make(map[string]interface{}) 12 | secretScanLogDoc["scan_id"] = scan_id 13 | secretScanLogDoc["scan_status"] = status 14 | secretScanLogDoc["scan_message"] = scan_message 15 | 16 | byteJson, err := json.Marshal(secretScanLogDoc) 17 | if err != nil { 18 | return err 19 | } 20 | 21 | err = writeScanDataToFile(string(byteJson), scanStatusFilename) 22 | if err != nil { 23 | return err 24 | } 25 | return nil 26 | } 27 | 28 | func writeScanDataToFile(secretScanMsg string, filename string) error { 29 | err := os.MkdirAll(filepath.Dir(filename), 0755) 30 | f, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600) 31 | if err != nil { 32 | return err 33 | } 34 | 35 | defer f.Close() 36 | 37 | secretScanMsg = strings.Replace(secretScanMsg, "\n", " ", -1) 38 | if _, err = f.WriteString(secretScanMsg + "\n"); err != nil { 39 | return err 40 | } 41 | return nil 42 | } 43 | 44 | func GetDfInstallDir() string { 45 | installDir, exists := os.LookupEnv("DF_INSTALL_DIR") 46 | if exists { 47 | return installDir 48 | } else { 49 | return "" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /docs/src/pages/index.md: -------------------------------------------------------------------------------- 1 | import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; 2 | export function siteConfig() { 3 | const {siteConfig, siteMetadata} = useDocusaurusContext(); 4 | return siteConfig; 5 | } 6 | export function siteMetadata() { 7 | const {siteConfig, siteMetadata} = useDocusaurusContext(); 8 | return siteMetadata; 9 | } 10 | 11 | 12 |

{siteConfig().title}

13 | 14 |

{siteConfig().tagline}

15 | 16 | 17 | 18 |
19 | {siteConfig().themeConfig.navbar.items[0].label} 20 |
21 |
22 |
23 | 24 |    25 | 26 | 27 | 28 |
29 | Edit on GitHub 30 |
31 |
32 |
33 | 34 | 35 |
36 | 37 |
38 | Technical Details 39 | 40 |
41 | Site Config. Site config comes from docusaurus.config.js 42 |
{JSON.stringify( siteConfig(), null, 2 )}
43 |
44 | 45 | 46 |
47 | Site MetaData. Site metadata comes from docusaurus install 48 |
{JSON.stringify(siteMetadata(), null, 2) }
49 |
50 | 51 |
-------------------------------------------------------------------------------- /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | ## Maintainers of Deepfence SecretScanner 2 | 3 | | Name | Email | Deepfence Community Slack | GitHub | Company | 4 | |-------------------|---------------------------|---------------------------|------------------------------------------------------|-----------| 5 | | Ramanan Ravikumar | ramanan@deepfence.io | `@Ramanan` | [@ramanan-ravi](https://github.com/ramanan-ravi) | Deepfence | 6 | | Saurabh Kumar | saurabh@deepfence.io | `@Saurabh` | [@saurabh2253](https://github.com/saurabh2253) | Deepfence | 7 | | Harshvardhan Karn | harshvardhan@deepfence.io | `@harsh` | [@ibreakthecloud](https://github.com/ibreakthecloud) | Deepfence | | | | | | 8 | 9 | Please reach any of the maintainers on slack (#community-support on https://deepfence-community.slack.com) or email if you want to help. 10 | 11 | ## How to be maintainer? 12 | 13 | Any contributor that shows effort, consistentcy and willingness in maintaining a repository will be invited to join the Maintainers team. 14 | 15 | Open Source is all about the trust, which is the key factor in decision to add write permissions. 16 | 17 | Reach us if you have any questions on how to join maintainer team. 18 | -------------------------------------------------------------------------------- /docs/docs/secretscanner/using/standalone.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Standalone Build 3 | --- 4 | 5 | # Running SecretScanner standalone 6 | 7 | 8 | As an alternative to running SecretScanner as a docker container, you can build it as a Standalone application. 9 | 10 | :::info 11 | 12 | ### Help needed! 13 | 14 | These instructions are out-of-date and need refreshed 15 | ::: 16 | 17 | ## Build Instructions 18 | 19 | 1. Run bootstrap.sh 20 | 2. Install Docker 21 | 3. Install Hyperscan 22 | 4. Install go for your platform (version 1.14) 23 | 5. Install go modules, if needed: `gohs`, `yaml.v3` and `color` 24 | 6. `go get github.com/deepfence/SecretScanner` will download and build SecretScanner automatically in `$GOPATH/bin` or `$HOME/go/bin` directory. Or, clone this repository and run `go build -v -i` to build the executable in the current directory. 25 | 7. Edit config.yaml file as needed and run the secret scanner with the appropriate config file directory. 26 | 27 | Refer to the [Install file](https://github.com/deepfence/SecretScanner/blob/master/Install.Ubuntu) for instructions on how to build on an ubuntu system. 28 | 29 | ## Instructions to Run on Local Host 30 | 31 | ### As a standalone application 32 | 33 | ```bash 34 | ./SecretScanner --help 35 | 36 | ./SecretScanner -config-path /path/to/config.yaml/dir -local test 37 | 38 | ./SecretScanner -config-path /path/to/config.yaml/dir -image-name node:8.11 39 | ``` 40 | 41 | ### As a server application 42 | ```bash 43 | ./SecretScanner -socket-path /path/to/socket.sock 44 | ``` 45 | -------------------------------------------------------------------------------- /docs/docs/secretscanner/using/scan.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Scan with SecretScanner 3 | --- 4 | 5 | # Scanning with SecretScanner 6 | 7 | You can use SecretScanner to scan running or at-rest container images, and local file systems. SecretScanner will match the assets it finds against the secrets rules it has been configured with. 8 | 9 | ## Scan a Container Image 10 | 11 | Pull the image to your local repository, then scan it 12 | 13 | ```bash 14 | docker pull node:latest 15 | 16 | docker run -it --rm --name=deepfence-secretscanner \ 17 | -e DEEPFENCE_PRODUCT= \ 18 | -e DEEPFENCE_LICENSE= \ 19 | -v /var/run/docker.sock:/var/run/docker.sock \ 20 | quay.io/deepfenceio/deepfence_secret_scanner_ce:2.5.7 \ 21 | # highlight-next-line 22 | --image-name node:latest 23 | 24 | docker rmi node:latest 25 | ``` 26 | 27 | ### Scan a filesystem 28 | 29 | Mount the filesystem within the SecretScanner container and scan it. Here, we scan the contents of `/tmp` on the host: 30 | 31 | ```bash 32 | docker run -it --rm --name=deepfence-secretscanner \ 33 | -e DEEPFENCE_PRODUCT= \ 34 | -e DEEPFENCE_LICENSE= \ 35 | # highlight-next-line 36 | -v /tmp:/deepfence/mnt \ 37 | quay.io/deepfenceio/deepfence_secret_scanner_ce:2.5.7 \ 38 | # highlight-next-line 39 | --host-mount-path /deepfence/mnt --local /deepfence/mnt 40 | ``` 41 | 42 | Note that you can use nerdctl as an alternative to docker in the commands above. -------------------------------------------------------------------------------- /server/grpc.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/deepfence/SecretScanner/jobs" 7 | "github.com/deepfence/SecretScanner/output" 8 | out "github.com/deepfence/YaraHunter/pkg/output" 9 | "github.com/deepfence/YaraHunter/pkg/server" 10 | pb "github.com/deepfence/agent-plugins-grpc/srcgo" 11 | "github.com/sirupsen/logrus" 12 | //nolint:typecheck 13 | ) 14 | 15 | type SecretGRPCServer struct { 16 | *server.GRPCScannerServer 17 | pb.UnimplementedSecretScannerServer 18 | } 19 | 20 | func (s *SecretGRPCServer) FindSecretInfo(c context.Context, r *pb.FindRequest) (*pb.FindResult, error) { 21 | yaraScanner, err := s.YaraRules.NewScanner() 22 | if err != nil { 23 | return &pb.FindResult{}, err 24 | } 25 | 26 | go func() { 27 | logrus.Infof("request to scan %+v", r) 28 | 29 | namespace := "" 30 | container := "" 31 | image := "" 32 | path := "" 33 | switch { 34 | case r.GetContainer() != nil: 35 | namespace = r.GetContainer().GetNamespace() 36 | container = r.GetContainer().GetId() 37 | case r.GetImage() != nil: 38 | image = r.GetImage().GetName() 39 | default: 40 | path = r.GetPath() 41 | } 42 | 43 | server.DoScan( 44 | r.ScanId, 45 | s.HostMountPath, 46 | s.ExtractorConfig, 47 | s.InactiveThreshold, 48 | &s.ScanMap, 49 | namespace, 50 | path, 51 | image, 52 | container, 53 | yaraScanner, 54 | func(res out.IOCFound, scanID string) { 55 | for i := range res.Matches { 56 | jobs.WriteSingleScanData(output.SecretToSecretInfo(res, i), scanID) 57 | } 58 | }, 59 | ) 60 | }() 61 | return &pb.FindResult{}, nil 62 | } 63 | -------------------------------------------------------------------------------- /jobs/scan.go: -------------------------------------------------------------------------------- 1 | package jobs 2 | 3 | import ( 4 | "encoding/json" 5 | "strings" 6 | "sync" 7 | 8 | pb "github.com/deepfence/agent-plugins-grpc/srcgo" 9 | log "github.com/sirupsen/logrus" 10 | ) 11 | 12 | var ScanMap sync.Map 13 | 14 | type SecretScanDoc struct { 15 | pb.SecretInfo 16 | ScanID string `json:"scan_id,omitempty"` 17 | } 18 | 19 | func writeMultiScanData(secrets []*pb.SecretInfo, scan_id string) { 20 | for _, secret := range secrets { 21 | if SecretScanDir == HostMountDir { 22 | secret.GetMatch().FullFilename = strings.Replace(secret.GetMatch().GetFullFilename(), SecretScanDir, "", 1) 23 | } 24 | secretScanDoc := SecretScanDoc{ 25 | SecretInfo: *secret, 26 | ScanID: scan_id, 27 | } 28 | byteJson, err := json.Marshal(secretScanDoc) 29 | if err != nil { 30 | log.Errorf("Error marshalling json: ", err) 31 | continue 32 | } 33 | err = writeScanDataToFile(string(byteJson), scanFilename) 34 | if err != nil { 35 | log.Errorf("Error in sending data to secretScanIndex:" + err.Error()) 36 | continue 37 | } 38 | } 39 | } 40 | 41 | func WriteSingleScanData(secret *pb.SecretInfo, scan_id string) { 42 | if SecretScanDir == HostMountDir { 43 | secret.GetMatch().FullFilename = strings.Replace(secret.GetMatch().GetFullFilename(), SecretScanDir, "", 1) 44 | } 45 | secretScanDoc := SecretScanDoc{ 46 | SecretInfo: *secret, 47 | ScanID: scan_id, 48 | } 49 | byteJson, err := json.Marshal(secretScanDoc) 50 | if err != nil { 51 | log.Errorf("Error marshalling json: ", err) 52 | return 53 | } 54 | err = writeScanDataToFile(string(byteJson), scanFilename) 55 | if err != nil { 56 | log.Errorf("Error in sending data to secretScanIndex:" + err.Error()) 57 | return 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /core/match.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | 7 | log "github.com/sirupsen/logrus" 8 | ) 9 | 10 | type MatchFile struct { 11 | Path string 12 | Filename string 13 | Extension string 14 | Contents []byte 15 | } 16 | 17 | // NewMatchFile Creates a new Matchfile data structure 18 | func NewMatchFile(path string) MatchFile { 19 | path = filepath.ToSlash(path) 20 | _, filename := filepath.Split(path) 21 | extension := filepath.Ext(path) 22 | // contents, _ := ioutil.ReadFile(path) 23 | 24 | return MatchFile{ 25 | Path: path, 26 | Filename: filename, 27 | Extension: extension, 28 | Contents: []byte(""), // contents, 29 | } 30 | } 31 | 32 | //// GetMatchingFiles Return the list of all applicable files inside the given directory for scanning 33 | // func GetMatchingFiles(dir string, baseDir string) (*bytes.Buffer, *bytes.Buffer, error) { 34 | // findCmd := "find " + dir 35 | // for _, skippableExt := range session.Config.BlacklistedExtensions { 36 | // findCmd += " -not -name \"*" + skippableExt + "\"" 37 | // } 38 | // hostMountPath := *session.Options.HostMountPath 39 | // if hostMountPath != "" { 40 | // baseDir = hostMountPath 41 | // } 42 | // for _, skippablePathIndicator := range session.Config.BlacklistedPaths { 43 | // findCmd += " -path " + baseDir + skippablePathIndicator + " -prune -o" 44 | // } 45 | // maxFileSize := strconv.FormatUint(uint64(*session.Options.MaximumFileSize), 10) 46 | // findCmd += " -type f -size " + maxFileSize + "M" 47 | // log.Info("find command: %s", findCmd) 48 | // 49 | // return ExecuteCommand(findCmd) 50 | //} 51 | 52 | // UpdateDirsPermissionsRW Update permissions for dirs in container images, so that they can be properly deleted 53 | func UpdateDirsPermissionsRW(dir string) { 54 | _ = filepath.WalkDir(dir, func(path string, f os.DirEntry, err error) error { 55 | if f.IsDir() { 56 | err := os.Chmod(path, 0700) 57 | if err != nil { 58 | log.Errorf("Failed to change dir %s permission: %s", path, err) 59 | } 60 | } 61 | return nil 62 | }) 63 | } 64 | -------------------------------------------------------------------------------- /docs/docs/secretscanner/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Introduction to SecretScanner 3 | --- 4 | 5 | # SecretScanner 6 | 7 | Deepfence SecretScanner can find unprotected secrets in container images or file systems. 8 | 9 | * SecretScanner is a standalone tool that retrieves and searches container and host filesystems, matching the contents against a database of approximately 140 secret types. 10 | * SecretScanner is also included in [ThreatMapper](https://github.com/deepfence/ThreatMapper), an open source scanner that identifies vulnerable dependencies and unprotected secrets in cloud native applications, and ranks these vulnerabilities based on their risk-of-exploit. 11 | 12 | ## What are Secrets? 13 | 14 | Secrets are any kind of sensitive or private data which gives authorized users permission to access critical IT infrastructure (such as accounts, devices, network, cloud based services), applications, storage, databases and other kinds of critical data for an organization. For example, passwords, AWS access IDs, AWS secret access keys, Google OAuth Key etc. are secrets. 15 | 16 | ## SecretScanner in Action 17 | 18 | ![SecretScanner in Action](img/secretscanner.svg) 19 | 20 | ## When to use SecretScanner 21 | 22 | Secrets should be strictly kept private. However, sometimes attackers can easily access secrets due to flawed security policies or inadvertent mistakes by developers. Sometimes developers use default secrets or leave hard-coded secrets such as passwords, API keys, encryption keys, SSH keys, tokens etc. in container images, especially during rapid development and deployment cycles in CI/CD pipeline. Also, sometimes users store passwords in plain text. Leakage of secrets to unauthorized entities can put your organization and infrastructure at serious security risk. 23 | 24 | Deepfence SecretScanner scans container images or local directories on hosts and outputs a JSON file with details of all the secrets found. 25 | 26 | 27 | ## Credits 28 | 29 | SecretScanner builds on the configuration file from [shhgit](https://github.com/eth0izzle/shhgit) project. 30 | -------------------------------------------------------------------------------- /core/config.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "regexp" 7 | 8 | "github.com/deepfence/match-scanner/pkg/config" 9 | ) 10 | 11 | type Config struct { 12 | Signatures []ConfigSignature `yaml:"signatures"` 13 | } 14 | 15 | type ConfigSignature struct { 16 | Name string `yaml:"name"` 17 | Part string `yaml:"part"` 18 | Match string `yaml:"match,omitempty"` 19 | Regex string `yaml:"regex,omitempty"` 20 | RegexType string `yaml:"regextype,omitempty"` 21 | CompiledRegex *regexp.Regexp 22 | Verifier string `yaml:"verifier,omitempty"` 23 | Severity string `yaml:"severity,omitempty"` 24 | SeverityScore float64 `yaml:"severityscore,omitempty"` 25 | ID int `yaml:"ID,omitempty"` 26 | } 27 | 28 | func (c *Config) Merge(in *Config) { 29 | signatureNames := make(map[string]bool, len(c.Signatures)) 30 | for _, sig := range c.Signatures { 31 | signatureNames[sig.Name] = true 32 | } 33 | for _, sig := range in.Signatures { 34 | if _, exists := signatureNames[sig.Name]; exists { 35 | for i, eSig := range c.Signatures { 36 | if sig.Name == eSig.Name { 37 | c.Signatures[i] = sig 38 | break 39 | } 40 | } 41 | } else { 42 | c.Signatures = append(c.Signatures, sig) 43 | } 44 | } 45 | } 46 | 47 | func mergeStringSlices(old, new []string) []string { 48 | m := make(map[string]bool, len(old)) 49 | for _, s := range old { 50 | m[s] = true 51 | } 52 | 53 | for _, s := range new { 54 | if _, ok := m[s]; !ok { 55 | old = append(old, s) 56 | } 57 | } 58 | 59 | return old 60 | } 61 | 62 | func loadExtractorConfigFile(options *Options) (config.Config, error) { 63 | configPath := *options.ConfigPath 64 | fstat, err := os.Stat(configPath) 65 | if err != nil { 66 | return config.Config{}, err 67 | } 68 | 69 | if fstat.IsDir() { 70 | return config.ParseConfig(filepath.Join(configPath, "config.yaml")) 71 | } 72 | return config.ParseConfig(configPath) 73 | } 74 | 75 | func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error { 76 | *c = Config{} 77 | type plain Config 78 | 79 | err := unmarshal((*plain)(c)) 80 | 81 | if err != nil { 82 | return err 83 | } 84 | 85 | return nil 86 | } 87 | -------------------------------------------------------------------------------- /docs/docs/secretscanner/quickstart.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: SecretScanner QuickStart 3 | --- 4 | 5 | # Quick Start 6 | 7 | Pull the latest SecretScanner image, and use it to scan a `node:latest` container. 8 | 9 | ## Pull the latest SecretScanner image 10 | 11 | ```bash 12 | docker pull quay.io/deepfenceio/deepfence_secret_scanner_ce:2.5.7 13 | ``` 14 | 15 | ## Generate License Key 16 | 17 | Run this command to generate a license key. Work/official email id has to be used. 18 | ```shell 19 | curl https://license.deepfence.io/threatmapper/generate-license?first_name=&last_name=&email=&company=&resend_email=true 20 | ``` 21 | 22 | ## Scan a Container Image 23 | 24 | Pull an image to your local repository, then scan it 25 | 26 | ```bash 27 | docker pull node:latest 28 | 29 | docker run -i --rm --name=deepfence-secretscanner \ 30 | -e DEEPFENCE_PRODUCT= \ 31 | -e DEEPFENCE_LICENSE= \ 32 | -v /var/run/docker.sock:/var/run/docker.sock \ 33 | quay.io/deepfenceio/deepfence_secret_scanner_ce:2.5.7 \ 34 | -image-name node:latest 35 | 36 | docker rmi node:latest 37 | ``` 38 | 39 | Rules can also be cached to use next run by mounting a seperate path and passing `rules-path` argument 40 | ```shell 41 | docker run -i --rm --name=deepfence-yarahunter \ 42 | -e DEEPFENCE_PRODUCT= \ 43 | -e DEEPFENCE_LICENSE= \ 44 | -v /var/run/docker.sock:/var/run/docker.sock \ 45 | -v /tmp/rules:/tmp/rules \ 46 | quay.io/deepfenceio/deepfence_secret_scanner_ce:2.5.7 \ 47 | --image-name node:8.11 \ 48 | --rules-path=/tmp/rules \ 49 | --output json > node.json 50 | ``` 51 | 52 | ## Process the results with jq 53 | 54 | You can summarise the results by processing the JSON output, e.g. using `jq`: 55 | 56 | ```bash 57 | docker run -i --rm --name=deepfence-secretscanner \ 58 | -e DEEPFENCE_PRODUCT= \ 59 | -e DEEPFENCE_LICENSE= \ 60 | -v /var/run/docker.sock:/var/run/docker.sock \ 61 | quay.io/deepfenceio/deepfence_secret_scanner_ce:2.5.7 \ 62 | --image-name node:latest \ 63 | --output json > /tmp/node-secret-scan.json 64 | 65 | cat /tmp/node-secret-scan.json | jq '.Secrets[] | { rule: ."Matched Rule Name", file: ."Full File Name" }' 66 | ``` -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ARG VECTORSCAN_IMG_TAG=latest 2 | ARG VECTORSCAN_IMAGE_REPOSITORY=deepfenceio 3 | FROM $VECTORSCAN_IMAGE_REPOSITORY/deepfence_vectorscan_build:$VECTORSCAN_IMG_TAG AS vectorscan 4 | 5 | FROM golang:1.23-alpine3.20 AS builder 6 | MAINTAINER DeepFence 7 | 8 | RUN apk update \ 9 | && apk add --upgrade gcc musl-dev pkgconfig g++ make git 10 | 11 | RUN apk add --no-cache \ 12 | git \ 13 | make \ 14 | build-base \ 15 | pkgconfig \ 16 | libpcap-dev \ 17 | libcap-dev \ 18 | openssl-dev \ 19 | file \ 20 | jansson-dev \ 21 | jansson-static \ 22 | bison \ 23 | tini \ 24 | su-exec 25 | 26 | RUN apk add --no-cache -t .build-deps py-setuptools \ 27 | openssl-libs-static \ 28 | jansson-dev \ 29 | build-base \ 30 | libc-dev \ 31 | file-dev \ 32 | automake \ 33 | autoconf \ 34 | libtool \ 35 | libcrypto3 \ 36 | flex \ 37 | git \ 38 | libmagic-static \ 39 | linux-headers 40 | 41 | RUN cd /root && wget https://github.com/VirusTotal/yara/archive/refs/tags/v4.3.2.tar.gz \ 42 | && tar -zxf v4.3.2.tar.gz \ 43 | && cd yara-4.3.2 \ 44 | && ./bootstrap.sh \ 45 | && ./configure --prefix=/usr/local/yara --disable-dotnet --enable-magic --enable-cuckoo --disable-shared --enable-static\ 46 | && make \ 47 | && make install \ 48 | && cd /usr/local/ \ 49 | && tar -czf yara.tar.gz yara 50 | 51 | WORKDIR /home/deepfence/src/SecretScanner 52 | COPY . . 53 | RUN make clean && make all 54 | 55 | FROM alpine:3.20 56 | MAINTAINER DeepFence 57 | LABEL deepfence.role=system 58 | 59 | ENV MGMT_CONSOLE_URL=deepfence-internal-router \ 60 | MGMT_CONSOLE_PORT=443 61 | 62 | ARG TARGETARCH 63 | 64 | RUN apk add --no-cache --upgrade tar libstdc++ libgcc docker skopeo bash podman 65 | 66 | RUN < ./agent-plugins-grpc 6 | 7 | require ( 8 | github.com/deepfence/YaraHunter v0.0.0-20250424121102-5c7a440b1405 9 | github.com/deepfence/agent-plugins-grpc v0.0.0-00010101000000-000000000000 10 | github.com/deepfence/golang_deepfence_sdk/client v0.0.0-20250404165334-270bd6030734 11 | github.com/deepfence/golang_deepfence_sdk/utils v0.0.0-20250404165334-270bd6030734 12 | github.com/deepfence/match-scanner v0.0.0-20250424114533-ad87103b0479 13 | github.com/fatih/color v1.18.0 14 | github.com/olekukonko/tablewriter v0.0.5 15 | github.com/sirupsen/logrus v1.9.3 16 | google.golang.org/grpc v1.72.0 17 | ) 18 | 19 | require ( 20 | github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect 21 | github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0 // indirect 22 | github.com/Microsoft/go-winio v0.6.2 // indirect 23 | github.com/Microsoft/hcsshim v0.11.7 // indirect 24 | github.com/VirusTotal/gyp v0.9.0 // indirect 25 | github.com/containerd/cgroups v1.1.0 // indirect 26 | github.com/containerd/containerd v1.7.27 // indirect 27 | github.com/containerd/containerd/api v1.8.0 // indirect 28 | github.com/containerd/continuity v0.4.4 // indirect 29 | github.com/containerd/errdefs v0.3.0 // indirect 30 | github.com/containerd/fifo v1.1.0 // indirect 31 | github.com/containerd/log v0.1.0 // indirect 32 | github.com/containerd/platforms v0.2.1 // indirect 33 | github.com/containerd/ttrpc v1.2.7 // indirect 34 | github.com/containerd/typeurl/v2 v2.1.1 // indirect 35 | github.com/deepfence/vessel v0.14.0 // indirect 36 | github.com/distribution/reference v0.6.0 // indirect 37 | github.com/docker/docker v28.1.1+incompatible // indirect 38 | github.com/docker/go-connections v0.4.0 // indirect 39 | github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect 40 | github.com/docker/go-units v0.5.0 // indirect 41 | github.com/felixge/httpsnoop v1.0.3 // indirect 42 | github.com/gabriel-vasile/mimetype v1.4.9 // indirect 43 | github.com/go-logr/logr v1.4.2 // indirect 44 | github.com/go-logr/stdr v1.2.2 // indirect 45 | github.com/gogo/protobuf v1.3.2 // indirect 46 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 47 | github.com/golang/protobuf v1.5.4 // indirect 48 | github.com/google/go-cmp v0.6.0 // indirect 49 | github.com/google/uuid v1.6.0 // indirect 50 | github.com/hashicorp/go-cleanhttp v0.5.2 // indirect 51 | github.com/hashicorp/go-retryablehttp v0.7.7 // indirect 52 | github.com/hillu/go-yara/v4 v4.3.3 // indirect 53 | github.com/klauspost/compress v1.16.7 // indirect 54 | github.com/mattn/go-colorable v0.1.13 // indirect 55 | github.com/mattn/go-isatty v0.0.20 // indirect 56 | github.com/mattn/go-runewidth v0.0.9 // indirect 57 | github.com/moby/docker-image-spec v1.3.1 // indirect 58 | github.com/moby/locker v1.0.1 // indirect 59 | github.com/moby/sys/mountinfo v0.6.2 // indirect 60 | github.com/moby/sys/sequential v0.6.0 // indirect 61 | github.com/moby/sys/signal v0.7.0 // indirect 62 | github.com/moby/sys/user v0.3.0 // indirect 63 | github.com/moby/sys/userns v0.1.0 // indirect 64 | github.com/nlepage/go-tarfs v1.2.1 // indirect 65 | github.com/opencontainers/go-digest v1.0.0 // indirect 66 | github.com/opencontainers/image-spec v1.1.0 // indirect 67 | github.com/opencontainers/runtime-spec v1.1.0 // indirect 68 | github.com/opencontainers/selinux v1.11.0 // indirect 69 | github.com/pkg/errors v0.9.1 // indirect 70 | go.opencensus.io v0.24.0 // indirect 71 | go.opentelemetry.io/auto/sdk v1.1.0 // indirect 72 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 // indirect 73 | go.opentelemetry.io/otel v1.34.0 // indirect 74 | go.opentelemetry.io/otel/metric v1.34.0 // indirect 75 | go.opentelemetry.io/otel/trace v1.34.0 // indirect 76 | golang.org/x/net v0.39.0 // indirect 77 | golang.org/x/sync v0.13.0 // indirect 78 | golang.org/x/sys v0.32.0 // indirect 79 | golang.org/x/text v0.24.0 // indirect 80 | google.golang.org/genproto v0.0.0-20231211222908-989df2bf70f3 // indirect 81 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect 82 | google.golang.org/protobuf v1.36.5 // indirect 83 | gopkg.in/yaml.v3 v3.0.1 // indirect 84 | ) 85 | -------------------------------------------------------------------------------- /core/util.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "crypto/sha1" 5 | "encoding/hex" 6 | "math" 7 | "os" 8 | "path" 9 | "path/filepath" 10 | "regexp" 11 | "strings" 12 | "time" 13 | 14 | log "github.com/sirupsen/logrus" 15 | ) 16 | 17 | // CreateRecursiveDir Create directory structure recursively, if they do not exist 18 | // @parameters 19 | // completePath - Complete path of directory which needs to be created 20 | // @returns 21 | // Error - Errors if any. Otherwise, returns nil 22 | func CreateRecursiveDir(completePath string) error { 23 | if _, err := os.Stat(completePath); os.IsNotExist(err) { 24 | log.Debugf("Folder does not exist. Creating folder... %s", completePath) 25 | err = os.MkdirAll(completePath, os.ModePerm) 26 | if err != nil { 27 | log.Errorf("createRecursiveDir %q: %s", completePath, err) 28 | } 29 | return err 30 | } else if err != nil { 31 | log.Errorf("createRecursiveDir %q: %s. Deleting temp dir", completePath, err) 32 | _ = DeleteTmpDir(completePath) 33 | return err 34 | } 35 | 36 | return nil 37 | } 38 | 39 | // Create a sanitized string from image name which can used as a filename 40 | // @parameters 41 | // imageName - Name of the container image 42 | // @returns 43 | // string - Sanitized string which can used as part of filename 44 | func getSanitizedString(imageName string) string { 45 | //nolint:gocritic 46 | reg, err := regexp.Compile("[^A-Za-z0-9]+") 47 | if err != nil { 48 | return "error" 49 | } 50 | sanitizedName := reg.ReplaceAllString(imageName, "") 51 | return sanitizedName 52 | } 53 | 54 | // GetTmpDir Create a temporrary directory to extract the conetents of container image 55 | // @parameters 56 | // imageName - Name of the container image 57 | // @returns 58 | // String - Complete path of the based directory where image will be extracted, empty string if error 59 | // Error - Errors if any. Otherwise, returns nil 60 | func GetTmpDir(imageName string) (string, error) { 61 | 62 | scanID := "df_" + getSanitizedString(imageName) 63 | 64 | dir := *session.Options.TempDirectory 65 | tempPath := filepath.Join(dir, "Deepfence", TempDirSuffix, scanID) 66 | 67 | // if runtime.GOOS == "windows" { 68 | // tempPath = dir + "\temp\Deepfence\SecretScanning\df_" + scanId 69 | //} 70 | 71 | completeTempPath := path.Join(tempPath, ExtractedImageFilesDir) 72 | 73 | err := CreateRecursiveDir(completeTempPath) 74 | if err != nil { 75 | log.Errorf("getTmpDir: Could not create temp dir %s", err) 76 | return "", err 77 | } 78 | 79 | return tempPath, err 80 | } 81 | 82 | // DeleteTmpDir Delete the temporary directory 83 | // @parameters 84 | // outputDir - Directory which need to be deleted 85 | // @returns 86 | // Error - Errors if any. Otherwise, returns nil 87 | func DeleteTmpDir(outputDir string) error { 88 | log.Infof("Deleting temporary dir %s", outputDir) 89 | // Output dir will be empty string in case of error, don't delete 90 | if outputDir != "" { 91 | // deleteFiles(outputDir+"/", "*") 92 | err := os.RemoveAll(outputDir) 93 | if err != nil { 94 | log.Errorf("deleteTmpDir: Could not delete temp dir: %s", err) 95 | return err 96 | } 97 | } 98 | return nil 99 | } 100 | 101 | // DeleteFiles Delete all the files and dirs recursively in specified directory 102 | // @parameters 103 | // path - Directory whose contents need to be deleted 104 | // wildcard - patterns to match the filenames (e.g. '*') 105 | func DeleteFiles(path string, wildCard string) { 106 | var val string 107 | files, _ := filepath.Glob(path + wildCard) 108 | for _, val = range files { 109 | os.RemoveAll(val) 110 | } 111 | } 112 | 113 | // IsSymLink Check if input is a symLink, not normal file/dir 114 | // path - Pathname which needs to be checked for symbolic link 115 | // @returns 116 | // bool - Return true if input is a symLink 117 | func IsSymLink(path string) bool { 118 | // can handle symbolic link, but will no follow the link 119 | fileInfo, err := os.Lstat(path) 120 | 121 | if err != nil { 122 | // panic(err) 123 | return false 124 | } 125 | 126 | // --- check if file is a symlink 127 | if fileInfo.Mode()&os.ModeSymlink == os.ModeSymlink { 128 | // fmt.Println("File is a symbolic link") 129 | return true 130 | } 131 | 132 | return false 133 | } 134 | 135 | func PathExists(path string) bool { 136 | _, err := os.Stat(path) 137 | if err == nil { 138 | return true 139 | } 140 | 141 | if os.IsNotExist(err) { 142 | return false 143 | } 144 | 145 | return false 146 | } 147 | 148 | func LogIfError(text string, err error) { 149 | if err != nil { 150 | log.Errorf("%s (%s", text, err.Error()) 151 | } 152 | } 153 | 154 | func GetHash(s string) string { 155 | h := sha1.New() 156 | h.Write([]byte(s)) 157 | 158 | return hex.EncodeToString(h.Sum(nil)) 159 | } 160 | 161 | func Pluralize(count int, singular string, plural string) string { 162 | if count == 1 { 163 | return singular 164 | } 165 | 166 | return plural 167 | } 168 | 169 | func GetEntropy(data string) (entropy float64) { 170 | if data == "" { 171 | return 0 172 | } 173 | 174 | for i := 0; i < 256; i++ { 175 | px := float64(strings.Count(data, string(byte(i)))) / float64(len(data)) 176 | if px > 0 { 177 | entropy += -px * math.Log2(px) 178 | } 179 | } 180 | 181 | return entropy 182 | } 183 | 184 | func GetTimestamp() int64 { 185 | return time.Now().UTC().UnixNano() / 1000000 186 | } 187 | 188 | func GetCurrentTime() string { 189 | return time.Now().UTC().Format("2006-01-02T15:04:05.000") + "Z" 190 | } 191 | -------------------------------------------------------------------------------- /core/options.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "flag" 5 | "os" 6 | "strings" 7 | 8 | "github.com/deepfence/YaraHunter/utils" 9 | ) 10 | 11 | const ( 12 | TempDirSuffix = "SecretScanning" 13 | ExtractedImageFilesDir = "ExtractedFiles" 14 | JSONOutput = "json" 15 | TableOutput = "table" 16 | ) 17 | 18 | var ( 19 | product string = utils.GetEnvOrDefault("DEEPFENCE_PRODUCT", "ThreatMapper") 20 | license string = utils.GetEnvOrDefault("DEEPFENCE_LICENSE", "") 21 | ) 22 | 23 | type Options struct { 24 | Threads *int 25 | Debug *bool 26 | MaximumFileSize *uint 27 | TempDirectory *string 28 | Local *string 29 | HostMountPath *string 30 | ConfigPath *string 31 | RulesPath *string 32 | RulesListingURL *string 33 | FailOnCompileWarning *bool 34 | EnableUpdater *bool 35 | MergeConfigs *bool 36 | ImageName *string 37 | MultipleMatch *bool 38 | MaxMultiMatch *uint 39 | MaxSecrets *uint 40 | ContainerID *string 41 | ContainerNS *string 42 | WorkersPerScan *int 43 | InactiveThreshold *int 44 | OutFormat *string 45 | ConsoleURL *string 46 | ConsolePort *int 47 | DeepfenceKey *string 48 | FailOnCount *int 49 | FailOnHighCount *int 50 | FailOnMediumCount *int 51 | FailOnLowCount *int 52 | Product *string 53 | License *string 54 | } 55 | 56 | type repeatableStringValue struct { 57 | values []string 58 | } 59 | 60 | func (v *repeatableStringValue) String() string { 61 | return strings.Join(v.values, ", ") 62 | } 63 | 64 | func (v *repeatableStringValue) Set(s string) error { 65 | v.values = append(v.values, s) 66 | return nil 67 | } 68 | 69 | func (v *repeatableStringValue) Values() []string { 70 | return v.values 71 | } 72 | 73 | func ParseOptions() (*Options, error) { 74 | options := &Options{ 75 | Threads: flag.Int("threads", 0, "Number of concurrent threads (default number of logical CPUs)"), 76 | Debug: flag.Bool("debug", false, "enable debug logs"), 77 | MaximumFileSize: flag.Uint("maximum-file-size", 256, "Maximum file size to process in KB"), 78 | TempDirectory: flag.String("temp-directory", os.TempDir(), "Directory to process and store repositories/matches"), 79 | Local: flag.String("local", "", "Specify local directory (absolute path) which to scan. Scans only given directory recursively."), 80 | HostMountPath: flag.String("host-mount-path", "", "If scanning the host, specify the host mount path for path exclusions to work correctly."), 81 | ConfigPath: flag.String("config-path", "", "yaml config path"), 82 | RulesPath: flag.String("rules-path", "/home/deepfence/usr", "yara rules path"), 83 | RulesListingURL: flag.String("rules-listing-url", "", "yara rules listing url"), 84 | FailOnCompileWarning: flag.Bool("fail-warning", false, "fail if compilation warning"), 85 | EnableUpdater: flag.Bool("enable-updated", false, "Enable rule updater"), 86 | MergeConfigs: flag.Bool("merge-configs", false, "Merge config files specified by --config-path into the default config"), 87 | ImageName: flag.String("image-name", "", "Name of the image along with tag to scan for secrets"), 88 | MultipleMatch: flag.Bool("multi-match", false, "Output multiple matches of same pattern in one file. By default, only one match of a pattern is output for a file for better performance"), 89 | MaxMultiMatch: flag.Uint("max-multi-match", 3, "Maximum number of matches of same pattern in one file. This is used only when multi-match option is enabled."), 90 | MaxSecrets: flag.Uint("max-secrets", 1000, "Maximum number of secrets to find in one container image or file system."), 91 | ContainerID: flag.String("container-id", "", "Id of existing container ID"), 92 | ContainerNS: flag.String("container-ns", "", "Namespace of existing container to scan, empty for docker runtime"), 93 | WorkersPerScan: flag.Int("workers-per-scan", 1, "Number of concurrent workers per scan"), 94 | InactiveThreshold: flag.Int("inactive-threshold", 600, "Threshold for Inactive scan in seconds"), 95 | OutFormat: flag.String("output", TableOutput, "Output format: json or table"), 96 | ConsoleURL: flag.String("console-url", "", "Deepfence Management Console URL"), 97 | ConsolePort: flag.Int("console-port", 443, "Deepfence Management Console Port"), 98 | DeepfenceKey: flag.String("deepfence-key", "", "Deepfence key for auth"), 99 | FailOnCount: flag.Int("fail-on-count", -1, "Exit with status 1 if number of secrets found is >= this value (Default: -1)"), 100 | FailOnHighCount: flag.Int("fail-on-high-count", -1, "Exit with status 1 if number of high secrets found is >= this value (Default: -1)"), 101 | FailOnMediumCount: flag.Int("fail-on-medium-count", -1, "Exit with status 1 if number of medium secrets found is >= this value (Default: -1)"), 102 | FailOnLowCount: flag.Int("fail-on-low-count", -1, "Exit with status 1 if number of low secrets found is >= this value (Default: -1)"), 103 | Product: flag.String("product", product, "Deepfence Product type can be ThreatMapper or ThreatStryker, also supports env var DEEPFENCE_PRODUCT"), 104 | License: flag.String("license", license, "TheratMapper or ThreatStryker license, also supports env var DEEPFENCE_LICENSE"), 105 | } 106 | flag.Parse() 107 | return options, nil 108 | } 109 | -------------------------------------------------------------------------------- /docs/static/css/deepfence.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Any CSS included here will be global. The classic template 3 | * bundles Infima by default. Infima is a CSS framework designed to 4 | * work well for content-centric websites. 5 | */ 6 | 7 | 8 | /* fonts */ 9 | @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+Display'); 10 | 11 | :root { 12 | --ifm-font-family-base: -apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"; 13 | --ifm-font-size-base: 16px; 14 | --ifm-heading-font-weight: 300; 15 | --ifm-navbar-shadow: 0px 2px 6px rgba(0,0,0,0.2); 16 | --ifm-breadcrumb-border-radius: 0; 17 | --ifm-breadcrumb-item-background-active: none; 18 | 19 | /* --ifm-h1-font-size: 2.0rem; 20 | --ifm-h2-font-size: 1.0rem; 21 | --ifm-h3-font-size: 1.0rem; 22 | --ifm-h4-font-size: 1.0rem;*/ 23 | 24 | } 25 | 26 | 27 | article header h1 { 28 | /* font-size: 5rem !important;*/ 29 | font-family: "Noto Sans Display" !important; 30 | } 31 | article header h2 { 32 | /* font-size: 5rem !important;*/ 33 | font-family: "Noto Sans Display" !important; 34 | font-weight: bold !important; 35 | } 36 | article header h3 { 37 | /* font-size: 1.3rem !important;*/ 38 | font-family: "Noto Sans Display" !important; 39 | } 40 | article header h4 { 41 | /* font-size: 1.1rem !important;*/ 42 | font-family: "Noto Sans Display" !important; 43 | } 44 | 45 | 46 | /* not sure why this is necessary */ 47 | pre code { 48 | background-color: var(--ifm-code-background) !important; 49 | } 50 | 51 | /* Navbar dropdown - for titles */ 52 | 53 | .nav-dropdown-title { 54 | font-size: 0.9rem !important; 55 | border-bottom: 1px solid var(--ifm-color-button); 56 | padding-top: 8px; 57 | padding-left: 8px; 58 | } 59 | 60 | 61 | 62 | /* hide the toc */ 63 | .theme-doc-toc-desktop { 64 | display: none; 65 | } 66 | .table-of-contents { 67 | display: none; 68 | } 69 | 70 | .theme-doc-sidebar-container { 71 | background-color: rgba(127,127,127,0.1); 72 | font-family: "Noto Sans Display" !important; 73 | font-size: 0.9rem !important; 74 | } 75 | 76 | .sidebar-title { 77 | font-size: 1rem !important; 78 | padding: var(--ifm-menu-link-padding-vertical) var(--ifm-menu-link-padding-horizontal); 79 | border-bottom: 1px solid var(--ifm-color-primary-darkest); 80 | } 81 | 82 | 83 | 84 | .deepfence-button { 85 | background-color: var(--ifm-color-button); 86 | border: 1px solid var(--ifm-color-button); 87 | color: var(--ifm-color-button-text); 88 | padding: 7px 16px; 89 | margin: 4px 16px; 90 | text-align: center; 91 | display: inline-block; 92 | font-size: 16px; 93 | border-radius: 6px; 94 | transition-duration: 0.4s; 95 | box-shadow: rgba(0, 0, 0, 0.1) 0px 1px 2px 0px 96 | } 97 | 98 | .deepfence-button:hover { 99 | background-color: var(--ifm-color-button-hover); 100 | color: var(--ifm-color-button-text-hover); 101 | } 102 | 103 | 104 | /* Pagination Links at footer of page */ 105 | 106 | .pagination-nav__link { 107 | background-color: var(--ifm-color-button); 108 | border: 1px solid var(--ifm-color-button); 109 | color: var(--ifm-color-button-text); 110 | padding: 7px 16px; 111 | margin: 4px 16px; 112 | display: inline-block; 113 | font-size: 16px; 114 | border-radius: 6px; 115 | transition-duration: 0.4s; 116 | box-shadow: rgba(0, 0, 0, 0.1) 0px 1px 2px 0px 117 | } 118 | .pagination-nav__sublabel { 119 | display: none; 120 | } 121 | .pagination-nav__label { 122 | padding-top: 7px; 123 | } 124 | .pagination-nav__link:hover { 125 | background-color: var(--ifm-color-button-hover); 126 | color: var(--ifm-color-button-text-hover); 127 | } 128 | 129 | /* Informational cards */ 130 | 131 | .card { 132 | background-color: rgba(127,127,127,0.1); 133 | border-radius: 6px; 134 | transition-duration: 0.4s; 135 | box-shadow: rgba(0, 0, 0, 0.1) 0px 1px 2px 0px !important; 136 | } 137 | 138 | 139 | /* You can override the default Infima variables here. */ 140 | :root { 141 | --ifm-color-primary: #1d3371; 142 | --ifm-color-primary-dark: #1a2e66; 143 | --ifm-color-primary-darker: #192b60; 144 | --ifm-color-primary-darkest: #14244f; 145 | --ifm-color-primary-light: #20387c; 146 | --ifm-color-primary-lighter: #213b82; 147 | --ifm-color-primary-lightest: #264293; 148 | --ifm-code-font-size: 90%; /*95%;*/ 149 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1); 150 | 151 | 152 | /* var(--ifm-alert-border-color) - light and dark */ 153 | --ifm-color-button: rgb(76, 179, 212); 154 | 155 | /* var(--ifm-alert-background-color) - light */ 156 | --ifm-color-button-text: rgb(238, 249, 253); 157 | --ifm-color-button-hover: rgb(238, 249, 253); 158 | 159 | /* var(--ifm-alert-background-color) - dark */ 160 | --ifm-color-button-text-hover: rgb(25, 60, 71); 161 | 162 | 163 | 164 | } 165 | 166 | /* For readability concerns, you should choose a lighter palette in dark mode. */ 167 | [data-theme='dark'] { 168 | --ifm-color-primary: #16c7f5; 169 | --ifm-color-primary-dark: #0ab9e6; 170 | --ifm-color-primary-darker: #09afda; 171 | --ifm-color-primary-darkest: #0890b3; 172 | --ifm-color-primary-light: #30cdf6; 173 | --ifm-color-primary-lighter: #3cd0f7; 174 | --ifm-color-primary-lightest: #63d9f8; 175 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3); 176 | 177 | /* var(--ifm-alert-border-color) - light and dark */ 178 | --ifm-color-button: rgb(76, 179, 212); 179 | 180 | /* var(--ifm-alert-background-color) - dark */ 181 | --ifm-color-button-text: rgb(25, 60, 71); 182 | --ifm-color-button-hover: rgb(25, 60, 71); 183 | 184 | /* var(--ifm-alert-background-color) - light */ 185 | --ifm-color-button-text-hover: rgb(238, 249, 253); 186 | } -------------------------------------------------------------------------------- /docs/static/img/deepfence-logo-black.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 28 | 29 | 30 | 31 | 32 | 33 | 36 | 37 | 38 | 39 | 44 | 49 | 54 | 59 | 62 | 67 | 69 | 73 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /docs/static/img/deepfence-logo-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 28 | 29 | 30 | 31 | 32 | 33 | 36 | 37 | 38 | 39 | 44 | 49 | 54 | 59 | 62 | 67 | 69 | 73 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SecretScanner 2 | 3 | [![Documentation](https://img.shields.io/badge/documentation-read-green)](https://community.deepfence.io/docs/secretscanner/) 4 | [![GitHub license](https://img.shields.io/github/license/deepfence/SecretScanner)](https://github.com/deepfence/SecretScanner/blob/master/LICENSE) 5 | [![GitHub stars](https://img.shields.io/github/stars/deepfence/SecretScanner)](https://github.com/deepfence/SecretScanner/stargazers) 6 | [![Hacktoberfest](https://img.shields.io/github/hacktoberfest/2022/deepfence/SecretScanner)](https://github.com/deepfence/SecretScanner/issues) 7 | [![GitHub issues](https://img.shields.io/github/issues/deepfence/SecretScanner)](https://github.com/deepfence/SecretScanner/issues) 8 | [![Slack](https://img.shields.io/badge/slack-@deepfence-blue.svg?logo=slack)](https://join.slack.com/t/deepfence-community/shared_invite/zt-podmzle9-5X~qYx8wMaLt9bGWwkSdgQ) 9 | [![Twitter](https://img.shields.io/twitter/url?style=social&url=https%3A%2F%2Fgithub.com%2Fdeepfence%2FSecretScanner)](https://twitter.com/intent/tweet?text=Check%20this%20out%3A&url=https%3A%2F%2Fgithub.com%2Fdeepfence%2FSecretScanner) 10 | 11 | # SecretScanner 12 | 13 | Deepfence SecretScanner can find unprotected secrets in container images or file systems. 14 | 15 | * SecretScanner is a standalone tool that retrieves and searches container and host filesystems, matching the contents against a database of approximately 140 secret types. 16 | * SecretScanner is also included in [ThreatMapper](https://github.com/deepfence/ThreatMapper), an open source scanner that identifies vulnerable dependencies and unprotected secrets in cloud native applications, and ranks these vulnerabilities based on their risk-of-exploit ([example](https://github.com/deepfence/ThreatMapper/wiki/Scanning-Production-Deployments)) 17 | 18 | ## What are Secrets? 19 | 20 | Secrets are any kind of sensitive or private data which gives authorized users permission to access critical IT infrastructure (such as accounts, devices, network, cloud based services), applications, storage, databases and other kinds of critical data for an organization. For example, passwords, AWS access IDs, AWS secret access keys, Google OAuth Key etc. are secrets. Secrets should be strictly kept private. However, sometimes attackers can easily access secrets due to flawed security policies or inadvertent mistakes by developers. Sometimes developers use default secrets or leave hard-coded secrets such as passwords, API keys, encryption keys, SSH keys, tokens etc. in container images, especially during rapid development and deployment cycles in CI/CD pipeline. Also, sometimes users store passwords in plain text. Leakage of secrets to unauthorized entities can put your organization and infrastructure at serious security risk. 21 | 22 | Deepfence SecretScanner helps users scan their container images or local directories on hosts and outputs a JSON file with details of all the secrets found. 23 | 24 | Check out our [blog](https://medium.com/deepfence-cloud-native-security/detecting-secrets-to-reduce-attack-surface-3405ee6329b5) for more details. 25 | 26 | ## When to use SecretScanner 27 | 28 | Use SecretScanner if you need a lightweight, efficient method to scan container images and filesystems for possible secrets (keys, tokens, passwords). You can then review these possible 'secrets' to determine if any of them should be removed from production deployments. 29 | 30 | ## Quick Start 31 | 32 | For full instructions, refer to the [SecretScanner Documentation](https://community.deepfence.io/docs/secretscanner/). 33 | 34 | ![SecretScanner QuickStart](docs/docs/secretscanner/img/secretscanner.svg) 35 | 36 | Install docker and run SecretScanner on a container image using the following instructions: 37 | 38 | * Build SecretScanner: 39 | ```shell 40 | ./bootstrap.sh 41 | docker build --rm=true --tag=quay.io/deepfenceio/deepfence_secret_scanner_ce:2.5.7 -f Dockerfile . 42 | ``` 43 | 44 | * Or, pull the latest build from docker hub by doing: 45 | ```shell 46 | docker pull quay.io/deepfenceio/deepfence_secret_scanner_ce:2.5.7 47 | ``` 48 | 49 | ### Generate License Key 50 | 51 | Run this command to generate a license key. Work/official email id has to be used. 52 | ```shell 53 | curl https://license.deepfence.io/threatmapper/generate-license?first_name=&last_name=&email=&company=&resend_email=true 54 | ``` 55 | 56 | ### Scan 57 | 58 | * Pull a container image for scanning: 59 | ```shell 60 | docker pull node:8.11 61 | ``` 62 | 63 | * Set Product and Licence and scan it:: 64 | ```shell 65 | docker run -i --rm --name=deepfence-secretscanner \ 66 | -e DEEPFENCE_PRODUCT= \ 67 | -e DEEPFENCE_LICENSE= \ 68 | -v /var/run/docker.sock:/var/run/docker.sock \ 69 | quay.io/deepfenceio/deepfence_secret_scanner_ce:2.5.7 \ 70 | --image-name node:8.11 \ 71 | --output json > node.json 72 | ``` 73 | 74 | Rules can also be cached to use next run by mounting a seperate path and passing `rules-path` argument 75 | ```shell 76 | docker run -i --rm --name=deepfence-yarahunter \ 77 | -e DEEPFENCE_PRODUCT= \ 78 | -e DEEPFENCE_LICENSE= \ 79 | -v /var/run/docker.sock:/var/run/docker.sock \ 80 | -v /tmp/rules:/tmp/rules \ 81 | quay.io/deepfenceio/deepfence_secret_scanner_ce:2.5.7 \ 82 | --image-name node:8.11 \ 83 | --rules-path=/tmp/rules \ 84 | --output json > node.json 85 | ``` 86 | 87 | # Credits 88 | 89 | We have built upon the configuration file from [shhgit](https://github.com/eth0izzle/shhgit) project. 90 | 91 | ## Get in touch 92 | 93 | Thank you for using SecretScanner. 94 | 95 | * [](https://community.deepfence.io/docs/secretscanner/) Start with the documentation 96 | * [](https://join.slack.com/t/deepfence-community/shared_invite/zt-podmzle9-5X~qYx8wMaLt9bGWwkSdgQ) Got a question, need some help? Find the Deepfence team on Slack 97 | * [![GitHub issues](https://img.shields.io/github/issues/deepfence/SecretScanner)](https://github.com/deepfence/SecretScanner/issues) Got a feature request or found a bug? Raise an issue 98 | * [productsecurity *at* deepfence *dot* io](SECURITY.md): Found a security issue? Share it in confidence 99 | * Find out more at [deepfence.io](https://deepfence.io/) 100 | 101 | ## Security and Support 102 | 103 | For any security-related issues in the SecretScanner project, contact [productsecurity *at* deepfence *dot* io](SECURITY.md). 104 | 105 | Please file GitHub issues as needed, and join the Deepfence Community [Slack channel](https://join.slack.com/t/deepfence-community/shared_invite/zt-podmzle9-5X~qYx8wMaLt9bGWwkSdgQ). 106 | 107 | 108 | # Disclaimer 109 | 110 | This tool is not meant to be used for hacking. Please use it only for legitimate purposes like detecting secrets on the infrastructure you own, not on others' infrastructure. DEEPFENCE shall not be liable for loss of profit, loss of business, other financial loss, or any other loss or damage which may be caused, directly or indirectly, by the inadequacy of SecretScanner for any purpose or use thereof or by any defect or deficiency therein. 111 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // ------------------------------------------------------------------------------ 4 | // MIT License 5 | 6 | // Copyright (c) 2020 deepfence 7 | 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | 15 | // The above copyright notice and this permission notice shall be included in all 16 | // copies or substantial portions of the Software. 17 | 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | // SOFTWARE. 25 | // ------------------------------------------------------------------------------ 26 | 27 | import ( 28 | "archive/tar" 29 | "context" 30 | "encoding/json" 31 | "flag" 32 | "io" 33 | "io/fs" 34 | "os" 35 | "os/signal" 36 | "path" 37 | "path/filepath" 38 | "runtime" 39 | "strconv" 40 | 41 | "github.com/deepfence/SecretScanner/core" 42 | "github.com/deepfence/SecretScanner/jobs" 43 | "github.com/deepfence/SecretScanner/output" 44 | "github.com/deepfence/SecretScanner/server" 45 | log "github.com/sirupsen/logrus" 46 | 47 | "google.golang.org/grpc" 48 | 49 | out "github.com/deepfence/YaraHunter/pkg/output" 50 | "github.com/deepfence/YaraHunter/pkg/runner" 51 | yaraserver "github.com/deepfence/YaraHunter/pkg/server" 52 | "github.com/deepfence/YaraHunter/pkg/threatintel" 53 | 54 | pb "github.com/deepfence/agent-plugins-grpc/srcgo" 55 | ) 56 | 57 | const ( 58 | PLUGIN_NAME = "SecretScanner" 59 | ) 60 | 61 | var ( 62 | socketPath = flag.String("socket-path", "", "The gRPC server unix socket path") 63 | version string 64 | checksumFile = "checksum.txt" 65 | sourceRuleFile = "df-secret.json" 66 | secretRuleFile = "secret.yar" 67 | ) 68 | 69 | // Read the regex signatures from config file, options etc. 70 | // and setup the session to start scanning for secrets 71 | var session = core.GetSession() 72 | 73 | type SecretsWriter interface { 74 | WriteJSON() error 75 | WriteTable() error 76 | GetSecrets() []output.SecretFound 77 | AddSecret(output.SecretFound) 78 | } 79 | 80 | func main() { 81 | log.SetOutput(os.Stderr) 82 | log.SetLevel(log.InfoLevel) 83 | log.SetReportCaller(true) 84 | log.SetFormatter(&log.TextFormatter{ 85 | DisableColors: false, 86 | ForceColors: true, 87 | FullTimestamp: true, 88 | CallerPrettyfier: func(f *runtime.Frame) (string, string) { 89 | return "", " " + path.Base(f.File) + ":" + strconv.Itoa(f.Line) 90 | }, 91 | }) 92 | 93 | log.Infof("version: %s", version) 94 | 95 | flag.Parse() 96 | 97 | if *core.GetSession().Options.Debug { 98 | log.SetLevel(log.DebugLevel) 99 | } 100 | 101 | ctx, _ := signal.NotifyContext(context.Background(), os.Interrupt) 102 | 103 | out.ScanStatusFilename = jobs.GetDfInstallDir() + "/var/log/fenced/secret-scan-log/secret_scan_log.log" 104 | out.ScanFilename = jobs.GetDfInstallDir() + "/var/log/fenced/secret-scan/secret_scan.log" 105 | 106 | runnerOpts := runner.RunnerOptions{ 107 | SocketPath: *socketPath, 108 | RulesPath: *core.GetSession().Options.RulesPath, 109 | RulesListingURL: *core.GetSession().Options.RulesListingURL, 110 | HostMountPath: *core.GetSession().Options.HostMountPath, 111 | FailOnCompileWarning: *core.GetSession().Options.FailOnCompileWarning, 112 | Local: *core.GetSession().Options.Local, 113 | ImageName: *core.GetSession().Options.ImageName, 114 | ContainerID: *core.GetSession().Options.ContainerID, 115 | ConsoleURL: *core.GetSession().Options.ConsoleURL, 116 | ConsolePort: *core.GetSession().Options.ConsolePort, 117 | DeepfenceKey: *core.GetSession().Options.DeepfenceKey, 118 | OutFormat: *core.GetSession().Options.OutFormat, 119 | FailOnHighCount: *core.GetSession().Options.FailOnHighCount, 120 | FailOnMediumCount: *core.GetSession().Options.FailOnMediumCount, 121 | FailOnLowCount: *core.GetSession().Options.FailOnLowCount, 122 | FailOnCount: *core.GetSession().Options.FailOnCount, 123 | InactiveThreshold: *core.GetSession().Options.InactiveThreshold, 124 | } 125 | 126 | if *core.GetSession().Options.EnableUpdater { 127 | go runner.ScheduleYaraHunterUpdater(ctx, runnerOpts) 128 | } 129 | 130 | // update rules required for cli mode 131 | if *socketPath == "" { 132 | updateRules(ctx, core.GetSession().Options) 133 | } 134 | 135 | runner.StartYaraHunter(ctx, runnerOpts, core.GetSession().ExtractorConfig, 136 | 137 | func(base *yaraserver.GRPCScannerServer) server.SecretGRPCServer { 138 | return server.SecretGRPCServer{ 139 | GRPCScannerServer: base, 140 | UnimplementedSecretScannerServer: pb.UnimplementedSecretScannerServer{}, 141 | } 142 | }, 143 | func(s grpc.ServiceRegistrar, impl any) { 144 | pb.RegisterSecretScannerServer(s, impl.(pb.SecretScannerServer)) 145 | }) 146 | } 147 | 148 | func updateRules(ctx context.Context, opts *core.Options) { 149 | log.Infof("check and update secret rules") 150 | 151 | listing, err := threatintel.FetchThreatIntelListing(ctx, version, *opts.Product, *opts.License) 152 | if err != nil { 153 | log.Fatal(err) 154 | } 155 | 156 | rulesInfo, err := listing.GetLatest(version, threatintel.SecretDBType) 157 | if err != nil { 158 | log.Fatal(err) 159 | } 160 | log.Debugf("rulesInfo: %+v", rulesInfo) 161 | 162 | // make sure output rules directory exists 163 | os.MkdirAll(*opts.RulesPath, fs.ModePerm) 164 | 165 | // check if update required 166 | if threatintel.SkipRulesUpdate(filepath.Join(*opts.RulesPath, checksumFile), rulesInfo.Checksum) { 167 | log.Info("skip rules update") 168 | return 169 | } 170 | 171 | log.Info("download new rules") 172 | content, err := threatintel.DownloadFile(ctx, rulesInfo.URL) 173 | if err != nil { 174 | log.Fatal(err) 175 | } 176 | 177 | log.Infof("rules file size: %d bytes", content.Len()) 178 | 179 | // write new checksum 180 | if err := os.WriteFile( 181 | filepath.Join(*opts.RulesPath, checksumFile), []byte(rulesInfo.Checksum), fs.ModePerm); err != nil { 182 | log.Fatal(err) 183 | } 184 | 185 | // write rules file 186 | outRuleFile := filepath.Join(*opts.RulesPath, secretRuleFile) 187 | threatintel.ProcessTarGz(content.Bytes(), sourceRuleFile, outRuleFile, processSecretRules) 188 | } 189 | 190 | func processSecretRules(header *tar.Header, reader io.Reader, outPath string) error { 191 | 192 | var fb threatintel.FeedsBundle 193 | if err := json.NewDecoder(reader).Decode(&fb); err != nil { 194 | log.Error(err) 195 | return err 196 | } 197 | 198 | if err := threatintel.ExportYaraRules(outPath, fb.ScannerFeeds.SecretRules, fb.Extra); err != nil { 199 | log.Error(err) 200 | return err 201 | } 202 | 203 | return nil 204 | } 205 | -------------------------------------------------------------------------------- /output/publish-to-console.go: -------------------------------------------------------------------------------- 1 | package output 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "crypto/tls" 7 | "crypto/x509" 8 | "errors" 9 | "fmt" 10 | "net" 11 | "net/http" 12 | "os" 13 | "strings" 14 | "time" 15 | 16 | dsc "github.com/deepfence/golang_deepfence_sdk/client" 17 | oahttp "github.com/deepfence/golang_deepfence_sdk/utils/http" 18 | log "github.com/sirupsen/logrus" 19 | ) 20 | 21 | var ( 22 | MgmtConsoleURL string 23 | DeepfenceKey string 24 | ) 25 | 26 | func init() { 27 | MgmtConsoleURL = os.Getenv("MGMT_CONSOLE_URL") 28 | mgmtConsolePort := os.Getenv("MGMT_CONSOLE_PORT") 29 | if mgmtConsolePort != "" && mgmtConsolePort != "443" { 30 | MgmtConsoleURL += ":" + mgmtConsolePort 31 | } 32 | DeepfenceKey = os.Getenv("DEEPFENCE_KEY") 33 | } 34 | 35 | func IngestSecretScanResults(secretScanMsg string, index string) error { 36 | secretScanMsg = strings.ReplaceAll(secretScanMsg, "\n", " ") 37 | postReader := bytes.NewReader([]byte(secretScanMsg)) 38 | retryCount := 0 39 | httpClient, err := buildClient() 40 | if err != nil { 41 | log.Errorf("Error building http client " + err.Error()) 42 | return err 43 | } 44 | for { 45 | httpReq, err := http.NewRequest("POST", "https://"+MgmtConsoleURL+"/ingest/topics/"+index, postReader) 46 | if err != nil { 47 | return err 48 | } 49 | httpReq.Close = true 50 | httpReq.Header.Add("deepfence-key", DeepfenceKey) 51 | httpReq.Header.Add("Content-Type", "application/vnd.kafka.json.v2+json") 52 | resp, err := httpClient.Do(httpReq) 53 | if err != nil { 54 | return err 55 | } 56 | if resp.StatusCode == 200 { 57 | resp.Body.Close() 58 | break 59 | } else { 60 | if retryCount > 5 { 61 | errMsg := fmt.Sprintf("Unable to complete request. Got %d ", resp.StatusCode) 62 | resp.Body.Close() 63 | return errors.New(errMsg) 64 | } 65 | resp.Body.Close() 66 | retryCount += 1 67 | time.Sleep(5 * time.Second) 68 | } 69 | } 70 | return nil 71 | } 72 | 73 | func buildClient() (*http.Client, error) { 74 | // Set up our own certificate pool 75 | tlsConfig := &tls.Config{RootCAs: x509.NewCertPool(), InsecureSkipVerify: true} 76 | client := &http.Client{ 77 | Transport: &http.Transport{ 78 | Proxy: http.ProxyFromEnvironment, 79 | TLSClientConfig: tlsConfig, 80 | DisableKeepAlives: false, 81 | MaxIdleConnsPerHost: 1024, 82 | DialContext: (&net.Dialer{ 83 | Timeout: 15 * time.Minute, 84 | KeepAlive: 15 * time.Minute, 85 | }).DialContext, 86 | TLSHandshakeTimeout: 10 * time.Second, 87 | ResponseHeaderTimeout: 5 * time.Minute, 88 | }, 89 | Timeout: 15 * time.Minute, 90 | } 91 | return client, nil 92 | } 93 | 94 | type Publisher struct { 95 | client *oahttp.OpenapiHttpClient 96 | stopScanStatus chan bool 97 | } 98 | 99 | func GetHostname() string { 100 | name, err := os.Hostname() 101 | if err != nil { 102 | return "" 103 | } 104 | return name 105 | } 106 | 107 | func NewPublisher(url string, port string, key string) (*Publisher, error) { 108 | client := oahttp.NewHttpsConsoleClient(url, port) 109 | if err := client.APITokenAuthenticate(key); err != nil { 110 | return nil, err 111 | } 112 | return &Publisher{client: client}, nil 113 | } 114 | 115 | func (p *Publisher) SendReport(hostname, imageName, containerID, nodeType string) { 116 | 117 | report := dsc.IngestersReportIngestionData{} 118 | 119 | host := map[string]interface{}{ 120 | "node_id": hostname, 121 | "host_name": hostname, 122 | "node_name": hostname, 123 | "node_type": "host", 124 | "cloud_region": "cli", 125 | "cloud_provider": "cli", 126 | "kubernetes_cluster_id": "", 127 | } 128 | report.HostBatch = []map[string]interface{}{host} 129 | 130 | if nodeType != "" { 131 | image := map[string]interface{}{ 132 | "docker_image_name_with_tag": imageName, 133 | "docker_image_id": imageName, 134 | "node_id": imageName, 135 | "node_name": imageName, 136 | "node_type": nodeType, 137 | } 138 | s := strings.Split(imageName, ":") 139 | if len(s) == 2 { 140 | image["docker_image_name"] = s[0] 141 | image["docker_image_tag"] = s[1] 142 | } 143 | containerImageEdge := map[string]interface{}{ 144 | "source": hostname, 145 | "destinations": imageName, 146 | } 147 | report.ContainerImageBatch = []map[string]interface{}{image} 148 | report.ContainerImageEdgeBatch = []map[string]interface{}{containerImageEdge} 149 | } 150 | 151 | log.Debugf("report: %+v", report) 152 | 153 | req := p.client.Client().TopologyAPI.IngestSyncAgentReport(context.Background()) 154 | req = req.IngestersReportIngestionData(report) 155 | 156 | resp, err := p.client.Client().TopologyAPI.IngestSyncAgentReportExecute(req) 157 | if err != nil { 158 | log.Error(err) 159 | } 160 | log.Debugf("report response %s", resp.Status) 161 | } 162 | 163 | func (p *Publisher) StartScan(nodeID, nodeType string) string { 164 | 165 | scanTrigger := dsc.ModelSecretScanTriggerReq{ 166 | Filters: *dsc.NewModelScanFilterWithDefaults(), 167 | NodeIds: []dsc.ModelNodeIdentifier{}, 168 | } 169 | 170 | nodeIds := dsc.ModelNodeIdentifier{NodeId: nodeID, NodeType: nodeType} 171 | if nodeType != "" { 172 | nodeIds.NodeType = "host" 173 | } 174 | 175 | scanTrigger.NodeIds = append(scanTrigger.NodeIds, nodeIds) 176 | 177 | req := p.client.Client().SecretScanAPI.StartSecretScan(context.Background()) 178 | req = req.ModelSecretScanTriggerReq(scanTrigger) 179 | res, resp, err := p.client.Client().SecretScanAPI.StartSecretScanExecute(req) 180 | if err != nil { 181 | log.Error(err) 182 | return "" 183 | } 184 | // defer resp.Body.Close() 185 | // io.Copy(io.Discard, resp.Body) 186 | 187 | log.Debugf("start scan response: %+v", res) 188 | log.Debugf("start scan response status: %s", resp.Status) 189 | 190 | return res.GetScanIds()[0] 191 | } 192 | 193 | func (p *Publisher) PublishScanStatusMessage(scanID, message, status string) { 194 | data := dsc.IngestersSecretScanStatus{} 195 | data.SetScanId(scanID) 196 | data.SetScanStatus(status) 197 | data.SetScanMessage(message) 198 | 199 | req := p.client.Client().SecretScanAPI.IngestSecretScanStatus(context.Background()) 200 | req = req.IngestersSecretScanStatus([]dsc.IngestersSecretScanStatus{data}) 201 | 202 | resp, err := p.client.Client().SecretScanAPI.IngestSecretScanStatusExecute(req) 203 | if err != nil { 204 | log.Error(err) 205 | } 206 | 207 | log.Debugf("publish scan status response: %v", resp) 208 | } 209 | 210 | func (p *Publisher) PublishScanError(scanID, errMsg string) { 211 | p.PublishScanStatusMessage(scanID, errMsg, "ERROR") 212 | } 213 | 214 | func (p *Publisher) PublishScanStatusPeriodic(scanID, status string) { 215 | go func() { 216 | p.PublishScanStatusMessage(scanID, "", status) 217 | ticker := time.NewTicker(30 * time.Second) 218 | for { 219 | select { 220 | case <-ticker.C: 221 | p.PublishScanStatusMessage(scanID, "", status) 222 | case <-p.stopScanStatus: 223 | return 224 | } 225 | } 226 | }() 227 | } 228 | 229 | func (p *Publisher) StopPublishScanStatus() { 230 | p.stopScanStatus <- true 231 | time.Sleep(5 * time.Second) 232 | } 233 | 234 | func (p *Publisher) IngestSecretScanResults(scanID string, secrets []SecretFound) error { 235 | data := []dsc.IngestersSecret{} 236 | 237 | for _, secret := range secrets { 238 | rule := dsc.NewIngestersSecretRule() 239 | rule.SetId(int32(secret.RuleID)) 240 | rule.SetName(secret.RuleName) 241 | rule.SetPart(secret.PartToMatch) 242 | rule.SetSignatureToMatch(secret.Regex) 243 | 244 | match := dsc.NewIngestersSecretMatch() 245 | match.SetFullFilename(secret.CompleteFilename) 246 | match.SetMatchedContent(secret.MatchedContents) 247 | match.SetRelativeEndingIndex(int32(secret.MatchToByte)) 248 | match.SetRelativeStartingIndex(int32(secret.MatchFromByte)) 249 | match.SetStartingIndex(int32(secret.PrintBufferStartIndex)) 250 | 251 | severity := dsc.NewIngestersSecretSeverity() 252 | severity.SetLevel(secret.Severity) 253 | severity.SetScore(float32(secret.SeverityScore)) 254 | 255 | s := dsc.NewIngestersSecret() 256 | s.SetImageLayerId(secret.LayerID) 257 | s.SetRule(*rule) 258 | s.SetMatch(*match) 259 | s.SetSeverity(*severity) 260 | s.SetScanId(scanID) 261 | 262 | data = append(data, *s) 263 | } 264 | 265 | req := p.client.Client().SecretScanAPI.IngestSecrets(context.Background()) 266 | req = req.IngestersSecret(data) 267 | 268 | resp, err := p.client.Client().SecretScanAPI.IngestSecretsExecute(req) 269 | if err != nil { 270 | log.Error(err) 271 | } 272 | 273 | log.Debugf("publish scan results response: %v", resp) 274 | 275 | return nil 276 | } 277 | -------------------------------------------------------------------------------- /output/output.go: -------------------------------------------------------------------------------- 1 | package output 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | "time" 8 | 9 | "github.com/deepfence/YaraHunter/pkg/output" 10 | pb "github.com/deepfence/agent-plugins-grpc/srcgo" 11 | "github.com/fatih/color" 12 | tw "github.com/olekukonko/tablewriter" 13 | log "github.com/sirupsen/logrus" 14 | ) 15 | 16 | const ( 17 | Indent = " " // Indentation for Json printing 18 | ) 19 | 20 | // severity 21 | const ( 22 | HIGH = "high" 23 | MEDIUM = "medium" 24 | LOW = "low" 25 | ) 26 | 27 | type SecretFound struct { 28 | LayerID string `json:"Image Layer ID,omitempty"` 29 | RuleID int `json:"Matched Rule ID,omitempty"` 30 | RuleName string `json:"Matched Rule Name,omitempty"` 31 | PartToMatch string `json:"Matched Part,omitempty"` 32 | Match string `json:"String to Match,omitempty"` 33 | Regex string `json:"Signature to Match,omitempty"` 34 | Severity string `json:"Severity,omitempty"` 35 | SeverityScore float64 `json:"Severity Score,omitempty"` 36 | PrintBufferStartIndex int `json:"Starting Index of Match in Original Content,omitempty"` 37 | MatchFromByte int `json:"Relative Starting Index of Match in Displayed Substring"` 38 | MatchToByte int `json:"Relative Ending Index of Match in Displayed Substring"` 39 | CompleteFilename string `json:"Full File Name,omitempty"` 40 | MatchedContents string `json:"Matched Contents,omitempty"` 41 | } 42 | 43 | type JSONDirSecretsOutput struct { 44 | Timestamp time.Time 45 | DirName string `json:"Directory Name"` 46 | Secrets []SecretFound 47 | } 48 | 49 | type JSONImageSecretsOutput struct { 50 | Timestamp time.Time 51 | ImageName string `json:"Image Name"` 52 | ImageID string `json:"Image ID"` 53 | ContainerID string `json:"Container ID"` 54 | Secrets []SecretFound 55 | } 56 | 57 | func (imageOutput *JSONImageSecretsOutput) SetImageName(imageName string) { 58 | imageOutput.ImageName = imageName 59 | } 60 | 61 | func (imageOutput *JSONImageSecretsOutput) SetImageID(imageID string) { 62 | imageOutput.ImageID = imageID 63 | } 64 | 65 | func (imageOutput *JSONImageSecretsOutput) SetTime() { 66 | imageOutput.Timestamp = time.Now() 67 | } 68 | 69 | func (imageOutput *JSONImageSecretsOutput) SetSecrets(secrets []SecretFound) { 70 | imageOutput.Secrets = secrets 71 | } 72 | 73 | func (imageOutput *JSONImageSecretsOutput) GetSecrets() []SecretFound { 74 | return imageOutput.Secrets 75 | } 76 | 77 | func (imageOutput *JSONImageSecretsOutput) AddSecret(secret SecretFound) { 78 | imageOutput.Secrets = append(imageOutput.Secrets, secret) 79 | } 80 | 81 | func (imageOutput JSONImageSecretsOutput) WriteJSON() error { 82 | return printSecretsToJSON(imageOutput) 83 | 84 | } 85 | 86 | func (imageOutput JSONImageSecretsOutput) WriteTable() error { 87 | return WriteTableOutput(&imageOutput.Secrets) 88 | } 89 | 90 | func (dirOutput *JSONDirSecretsOutput) SetDirName(dirName string) { 91 | dirOutput.DirName = dirName 92 | } 93 | 94 | func (dirOutput *JSONDirSecretsOutput) SetTime() { 95 | dirOutput.Timestamp = time.Now() 96 | } 97 | 98 | func (dirOutput *JSONDirSecretsOutput) SetSecrets(secrets []SecretFound) { 99 | dirOutput.Secrets = secrets 100 | } 101 | func (dirOutput *JSONDirSecretsOutput) GetSecrets() []SecretFound { 102 | return dirOutput.Secrets 103 | } 104 | 105 | func (dirOutput JSONDirSecretsOutput) WriteJSON() error { 106 | return printSecretsToJSON(dirOutput) 107 | } 108 | 109 | func (dirOutput JSONDirSecretsOutput) WriteTable() error { 110 | return WriteTableOutput(&dirOutput.Secrets) 111 | } 112 | 113 | func (imageOutput *JSONDirSecretsOutput) AddSecret(secret SecretFound) { 114 | imageOutput.Secrets = append(imageOutput.Secrets, secret) 115 | } 116 | 117 | func printSecretsToJSON(secretsJSON interface{}) error { 118 | file, err := json.MarshalIndent(secretsJSON, "", Indent) 119 | if err != nil { 120 | log.Errorf("printSecretsToJsonFile: Couldn't format json output: %s", err) 121 | return err 122 | } 123 | 124 | fmt.Println(string(file)) 125 | 126 | return nil 127 | } 128 | 129 | func PrintColoredSecrets(secrets []SecretFound, isFirstSecret *bool) { 130 | for _, secret := range secrets { 131 | printColoredSecretJSONObject(secret, isFirstSecret) 132 | *isFirstSecret = false 133 | } 134 | } 135 | 136 | // Function to print json object with the matches secret string in color 137 | // @parameters 138 | // secret - Structure with details of the secret found 139 | // isFirstSecret - indicates if some secrets are already printed, used to properly format json 140 | func printColoredSecretJSONObject(secret SecretFound, isFirstSecret *bool) { 141 | Indent3 := Indent + Indent + Indent 142 | 143 | if *isFirstSecret { 144 | fmt.Printf(Indent + Indent + "{\n") 145 | } else { 146 | fmt.Printf(",\n" + Indent + Indent + "{\n") 147 | } 148 | 149 | fmt.Printf(Indent3+"\"Image Layer ID\": %s,\n", jsonMarshal(secret.LayerID)) 150 | fmt.Printf(Indent3+"\"Matched Rule ID\": %d,\n", secret.RuleID) 151 | fmt.Printf(Indent3+"\"Matched Rule Name\": %s,\n", jsonMarshal(secret.RuleName)) 152 | fmt.Printf(Indent3+"\"Matched Part\": %s,\n", jsonMarshal(secret.PartToMatch)) 153 | fmt.Printf(Indent3+"\"String to Match\": %s,\n", jsonMarshal(secret.Match)) 154 | fmt.Printf(Indent3+"\"Signature to Match\": %s,\n", jsonMarshal(secret.Regex)) 155 | fmt.Printf(Indent3+"\"Severity\": %s,\n", jsonMarshal(secret.Severity)) 156 | fmt.Printf(Indent3+"\"Severity Score\": %.2f,\n", secret.SeverityScore) 157 | fmt.Printf(Indent3+"\"Starting Index of Match in Original Content\": %d,\n", secret.PrintBufferStartIndex) 158 | fmt.Printf(Indent3+"\"Relative Starting Index of Match in Displayed Substring\": %d,\n", secret.MatchFromByte) 159 | fmt.Printf(Indent3+"\"Relative Ending Index of Match in Displayed Substring\": %d,\n", secret.MatchToByte) 160 | fmt.Printf(Indent3+"\"Full File Name\": %s,\n", jsonMarshal(secret.CompleteFilename)) 161 | match := secret.MatchedContents 162 | from := secret.MatchFromByte 163 | to := secret.MatchToByte 164 | prefix := removeFirstLastChar(jsonMarshal(match[0:from])) 165 | coloredMatch := color.RedString(removeFirstLastChar(jsonMarshal(string(match[from:to])))) 166 | suffix := removeFirstLastChar(jsonMarshal(match[to:])) 167 | fmt.Printf(Indent3+"\"Matched Contents\": \"%s%s%s\"\n", prefix, coloredMatch, suffix) 168 | 169 | fmt.Printf(Indent + Indent + "}") 170 | } 171 | 172 | func jsonMarshal(input string) string { 173 | output, _ := json.Marshal(input) 174 | return string(output) 175 | } 176 | 177 | func removeFirstLastChar(input string) string { 178 | if len(input) <= 1 { 179 | return input 180 | } 181 | return input[1 : len(input)-1] 182 | } 183 | 184 | func SecretToSecretInfo(out output.IOCFound, matchIndex int) *pb.SecretInfo { 185 | signature := "" 186 | if len(out.Meta) != 0 { 187 | signature = out.Meta[0] 188 | } 189 | severity := "low" 190 | if out.FileSeverity != "" { 191 | severity = out.FileSeverity 192 | } 193 | return &pb.SecretInfo{ 194 | ImageLayerId: out.LayerID, 195 | Rule: &pb.MatchRule{ 196 | Name: out.RuleName, 197 | SignatureToMatch: signature, 198 | }, 199 | Match: &pb.Match{ 200 | FullFilename: out.CompleteFilename, 201 | MatchedContent: out.Matches[matchIndex].Data, 202 | StartingIndex: int64(out.Matches[matchIndex].Offset), 203 | }, 204 | Severity: &pb.Severity{ 205 | Level: severity, 206 | Score: float32(out.SeverityScore), 207 | }, 208 | } 209 | } 210 | 211 | func WriteTableOutput(report *[]SecretFound) error { 212 | table := tw.NewWriter(os.Stdout) 213 | table.SetHeader([]string{"Matched Part", "Rule Name", "Severity", "File Name", "Signature"}) 214 | table.SetHeaderLine(true) 215 | table.SetBorder(true) 216 | table.SetAutoWrapText(true) 217 | table.SetAutoFormatHeaders(true) 218 | table.SetColMinWidth(0, 10) 219 | table.SetColMinWidth(1, 10) 220 | table.SetColMinWidth(2, 10) 221 | table.SetColMinWidth(3, 20) 222 | table.SetColMinWidth(4, 20) 223 | 224 | for _, r := range *report { 225 | table.Append([]string{r.PartToMatch, r.RuleName, r.Severity, r.CompleteFilename, r.Regex}) 226 | } 227 | table.Render() 228 | return nil 229 | } 230 | 231 | type SevCount struct { 232 | Total int 233 | High int 234 | Medium int 235 | Low int 236 | } 237 | 238 | func CountBySeverity(report []SecretFound) SevCount { 239 | detail := SevCount{} 240 | 241 | for _, r := range report { 242 | detail.Total += 1 243 | switch r.Severity { 244 | case HIGH: 245 | detail.High += 1 246 | case MEDIUM: 247 | detail.Medium += 1 248 | case LOW: 249 | detail.Low += 1 250 | } 251 | } 252 | 253 | return detail 254 | } 255 | 256 | func ExitOnSeverity(severity string, count int, failOnCount int) { 257 | log.Debugf("ExitOnSeverity severity=%s count=%d failOnCount=%d", 258 | severity, count, failOnCount) 259 | if count >= failOnCount { 260 | if len(severity) > 0 { 261 | msg := "Exit secret scan. Number of %s secrets (%d) reached/exceeded the limit (%d).\n" 262 | fmt.Printf(msg, severity, count, failOnCount) 263 | os.Exit(1) 264 | } 265 | msg := "Exit secret scan. Number of secrets (%d) reached/exceeded the limit (%d).\n" 266 | fmt.Printf(msg, count, failOnCount) 267 | os.Exit(1) 268 | } 269 | } 270 | 271 | func FailOn(details SevCount, failOnHighCount int, failOnMediumCount int, failOnLowCount int, failOnCount int) { 272 | if failOnHighCount > 0 { 273 | ExitOnSeverity(HIGH, details.High, failOnHighCount) 274 | } 275 | if failOnMediumCount > 0 { 276 | ExitOnSeverity(MEDIUM, details.Medium, failOnMediumCount) 277 | } 278 | if failOnLowCount > 0 { 279 | ExitOnSeverity(LOW, details.Low, failOnLowCount) 280 | } 281 | if failOnCount > 0 { 282 | ExitOnSeverity("", details.Total, failOnCount) 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /signature/signatures.go: -------------------------------------------------------------------------------- 1 | package signature 2 | 3 | import ( 4 | // "regexp" 5 | // "regexp/syntax" 6 | // "strings" 7 | "bufio" 8 | "io" 9 | "math" 10 | "regexp" 11 | "strings" 12 | 13 | "github.com/deepfence/SecretScanner/core" 14 | "github.com/deepfence/SecretScanner/output" 15 | "github.com/sirupsen/logrus" 16 | log "github.com/sirupsen/logrus" 17 | ) 18 | 19 | // Constants representing different parts to be matched 20 | // and constants for matching complex regex patterns 21 | const ( 22 | ExtPart = "extension" 23 | FilenamePart = "filename" 24 | PathPart = "path" 25 | ContentsPart = "contents" 26 | LargeRegexType = "large" 27 | MaxSecretLength = 1000 // Maximum length of secret to search to find exact position of secrets in large regex patterns 28 | ) 29 | 30 | // Different map data structures to map to appropriate signatures, DBs etc. 31 | var ( 32 | matchRegexpMap map[string]*regexp.Regexp 33 | patternSignatureMap map[string][]core.ConfigSignature 34 | signatureIDMap map[int]core.ConfigSignature 35 | matchSignatureMap map[string]map[string]core.ConfigSignature 36 | ) 37 | 38 | // Initialize all the data structures 39 | func init() { 40 | // log.Infof("Initializing Patterns....") 41 | patternSignatureMap = make(map[string][]core.ConfigSignature) 42 | signatureIDMap = make(map[int]core.ConfigSignature) 43 | matchRegexpMap = make(map[string]*regexp.Regexp) 44 | matchSignatureMap = make(map[string]map[string]core.ConfigSignature) 45 | for _, part := range []string{ContentsPart, FilenamePart, PathPart, ExtPart} { 46 | matchSignatureMap[part] = make(map[string]core.ConfigSignature) 47 | } 48 | } 49 | 50 | // Scan to find complex pattern matches for the contents, path, filename and extension of this file 51 | // @parameters 52 | // contents - content of the file 53 | // path - Complete path of the file 54 | // filename - Name of the file 55 | // extension - Extension of the file 56 | // layerID - layer ID of this file in the container image 57 | // @returns 58 | // []output.SecretFound - List of all secrets found 59 | // Error - Errors if any. Otherwise, returns nil 60 | func MatchPatternSignatures(contents io.ReadSeeker, path string, filename string, extension string, layerID string) ([]output.SecretFound, error) { 61 | var tempSecretsFound []output.SecretFound 62 | var matchingStr io.ReadSeeker 63 | 64 | for _, part := range []string{ContentsPart, FilenamePart, PathPart, ExtPart} { 65 | 66 | switch part { 67 | case FilenamePart: 68 | matchingStr = strings.NewReader(filename) 69 | case PathPart: 70 | matchingStr = strings.NewReader(path) 71 | case ExtPart: 72 | matchingStr = strings.NewReader(extension) 73 | case ContentsPart: 74 | matchingStr = contents 75 | } 76 | 77 | for _, signature := range patternSignatureMap[part] { 78 | matchingStr.Seek(0, io.SeekStart) 79 | indexes := signature.CompiledRegex.FindReaderIndex(bufio.NewReader(matchingStr)) 80 | if indexes != nil { 81 | match := make([]byte, indexes[1]-indexes[0]) 82 | matchingStr.Seek(int64(indexes[0]), io.SeekStart) 83 | _, err := matchingStr.Read(match) 84 | if err != nil { 85 | logrus.Infof("content read: %v", err) 86 | } 87 | matchStr := string(match) 88 | tempSecretsFound = append(tempSecretsFound, output.SecretFound{ 89 | LayerID: layerID, 90 | RuleID: signature.ID, 91 | RuleName: signature.Name, 92 | PartToMatch: part, 93 | Match: matchStr, 94 | MatchedContents: matchStr, 95 | Regex: signature.Regex, 96 | Severity: signature.Severity, 97 | SeverityScore: signature.SeverityScore, 98 | MatchFromByte: indexes[0], 99 | MatchToByte: indexes[1], 100 | CompleteFilename: path, 101 | }) 102 | break 103 | } 104 | } 105 | } 106 | 107 | return tempSecretsFound, nil 108 | } 109 | 110 | func MatchSimpleSignatures(contents io.ReadSeeker, path string, filename string, extension string, layerID string) ([]output.SecretFound, error) { 111 | var tempSecretsFound []output.SecretFound 112 | var matchingStr io.ReadSeeker 113 | 114 | for _, part := range []string{ContentsPart, FilenamePart, PathPart, ExtPart} { 115 | if _, has := matchRegexpMap[part]; !has { 116 | continue 117 | } 118 | 119 | switch part { 120 | case FilenamePart: 121 | matchingStr = strings.NewReader(filename) 122 | case PathPart: 123 | matchingStr = strings.NewReader(path) 124 | case ExtPart: 125 | matchingStr = strings.NewReader(extension) 126 | case ContentsPart: 127 | matchingStr = contents 128 | } 129 | 130 | indexes := matchRegexpMap[part].FindReaderIndex(bufio.NewReader(matchingStr)) 131 | if indexes != nil { 132 | match := make([]byte, indexes[1]-indexes[0]) 133 | matchingStr.Seek(int64(indexes[0]), io.SeekStart) 134 | _, err := matchingStr.Read(match) 135 | if err != nil { 136 | logrus.Infof("content read: %v", err) 137 | } 138 | matchStr := string(match) 139 | signature := matchSignatureMap[part][matchStr] 140 | 141 | tempSecretsFound = append(tempSecretsFound, output.SecretFound{ 142 | LayerID: layerID, 143 | RuleID: signature.ID, 144 | RuleName: signature.Name, 145 | PartToMatch: part, 146 | Match: matchStr, 147 | MatchedContents: matchStr, 148 | Regex: signature.Regex, 149 | Severity: signature.Severity, 150 | SeverityScore: signature.SeverityScore, 151 | MatchFromByte: indexes[0], 152 | MatchToByte: indexes[1], 153 | CompleteFilename: path, 154 | }) 155 | } 156 | } 157 | 158 | return tempSecretsFound, nil 159 | } 160 | 161 | // Process all the extracted signatures from config file, add severity and severity scores, finally 162 | // store them in appropriate maps 163 | // @parameters 164 | // configSignatures - Extracted patterns from signature config file 165 | func ProcessSignatures(configSignatures []core.ConfigSignature) { 166 | var simpleContentSignatures []string 167 | var simpleExtSignatures []string 168 | var simpleFilenameSignatures []string 169 | var simplePathSignatures []string 170 | 171 | var patternContentReg []string 172 | var patternExtReg []string 173 | var patternFilenameReg []string 174 | var patternPathReg []string 175 | 176 | var patternContentSignatures []core.ConfigSignature 177 | var patternExtSignatures []core.ConfigSignature 178 | var patternFilenameSignatures []core.ConfigSignature 179 | var patternPathSignatures []core.ConfigSignature 180 | 181 | for i, signature := range configSignatures { 182 | signature.ID = i 183 | 184 | if signature.Match != "" { 185 | if signature.Severity == "" { 186 | signature.Severity = "low" 187 | signature.SeverityScore = 2.5 188 | } 189 | 190 | log.Debugf("Simple Signature %s %s %s %s %d", signature.Name, 191 | signature.Part, signature.Match, signature.Severity, signature.ID) 192 | 193 | matchSignatureMap[signature.Part][signature.Match] = signature 194 | 195 | switch signature.Part { 196 | case ContentsPart: 197 | simpleContentSignatures = append(simpleContentSignatures, 198 | strings.ReplaceAll(signature.Match, ".", `\.`)) 199 | case ExtPart: 200 | simpleExtSignatures = append(simpleExtSignatures, 201 | strings.ReplaceAll(signature.Match, ".", `\.`)) 202 | case FilenamePart: 203 | simpleFilenameSignatures = append(simpleFilenameSignatures, 204 | strings.ReplaceAll(signature.Match, ".", `\.`)) 205 | case PathPart: 206 | simplePathSignatures = append(simplePathSignatures, 207 | strings.ReplaceAll(signature.Match, ".", `\.`)) 208 | } 209 | } else { 210 | if signature.Severity == "" { 211 | if signature.RegexType == LargeRegexType { 212 | signature.Severity = "high" 213 | signature.SeverityScore = 7.5 214 | } else { 215 | signature.Severity = "medium" 216 | signature.SeverityScore = 5.0 217 | } 218 | } 219 | 220 | log.Debugf("Pattern Signature %s %s %s %s %s %s %d", signature.Name, signature.Part, 221 | signature.Match, signature.Regex, signature.RegexType, signature.Severity, signature.ID) 222 | 223 | signature.CompiledRegex = regexp.MustCompile(signature.Regex) 224 | 225 | switch signature.Part { 226 | case ContentsPart: 227 | patternContentSignatures = append(patternContentSignatures, signature) 228 | patternContentReg = append(patternContentReg, signature.Regex) 229 | case ExtPart: 230 | patternExtSignatures = append(patternExtSignatures, signature) 231 | patternExtReg = append(patternExtReg, signature.Regex) 232 | case FilenamePart: 233 | patternFilenameSignatures = append(patternFilenameSignatures, signature) 234 | patternFilenameReg = append(patternFilenameReg, signature.Regex) 235 | case PathPart: 236 | patternPathSignatures = append(patternPathSignatures, signature) 237 | patternPathReg = append(patternPathReg, signature.Regex) 238 | } 239 | } 240 | 241 | signatureIDMap[signature.ID] = signature 242 | 243 | } 244 | 245 | if len(simpleContentSignatures) != 0 { 246 | matchRegexpMap[ContentsPart] = regexp.MustCompile(strings.Join(simpleContentSignatures, "|")) 247 | } 248 | if len(simpleExtSignatures) != 0 { 249 | matchRegexpMap[ExtPart] = regexp.MustCompile(strings.Join(simpleExtSignatures, "|")) 250 | } 251 | if len(simpleFilenameSignatures) != 0 { 252 | matchRegexpMap[FilenamePart] = regexp.MustCompile(strings.Join(simpleFilenameSignatures, "|")) 253 | } 254 | if len(simplePathSignatures) != 0 { 255 | matchRegexpMap[PathPart] = regexp.MustCompile(strings.Join(simplePathSignatures, "|")) 256 | } 257 | 258 | patternSignatureMap[ContentsPart] = patternContentSignatures 259 | patternSignatureMap[ExtPart] = patternExtSignatures 260 | patternSignatureMap[FilenamePart] = patternFilenameSignatures 261 | patternSignatureMap[PathPart] = patternPathSignatures 262 | 263 | for _, part := range []string{ContentsPart, FilenamePart, PathPart, ExtPart} { 264 | log.Debugf("Number of Complex Patterns for matching %s: %d", part, len(patternSignatureMap[part])) 265 | log.Debugf("Number of Simple Patterns for matching %s: %d", part, len(matchSignatureMap[part])) 266 | } 267 | } 268 | 269 | // Append one signature to the list of signatures 270 | // @parameters 271 | // signature - signature to be added 272 | // configSignatures - List of signatures 273 | func addToSignatures(signature core.ConfigSignature, Signatures *[]core.ConfigSignature) { 274 | *Signatures = append(*Signatures, signature) 275 | } 276 | 277 | // Update severity and score based on length of match 278 | // @parameters 279 | // inputMatch - Matched portion of the input 280 | // severity - Original Severity 281 | // severityScore - Original Severity Score 282 | // @returns 283 | // string - Updated Severity 284 | // float64 - Updated Severity Score 285 | func calculateSeverity(inputMatch []byte, severity string, severityScore float64) (string, float64) { 286 | updatedSeverity := "low" 287 | lenMatch := len(inputMatch) 288 | MinSecretLength := 10 289 | 290 | if lenMatch < MinSecretLength { 291 | return severity, severityScore 292 | } 293 | 294 | if lenMatch >= MaxSecretLength { 295 | return "high", 10.0 296 | } 297 | 298 | scoreRange := 10.0 - severityScore 299 | 300 | increament := ((float64(lenMatch) - float64(MinSecretLength)) * scoreRange) / (float64(MaxSecretLength) - float64(MinSecretLength)) 301 | 302 | updatedScore := severityScore + increament 303 | if updatedScore > 10.0 { 304 | updatedScore = 10.0 305 | } 306 | 307 | if 2.5 < updatedScore && updatedScore <= 7.5 { 308 | updatedSeverity = "medium" 309 | } else if 7.5 < updatedScore { 310 | updatedSeverity = "high" 311 | } 312 | 313 | return updatedSeverity, math.Round(updatedScore*100) / 100 314 | } 315 | 316 | // Find min of 2 int values 317 | func Min(value_0, value_1 int) int { 318 | if value_0 < value_1 { 319 | return value_0 320 | } 321 | return value_1 322 | } 323 | 324 | // Find max of 2 int values 325 | func Max(value_0, value_1 int) int { 326 | if value_0 > value_1 { 327 | return value_0 328 | } 329 | return value_1 330 | } 331 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= 3 | github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= 4 | github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0 h1:59MxjQVfjXsBpLy+dbd2/ELV5ofnUkUZBvWSC85sheA= 5 | github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0/go.mod h1:OahwfttHWG6eJ0clwcfBAHoDI6X/LV/15hx/wlMZSrU= 6 | github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= 7 | github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= 8 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 9 | github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= 10 | github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= 11 | github.com/Microsoft/hcsshim v0.11.7 h1:vl/nj3Bar/CvJSYo7gIQPyRWc9f3c6IeSNavBTSZNZQ= 12 | github.com/Microsoft/hcsshim v0.11.7/go.mod h1:MV8xMfmECjl5HdO7U/3/hFVnkmSBjAjmA09d4bExKcU= 13 | github.com/VirusTotal/gyp v0.9.0 h1:jhOBl93jfStmAcKLa/EcTmdPng5bn5kvJJZqQqJ5R4g= 14 | github.com/VirusTotal/gyp v0.9.0/go.mod h1:nmcW15dQ1657PmMcG9X/EZmp6rTQsyo9g8r6Cz1/AHc= 15 | github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= 16 | github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 17 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 18 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 19 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 20 | github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= 21 | github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= 22 | github.com/containerd/containerd v1.7.27 h1:yFyEyojddO3MIGVER2xJLWoCIn+Up4GaHFquP7hsFII= 23 | github.com/containerd/containerd v1.7.27/go.mod h1:xZmPnl75Vc+BLGt4MIfu6bp+fy03gdHAn9bz+FreFR0= 24 | github.com/containerd/containerd/api v1.8.0 h1:hVTNJKR8fMc/2Tiw60ZRijntNMd1U+JVMyTRdsD2bS0= 25 | github.com/containerd/containerd/api v1.8.0/go.mod h1:dFv4lt6S20wTu/hMcP4350RL87qPWLVa/OHOwmmdnYc= 26 | github.com/containerd/continuity v0.4.4 h1:/fNVfTJ7wIl/YPMHjf+5H32uFhl63JucB34PlCpMKII= 27 | github.com/containerd/continuity v0.4.4/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE= 28 | github.com/containerd/errdefs v0.3.0 h1:FSZgGOeK4yuT/+DnF07/Olde/q4KBoMsaamhXxIMDp4= 29 | github.com/containerd/errdefs v0.3.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= 30 | github.com/containerd/fifo v1.1.0 h1:4I2mbh5stb1u6ycIABlBw9zgtlK8viPI9QkQNRQEEmY= 31 | github.com/containerd/fifo v1.1.0/go.mod h1:bmC4NWMbXlt2EZ0Hc7Fx7QzTFxgPID13eH0Qu+MAb2o= 32 | github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= 33 | github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= 34 | github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= 35 | github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= 36 | github.com/containerd/ttrpc v1.2.7 h1:qIrroQvuOL9HQ1X6KHe2ohc7p+HP/0VE6XPU7elJRqQ= 37 | github.com/containerd/ttrpc v1.2.7/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o= 38 | github.com/containerd/typeurl/v2 v2.1.1 h1:3Q4Pt7i8nYwy2KmQWIw2+1hTvwTE/6w9FqcttATPO/4= 39 | github.com/containerd/typeurl/v2 v2.1.1/go.mod h1:IDp2JFvbwZ31H8dQbEIY7sDl2L3o3HZj1hsSQlywkQ0= 40 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 41 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 42 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 43 | github.com/deepfence/YaraHunter v0.0.0-20250424121102-5c7a440b1405 h1:/dnOnTsIekoFJYC2Cb3j5h/+G5QvI2g+x6UPZ2BRQ9c= 44 | github.com/deepfence/YaraHunter v0.0.0-20250424121102-5c7a440b1405/go.mod h1:MvIfhEMk1flv5/uKEej2e61t3x/gZZnPIDpPHzTiN9c= 45 | github.com/deepfence/golang_deepfence_sdk/client v0.0.0-20250404165334-270bd6030734 h1:8EmNvcgwDrxNV5WMPSHelAP+Bi6GgaQeGNuPpNAXDzE= 46 | github.com/deepfence/golang_deepfence_sdk/client v0.0.0-20250404165334-270bd6030734/go.mod h1:UkHg/qLuPVnTqx4fPwmc2DhlNp5isdYwIxQ63B9JB4o= 47 | github.com/deepfence/golang_deepfence_sdk/utils v0.0.0-20250404165334-270bd6030734 h1:DKCSSy/PsKao2SUVXBm5x2sPdHI0ttNyazDcjEXVXIU= 48 | github.com/deepfence/golang_deepfence_sdk/utils v0.0.0-20250404165334-270bd6030734/go.mod h1:QdyXNUGNYGPMj8ls9R4N1y/IzmM7LrBQSBC/QuYCX+U= 49 | github.com/deepfence/match-scanner v0.0.0-20250424114533-ad87103b0479 h1:0Cz9ZEyNC4kFQ87Ds4Q98eoYeCl+HIgWj3Wj5++QJuc= 50 | github.com/deepfence/match-scanner v0.0.0-20250424114533-ad87103b0479/go.mod h1:THfKIy0Nn+7H/QLLx/2UwstV+gCUZmH/RKziYqP8Zzo= 51 | github.com/deepfence/vessel v0.14.0 h1:EJr9xGKhT6OXtHGhHx17Qq6TgH1ykzGvTw2hoAdci5A= 52 | github.com/deepfence/vessel v0.14.0/go.mod h1:bxV3AilO9gJI7j5QLjIAfhNKbcHYPDDmtKxODwjZfRU= 53 | github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= 54 | github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= 55 | github.com/docker/docker v28.1.1+incompatible h1:49M11BFLsVO1gxY9UX9p/zwkE/rswggs8AdFmXQw51I= 56 | github.com/docker/docker v28.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= 57 | github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= 58 | github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= 59 | github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8= 60 | github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= 61 | github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= 62 | github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= 63 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 64 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 65 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 66 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 67 | github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= 68 | github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= 69 | github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= 70 | github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= 71 | github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= 72 | github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= 73 | github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY= 74 | github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok= 75 | github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 76 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 77 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 78 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 79 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 80 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 81 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 82 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 83 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 84 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= 85 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 86 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 87 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 88 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 89 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 90 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 91 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 92 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 93 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 94 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 95 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 96 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 97 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 98 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 99 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 100 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 101 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 102 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 103 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 104 | github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 105 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 106 | github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 107 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 108 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 109 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 110 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 111 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 112 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= 113 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= 114 | github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= 115 | github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= 116 | github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= 117 | github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= 118 | github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= 119 | github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= 120 | github.com/hillu/go-yara/v4 v4.3.3 h1:O+7iYTZK20fzsXiJyvA0d529RTdnZCrgS6HdE0O7BMg= 121 | github.com/hillu/go-yara/v4 v4.3.3/go.mod h1:AHEs/FXVMQKVVlT6iG9d+q1BRr0gq0WoAWZQaZ0gS7s= 122 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 123 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 124 | github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= 125 | github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= 126 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 127 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 128 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 129 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 130 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 131 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 132 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 133 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 134 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 135 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 136 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 137 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 138 | github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= 139 | github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 140 | github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= 141 | github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= 142 | github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= 143 | github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= 144 | github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= 145 | github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= 146 | github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78= 147 | github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= 148 | github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= 149 | github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= 150 | github.com/moby/sys/signal v0.7.0 h1:25RW3d5TnQEoKvRbEKUGay6DCQ46IxAVTT9CUMgmsSI= 151 | github.com/moby/sys/signal v0.7.0/go.mod h1:GQ6ObYZfqacOwTtlXvcmh9A26dVRul/hbOZn88Kg8Tg= 152 | github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo= 153 | github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= 154 | github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= 155 | github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= 156 | github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= 157 | github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= 158 | github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= 159 | github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= 160 | github.com/nlepage/go-tarfs v1.2.1 h1:o37+JPA+ajllGKSPfy5+YpsNHDjZnAoyfvf5GsUa+Ks= 161 | github.com/nlepage/go-tarfs v1.2.1/go.mod h1:rno18mpMy9aEH1IiJVftFsqPyIpwqSUiAOpJYjlV2NA= 162 | github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= 163 | github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= 164 | github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= 165 | github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= 166 | github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= 167 | github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= 168 | github.com/opencontainers/runtime-spec v1.1.0 h1:HHUyrt9mwHUjtasSbXSMvs4cyFxh+Bll4AjJ9odEGpg= 169 | github.com/opencontainers/runtime-spec v1.1.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= 170 | github.com/opencontainers/selinux v1.11.0 h1:+5Zbo97w3Lbmb3PeqQtpmTkMwsW5nRI3YaLpt7tQ7oU= 171 | github.com/opencontainers/selinux v1.11.0/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec= 172 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 173 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 174 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 175 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 176 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 177 | github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= 178 | github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= 179 | github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= 180 | github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= 181 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 182 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 183 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 184 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 185 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 186 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 187 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 188 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 189 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 190 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 191 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 192 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 193 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 194 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 195 | go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= 196 | go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= 197 | go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= 198 | go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= 199 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 h1:x8Z78aZx8cOF0+Kkazoc7lwUNMGy0LrzEMxTm4BbTxg= 200 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0/go.mod h1:62CPTSry9QZtOaSsE3tOzhx6LzDhHnXJ6xHeMNNiM6Q= 201 | go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= 202 | go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= 203 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U= 204 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE= 205 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg= 206 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU= 207 | go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= 208 | go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= 209 | go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= 210 | go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= 211 | go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= 212 | go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= 213 | go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= 214 | go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= 215 | go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= 216 | go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= 217 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 218 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 219 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 220 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 221 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 222 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 223 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 224 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 225 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 226 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 227 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 228 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 229 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 230 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 231 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 232 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 233 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 234 | golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 235 | golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= 236 | golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= 237 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 238 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 239 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 240 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 241 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 242 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 243 | golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= 244 | golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 245 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 246 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 247 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 248 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 249 | golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 250 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 251 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 252 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 253 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 254 | golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= 255 | golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 256 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 257 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 258 | golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= 259 | golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= 260 | golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44= 261 | golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 262 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 263 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 264 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 265 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 266 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 267 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 268 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 269 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 270 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 271 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 272 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 273 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 274 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 275 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 276 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 277 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 278 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 279 | google.golang.org/genproto v0.0.0-20231211222908-989df2bf70f3 h1:1hfbdAfFbkmpg41000wDVqr7jUpK/Yo+LPnIxxGzmkg= 280 | google.golang.org/genproto v0.0.0-20231211222908-989df2bf70f3/go.mod h1:5RBcpGRxr25RbDzY5w+dmaqpSEvl8Gwl1x2CICf60ic= 281 | google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a h1:nwKuGPlUAt+aR+pcrkfFRrTU1BVrSmYyYMxYbUIVHr0= 282 | google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a/go.mod h1:3kWAYMk1I75K4vykHtKt2ycnOgpA6974V7bREqbsenU= 283 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4= 284 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ= 285 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 286 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 287 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 288 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 289 | google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= 290 | google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM= 291 | google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= 292 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 293 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 294 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 295 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 296 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 297 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 298 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 299 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 300 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 301 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 302 | google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 303 | google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= 304 | google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 305 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 306 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 307 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 308 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 309 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 310 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 311 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 312 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 313 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 314 | gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= 315 | gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= 316 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 317 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 318 | --------------------------------------------------------------------------------