├── .gitignore ├── internal ├── testutils │ ├── helper.go │ ├── integration.go │ └── testutils.go └── runner │ ├── banner.go │ ├── output-writer.go │ ├── options.go │ └── runner.go ├── README.md ├── Dockerfile ├── cmd ├── cdncheck │ └── main.go ├── functional-test │ ├── run.sh │ └── main.go ├── integration-test │ ├── integration-test.go │ └── library.go └── generate-index │ ├── main.go │ ├── provider.yaml │ └── sources_data.json ├── sources_data.go ├── Makefile ├── generate ├── options.go ├── types.go ├── ranges.go └── input.go ├── integration_tests └── run.sh ├── .github ├── workflows │ ├── dep-auto-merge.yml │ ├── lint-test.yml │ ├── functional-test.yml │ ├── release-binary.yml │ ├── codeql-analysis.yml │ ├── build-test.yml │ ├── fingerprint-update.yml │ ├── dockerhub-push.yml │ └── autorelease-tag.yml └── dependabot.yml ├── cdncheck_test.go ├── .goreleaser.yaml ├── examples └── simple.go ├── LICENSE.md ├── types.go ├── other.go ├── other_test.go ├── go.mod ├── cdncheck.go └── go.sum /.gitignore: -------------------------------------------------------------------------------- 1 | cmd/generate-index/generate-index 2 | cmd/cdncheck/cdncheck 3 | cmd/functional-test/cdncheck_dev 4 | cmd/functional-test/functional-test 5 | dist/ -------------------------------------------------------------------------------- /internal/testutils/helper.go: -------------------------------------------------------------------------------- 1 | package testutils 2 | 3 | func CompareOutput(input, expected []string) bool { 4 | if len(input) != len(expected) { 5 | return false 6 | } 7 | for i, v := range input { 8 | if v != expected[i] { 9 | return false 10 | } 11 | } 12 | return true 13 | } 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 修改自:https://github.com/projectdiscovery/cdncheck 2 | 增加了国内CDN IP判断,及大部分主流CDN 域名判断、主流云资产识别 3 | 4 | 5 | - Baidu-加速乐 (创宇云盾) 6 | - 网宿 CDN 7 | - 腾讯云CDN 8 | - 阿里云 CDN 9 | - 百度智能云CDN 10 | 11 | 12 | 13 | 如要增加其它CDN或WAF 请更新: 14 | sources_data.json 文件并重新编译 15 | 16 | 17 | 后面仅更新sources_data.json 不再更新Releases,请自行编译 18 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Base 2 | FROM golang:1.20.6-alpine AS builder 3 | 4 | RUN apk add --no-cache git build-base gcc musl-dev 5 | WORKDIR /app 6 | COPY . /app 7 | RUN go mod download 8 | RUN go build ./cmd/cdncheck 9 | 10 | FROM alpine:3.18.2 11 | RUN apk -U upgrade --no-cache \ 12 | && apk add --no-cache bind-tools ca-certificates 13 | COPY --from=builder /app/cdncheck /usr/local/bin/ 14 | 15 | ENTRYPOINT ["cdncheck"] 16 | -------------------------------------------------------------------------------- /cmd/cdncheck/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/projectdiscovery/cdncheck/internal/runner" 5 | "github.com/projectdiscovery/gologger" 6 | ) 7 | 8 | func main() { 9 | options := runner.ParseOptions() 10 | 11 | newRunner := runner.NewRunner(options) 12 | 13 | err := newRunner.Run() 14 | if err != nil { 15 | gologger.Fatal().Msgf("Could not run cdncheck enumeration: %s\n", err) 16 | } 17 | } 18 | 19 | -------------------------------------------------------------------------------- /sources_data.go: -------------------------------------------------------------------------------- 1 | package cdncheck 2 | 3 | import ( 4 | _ "embed" 5 | "encoding/json" 6 | "fmt" 7 | ) 8 | 9 | //go:embed sources_data.json 10 | var data string 11 | 12 | var generatedData InputCompiled 13 | 14 | func init() { 15 | if err := json.Unmarshal([]byte(data), &generatedData); err != nil { 16 | panic(fmt.Sprintf("Could not parse cidr data: %s", err)) 17 | } 18 | DefaultCDNProviders = mapKeys(generatedData.CDN) 19 | DefaultWafProviders = mapKeys(generatedData.WAF) 20 | DefaultCloudProviders = mapKeys(generatedData.Cloud) 21 | } 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Go parameters 2 | GOCMD=go 3 | GOBUILD=$(GOCMD) build 4 | GOMOD=$(GOCMD) mod 5 | GOTEST=$(GOCMD) test 6 | GOFLAGS := -v 7 | LDFLAGS := -s -w 8 | 9 | ifneq ($(shell go env GOOS),darwin) 10 | LDFLAGS := -extldflags "-static" 11 | endif 12 | 13 | all: build 14 | build: 15 | $(GOBUILD) $(GOFLAGS) -ldflags '$(LDFLAGS)' -o "cdncheck" cmd/cdncheck/main.go 16 | test: 17 | $(GOTEST) $(GOFLAGS) ./... 18 | functional: 19 | cd cmd/functional-test; bash run.sh 20 | integration: 21 | cd integration_tests; bash run.sh 22 | tidy: 23 | $(GOMOD) tidy 24 | -------------------------------------------------------------------------------- /cmd/functional-test/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # reading os type from arguments 4 | CURRENT_OS=$1 5 | 6 | if [ "${CURRENT_OS}" == "windows-latest" ];then 7 | extension=.exe 8 | fi 9 | 10 | echo "::group::Building functional-test binary" 11 | go build -o functional-test$extension 12 | echo "::endgroup::" 13 | 14 | echo "::group::Building cdncheck binary from current branch" 15 | go build -o cdncheck_dev$extension ../cdncheck 16 | echo "::endgroup::" 17 | 18 | 19 | echo 'Starting cdncheck functional test' 20 | ./functional-test$extension -dev ./cdncheck_dev$extension 21 | -------------------------------------------------------------------------------- /generate/options.go: -------------------------------------------------------------------------------- 1 | package generate 2 | 3 | import ( 4 | "net/http" 5 | "os" 6 | ) 7 | 8 | type Options struct { 9 | IPInfoToken string 10 | HTTPClient *http.Client 11 | } 12 | 13 | // HasAuthInfo returns true if auth info has been provided 14 | func (options *Options) HasAuthInfo() bool { 15 | return options.IPInfoToken != "" 16 | } 17 | 18 | // ParseFromEnv parses auth tokens from env or file 19 | func (options *Options) ParseFromEnv() { 20 | if ipInfoToken := os.Getenv("IPINFO_TOKEN"); ipInfoToken != "" { 21 | options.IPInfoToken = ipInfoToken 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /integration_tests/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "::group::Build cdncheck 4 | " 5 | rm integration-test cdncheck 6 | 2>/dev/null 7 | cd ../cmd/cdncheck 8 | 9 | go build 10 | mv cdncheck ../../integration_tests/cdncheck 11 | 12 | echo "::endgroup::" 13 | 14 | echo "::group::Build cdncheck 15 | integration-test" 16 | cd ../integration-test 17 | go build 18 | mv integration-test ../../integration_tests/integration-test 19 | cd ../../integration_tests 20 | echo "::endgroup::" 21 | 22 | ./integration-test 23 | if [ $? -eq 0 ] 24 | then 25 | exit 0 26 | else 27 | exit 1 28 | fi 29 | -------------------------------------------------------------------------------- /.github/workflows/dep-auto-merge.yml: -------------------------------------------------------------------------------- 1 | name: 🤖 dep auto merge 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | workflow_dispatch: 8 | 9 | permissions: 10 | pull-requests: write 11 | issues: write 12 | repository-projects: write 13 | 14 | jobs: 15 | automerge: 16 | runs-on: ubuntu-latest 17 | if: github.actor == 'dependabot[bot]' 18 | steps: 19 | - uses: actions/checkout@v3 20 | with: 21 | token: ${{ secrets.DEPENDABOT_PAT }} 22 | 23 | - uses: ahmadnassri/action-dependabot-auto-merge@v2 24 | with: 25 | github-token: ${{ secrets.DEPENDABOT_PAT }} 26 | target: all 27 | -------------------------------------------------------------------------------- /.github/workflows/lint-test.yml: -------------------------------------------------------------------------------- 1 | name: 🙏🏻 Lint Test 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - '**.go' 7 | - '**.mod' 8 | workflow_dispatch: 9 | 10 | jobs: 11 | lint: 12 | name: Lint Test 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout code 16 | uses: actions/checkout@v3 17 | 18 | - name: Set up Go 19 | uses: actions/setup-go@v4 20 | with: 21 | go-version: 1.19 22 | 23 | - name: Run golangci-lint 24 | uses: golangci/golangci-lint-action@v3.6.0 25 | with: 26 | version: latest 27 | args: --timeout 5m 28 | working-directory: . 29 | -------------------------------------------------------------------------------- /internal/testutils/integration.go: -------------------------------------------------------------------------------- 1 | package testutils 2 | 3 | import ( 4 | "fmt" 5 | "os/exec" 6 | "strings" 7 | ) 8 | 9 | func RunCdncheckBinaryAndGetResults(target string, cdncheckBinary string, debug bool, args []string) ([]string, error) { 10 | cmd := exec.Command("bash", "-c") 11 | cmdLine := fmt.Sprintf(`echo %s | %s `, target, cdncheckBinary) 12 | cmdLine += strings.Join(args, " ") 13 | 14 | cmd.Args = append(cmd.Args, cmdLine) 15 | data, err := cmd.Output() 16 | if err != nil { 17 | return nil, err 18 | } 19 | parts := []string{} 20 | items := strings.Split(string(data), "\n") 21 | for _, i := range items { 22 | if i != "" { 23 | parts = append(parts, i) 24 | } 25 | } 26 | return parts, nil 27 | } 28 | -------------------------------------------------------------------------------- /cdncheck_test.go: -------------------------------------------------------------------------------- 1 | package cdncheck 2 | 3 | import ( 4 | "net" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestCDNCheckValid(t *testing.T) { 11 | client := New() 12 | 13 | found, provider, itemType, err := client.Check(net.ParseIP("173.245.48.12")) 14 | require.Equal(t, "cloudflare", provider, "could not get correct provider") 15 | require.Equal(t, "waf", itemType, "could not get correct item type") 16 | require.Nil(t, err, "Could not check ip in ranger") 17 | require.True(t, found, "Could not check cloudlfare ip blacklist") 18 | 19 | found, _, _, err = client.Check(net.ParseIP("127.0.0.1")) 20 | require.Nil(t, err, "Could not check ip in ranger") 21 | require.False(t, found, "Localhost IP found in blacklist") 22 | } 23 | -------------------------------------------------------------------------------- /.github/workflows/functional-test.yml: -------------------------------------------------------------------------------- 1 | name: 🧪 Functional Test 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - '**.go' 7 | - '**.mod' 8 | workflow_dispatch: 9 | 10 | jobs: 11 | functional: 12 | name: Functional Test 13 | runs-on: ${{ matrix.os }} 14 | strategy: 15 | matrix: 16 | os: [ubuntu-latest, windows-latest, macOS-latest] 17 | steps: 18 | - name: Set up Go 19 | uses: actions/setup-go@v4 20 | with: 21 | go-version: 1.19 22 | 23 | - name: Check out code 24 | uses: actions/checkout@v3 25 | 26 | - name: Functional Tests 27 | run: | 28 | chmod +x run.sh 29 | bash run.sh ${{ matrix.os }} 30 | working-directory: cmd/functional-test 31 | -------------------------------------------------------------------------------- /internal/runner/banner.go: -------------------------------------------------------------------------------- 1 | package runner 2 | 3 | import ( 4 | "github.com/projectdiscovery/gologger" 5 | updateutils "github.com/projectdiscovery/utils/update" 6 | ) 7 | 8 | const banner = ` 9 | __ __ __ 10 | _________/ /___ _____/ /_ ___ _____/ /__ 11 | / ___/ __ / __ \/ ___/ __ \/ _ \/ ___/ //_/ 12 | / /__/ /_/ / / / / /__/ / / / __/ /__/ ,< 13 | \___/\__,_/_/ /_/\___/_/ /_/\___/\___/_/|_| 14 | ` 15 | 16 | // version is the current version of cdncheck 17 | const version = `v1.0.9` 18 | 19 | // showBanner is used to show the banner to the user 20 | func showBanner() { 21 | gologger.Print().Msgf("%s\n", banner) 22 | gologger.Print().Msgf("\t\tprojectdiscovery.io\n\n") 23 | } 24 | 25 | // GetUpdateCallback returns a callback function that updates cdncheck 26 | func GetUpdateCallback() func() { 27 | return func() { 28 | showBanner() 29 | updateutils.GetUpdateToolCallback("cdncheck", version)() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /.github/workflows/release-binary.yml: -------------------------------------------------------------------------------- 1 | name: 🎉 Release Binary 2 | 3 | on: 4 | push: 5 | tags: 6 | - v* 7 | workflow_dispatch: 8 | 9 | jobs: 10 | release: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: "Check out code" 14 | uses: actions/checkout@v3 15 | with: 16 | fetch-depth: 0 17 | 18 | - name: "Set up Go" 19 | uses: actions/setup-go@v4 20 | with: 21 | go-version: 1.19 22 | 23 | - name: "Create release on GitHub" 24 | uses: goreleaser/goreleaser-action@v4 25 | with: 26 | args: "release --rm-dist" 27 | version: latest 28 | workdir: . 29 | env: 30 | GITHUB_TOKEN: "${{ secrets.PDTEAMX_PAT }}" 31 | SLACK_WEBHOOK: "${{ secrets.RELEASE_SLACK_WEBHOOK }}" 32 | DISCORD_WEBHOOK_ID: "${{ secrets.DISCORD_WEBHOOK_ID }}" 33 | DISCORD_WEBHOOK_TOKEN: "${{ secrets.DISCORD_WEBHOOK_TOKEN }}" -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: 🚨 CodeQL Analysis 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | branches: 7 | - dev 8 | 9 | jobs: 10 | analyze: 11 | name: Analyze 12 | runs-on: ubuntu-latest 13 | permissions: 14 | actions: read 15 | contents: read 16 | security-events: write 17 | 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | language: [ 'go' ] 22 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 23 | 24 | steps: 25 | - name: Checkout repository 26 | uses: actions/checkout@v3 27 | 28 | # Initializes the CodeQL tools for scanning. 29 | - name: Initialize CodeQL 30 | uses: github/codeql-action/init@v2 31 | with: 32 | languages: ${{ matrix.language }} 33 | 34 | - name: Autobuild 35 | uses: github/codeql-action/autobuild@v2 36 | 37 | - name: Perform CodeQL Analysis 38 | uses: github/codeql-action/analyze@v2 -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | before: 2 | hooks: 3 | - go mod tidy 4 | 5 | builds: 6 | - env: 7 | - CGO_ENABLED=0 8 | goos: 9 | - windows 10 | - linux 11 | - darwin 12 | goarch: 13 | - amd64 14 | - 386 15 | - arm 16 | - arm64 17 | 18 | ignore: 19 | - goos: darwin 20 | goarch: '386' 21 | - goos: windows 22 | goarch: 'arm' 23 | - goos: windows 24 | goarch: 'arm64' 25 | 26 | binary: '{{ .ProjectName }}' 27 | main: cmd/cdncheck/main.go 28 | 29 | archives: 30 | - format: zip 31 | replacements: 32 | darwin: macOS 33 | 34 | checksum: 35 | algorithm: sha256 36 | 37 | announce: 38 | slack: 39 | enabled: true 40 | channel: '#release' 41 | username: GoReleaser 42 | message_template: 'New Release: {{ .ProjectName }} {{ .Tag }} is out! Check it out at {{ .ReleaseURL }}' 43 | 44 | discord: 45 | enabled: true 46 | message_template: '**New Release: {{ .ProjectName }} {{.Tag}}** is published! Check it out at {{ .ReleaseURL }}' -------------------------------------------------------------------------------- /internal/runner/output-writer.go: -------------------------------------------------------------------------------- 1 | package runner 2 | 3 | import ( 4 | "encoding/json" 5 | "io" 6 | "sync" 7 | ) 8 | 9 | type OutputWriter struct { 10 | writers []io.Writer 11 | sync.RWMutex 12 | } 13 | 14 | func NewOutputWriter() (*OutputWriter, error) { 15 | return &OutputWriter{}, nil 16 | } 17 | 18 | func (o *OutputWriter) AddWriters(writers ...io.Writer) { 19 | o.writers = append(o.writers, writers...) 20 | } 21 | 22 | func (o *OutputWriter) Write(data []byte) { 23 | o.Lock() 24 | defer o.Unlock() 25 | for _, writer := range o.writers { 26 | _, _ = writer.Write(data) 27 | _, _ = writer.Write([]byte("\n")) 28 | } 29 | } 30 | 31 | func (o *OutputWriter) WriteString(data string) { 32 | o.Write([]byte(data)) 33 | } 34 | func (o *OutputWriter) WriteJSON(data Output) { 35 | jsonData, err := json.Marshal(data) 36 | if err != nil { 37 | return 38 | } 39 | o.Write(jsonData) 40 | } 41 | 42 | func (o *OutputWriter) Close() { 43 | for _, writer := range o.writers { 44 | if wr, ok := writer.(io.Closer); ok { 45 | wr.Close() 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /generate/types.go: -------------------------------------------------------------------------------- 1 | package generate 2 | 3 | // Categories contains various cdn, waf, cloud and fqdn operators 4 | type Categories struct { 5 | // CDN contains a list of inputs for CDN cidrs 6 | CDN *Category `yaml:"cdn"` 7 | // WAF contains a list of inputs for WAF cidrs 8 | WAF *Category `yaml:"waf"` 9 | // Cloud contains a list of inputs for Cloud cidrs 10 | Cloud *Category `yaml:"cloud"` 11 | Common *Category `yaml:"common"` 12 | } 13 | 14 | // Category contains configuration for a specific category 15 | type Category struct { 16 | // URLs contains a list of static URLs for CIDR list 17 | URLs map[string][]string `yaml:"urls"` 18 | // ASN contains ASN numbers for an Input item 19 | ASN map[string][]string `yaml:"asn"` 20 | // CIDR contains a list of CIDRs for Input item 21 | // 22 | // CIDR is generated using generate-index tool which is then 23 | // used for checking the provided IP for each input type. 24 | CIDR map[string][]string `yaml:"cidr"` 25 | // FQDN contains public suffixes for major cloud operators 26 | FQDN map[string][]string `yaml:"fqdn"` 27 | } 28 | -------------------------------------------------------------------------------- /examples/simple.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | 7 | "github.com/projectdiscovery/cdncheck" 8 | ) 9 | 10 | func main() { 11 | client := cdncheck.New() 12 | ip := net.ParseIP("173.245.48.12") 13 | 14 | // checks if an IP is contained in the cdn denylist 15 | matched, val, err := client.CheckCDN(ip) 16 | if err != nil { 17 | panic(err) 18 | } 19 | 20 | if matched { 21 | fmt.Printf("%v is a %v\n", ip, val) 22 | } else { 23 | fmt.Printf("%v is not a CDN\n", ip) 24 | } 25 | 26 | // checks if an IP is contained in the cloud denylist 27 | matched, val, err = client.CheckCloud(ip) 28 | if err != nil { 29 | panic(err) 30 | } 31 | 32 | if matched { 33 | fmt.Printf("%v is a %v\n", ip, val) 34 | } else { 35 | fmt.Printf("%v is not a Cloud\n", ip) 36 | } 37 | 38 | // checks if an IP is contained in the waf denylist 39 | matched, val, err = client.CheckWAF(ip) 40 | if err != nil { 41 | panic(err) 42 | } 43 | 44 | if matched { 45 | fmt.Printf("%v WAF is %v\n", ip, val) 46 | } else { 47 | fmt.Printf("%v is not a WAF\n", ip) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 ProjectDiscovery, Inc. 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 | -------------------------------------------------------------------------------- /.github/workflows/build-test.yml: -------------------------------------------------------------------------------- 1 | name: 🔨 Build Test 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - '**.go' 7 | - '**.mod' 8 | workflow_dispatch: 9 | 10 | 11 | jobs: 12 | build: 13 | name: Test Builds 14 | runs-on: ${{ matrix.os }} 15 | strategy: 16 | matrix: 17 | os: [ubuntu-latest, windows-latest, macOS-12] 18 | go-version: [1.19.x] 19 | steps: 20 | - name: Set up Go 21 | uses: actions/setup-go@v4 22 | with: 23 | go-version: 1.19 24 | 25 | - name: Check out code 26 | uses: actions/checkout@v3 27 | 28 | - name: Test 29 | run: go test ./... 30 | 31 | - name: Run Example 32 | run: go run . 33 | working-directory: examples/ 34 | 35 | - name: Integration Tests 36 | run: | 37 | chmod +x run.sh 38 | bash run.sh ${{ matrix.os }} 39 | working-directory: integration_tests/ 40 | 41 | - name: Install 42 | run: go install 43 | working-directory: cmd/cdncheck/ 44 | 45 | - name: Race Condition Tests 46 | run: go build -race . 47 | working-directory: cmd/cdncheck/ -------------------------------------------------------------------------------- /cmd/integration-test/integration-test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | 8 | "github.com/logrusorgru/aurora" 9 | ) 10 | 11 | type TestCase interface { 12 | // Execute executes a test case and returns any errors if occurred 13 | Execute() error 14 | } 15 | 16 | var ( 17 | // debug = os.Getenv("DEBUG") == "true" 18 | customTest = os.Getenv("TEST") 19 | 20 | errored = false 21 | success = aurora.Green("[✓]").String() 22 | failed = aurora.Red("[✘]").String() 23 | 24 | tests = map[string]map[string]TestCase{ 25 | "code": libraryTestcases, 26 | } 27 | ) 28 | 29 | func main() { 30 | for name, tests := range tests { 31 | fmt.Printf("Running test cases for \"%s\"\n", aurora.Blue(name)) 32 | if customTest != "" && !strings.Contains(name, customTest) { 33 | continue // only run tests user asked 34 | } 35 | for name, test := range tests { 36 | err := test.Execute() 37 | if err != nil { 38 | fmt.Fprintf(os.Stderr, "%s Test \"%s\" failed: %s\n", failed, name, err) 39 | errored = true 40 | } else { 41 | fmt.Printf("%s Test \"%s\" passed!\n", success, name) 42 | } 43 | } 44 | } 45 | if errored { 46 | os.Exit(1) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /cmd/integration-test/library.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/projectdiscovery/cdncheck" 7 | errorutil "github.com/projectdiscovery/utils/errors" 8 | ) 9 | 10 | var libraryTestcases = map[string]TestCase{ 11 | "cdncheck as library": &goIntegrationTest{}, 12 | } 13 | 14 | type goIntegrationTest struct{} 15 | 16 | func (h *goIntegrationTest) Execute() error { 17 | client := cdncheck.New() 18 | ip := net.ParseIP("173.245.48.12") 19 | // checks if an IP is contained in the cdn denylist 20 | matched, val, err := client.CheckCDN(ip) 21 | if err != nil { 22 | return err 23 | } 24 | if matched { 25 | return errorutil.New("Expected %v is WAF, but got %v", ip, val) 26 | } 27 | // checks if an IP is contained in the cloud denylist 28 | matched, val, err = client.CheckCloud(ip) 29 | if err != nil { 30 | return err 31 | } 32 | if matched { 33 | return errorutil.New("Expected %v is WAF, but got %v", ip, val) 34 | } 35 | // checks if an IP is contained in the waf denylist 36 | matched, val, err = client.CheckWAF(ip) 37 | if err != nil { 38 | return err 39 | } 40 | if !matched { 41 | return errorutil.New("Expected %v WAF is cloudflare, but got %v", ip, val) 42 | } 43 | return err 44 | } 45 | -------------------------------------------------------------------------------- /.github/workflows/fingerprint-update.yml: -------------------------------------------------------------------------------- 1 | name: 💡Fingerprint Update 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: '0 0 * * 0' 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout Repo 13 | uses: actions/checkout@v3 14 | with: 15 | persist-credentials: false 16 | 17 | - name: Setup golang 18 | uses: actions/setup-go@v4 19 | with: 20 | go-version: 1.19 21 | 22 | - name: Build CDN Data 23 | env: 24 | IPINFO_TOKEN: "${{ secrets.IPINFO_TOKEN }}" 25 | run: go run . -output ../../sources_data.json 26 | working-directory: cmd/generate-index 27 | 28 | - name: Create local changes 29 | run: | 30 | git add sources_data.json 31 | 32 | - name: Commit files 33 | run: | 34 | git config --local user.email "action@github.com" 35 | git config --local user.name "GitHub Action" 36 | git commit -m "Weekly fingerprints update [$(date)] :robot:" -a --allow-empty 37 | 38 | - name: Push changes 39 | uses: ad-m/github-push-action@master 40 | with: 41 | github_token: ${{ secrets.GITHUB_TOKEN }} 42 | branch: ${{ github.ref }} -------------------------------------------------------------------------------- /generate/ranges.go: -------------------------------------------------------------------------------- 1 | package generate 2 | 3 | import ( 4 | "io" 5 | "net/http" 6 | "strings" 7 | ) 8 | 9 | type scraperNameFuncPair struct { 10 | name string 11 | scraper scraperFunc 12 | } 13 | 14 | var scraperTypeToScraperMap = map[string][]scraperNameFuncPair{ 15 | "cdn": {}, 16 | "waf": { 17 | {name: "incapsula", scraper: scrapeIncapsula}, 18 | }, 19 | "cloud": {}, 20 | } 21 | 22 | type scraperFunc func(httpClient *http.Client) ([]string, error) 23 | 24 | // scrapeIncapsula scrapes incapsula firewall's CIDR ranges from their API 25 | func scrapeIncapsula(httpClient *http.Client) ([]string, error) { 26 | req, err := http.NewRequest(http.MethodPost, "https://my.incapsula.com/api/integration/v1/ips", strings.NewReader("resp_format=text")) 27 | if err != nil { 28 | return nil, err 29 | } 30 | req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 31 | 32 | resp, err := httpClient.Do(req) 33 | if err != nil { 34 | return nil, err 35 | } 36 | defer resp.Body.Close() 37 | 38 | data, err := io.ReadAll(resp.Body) 39 | if err != nil { 40 | return nil, err 41 | } 42 | body := string(data) 43 | 44 | cidrs := cidrRegex.FindAllString(body, -1) 45 | if len(cidrs) == 0 { 46 | return nil, errNoCidrFound 47 | } 48 | return cidrs, nil 49 | } 50 | -------------------------------------------------------------------------------- /.github/workflows/dockerhub-push.yml: -------------------------------------------------------------------------------- 1 | name: 🌥 Docker Push 2 | 3 | on: 4 | workflow_run: 5 | workflows: ["🎉 Release Binary"] 6 | types: 7 | - completed 8 | workflow_dispatch: 9 | 10 | jobs: 11 | docker: 12 | runs-on: ubuntu-latest-16-cores 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v3 16 | 17 | - name: Get Github tag 18 | id: meta 19 | run: | 20 | curl --silent "https://api.github.com/repos/projectdiscovery/cdncheck/releases/latest" | jq -r .tag_name | xargs -I {} echo TAG={} >> $GITHUB_OUTPUT 21 | 22 | - name: Set up QEMU 23 | uses: docker/setup-qemu-action@v2 24 | 25 | - name: Set up Docker Buildx 26 | uses: docker/setup-buildx-action@v2 27 | 28 | - name: Login to DockerHub 29 | uses: docker/login-action@v2 30 | with: 31 | username: ${{ secrets.DOCKER_USERNAME }} 32 | password: ${{ secrets.DOCKER_TOKEN }} 33 | 34 | - name: Build and push 35 | uses: docker/build-push-action@v4 36 | with: 37 | context: . 38 | platforms: linux/amd64,linux/arm64,linux/arm 39 | push: true 40 | tags: projectdiscovery/cdncheck:latest,projectdiscovery/cdncheck:${{ steps.meta.outputs.TAG }} -------------------------------------------------------------------------------- /.github/workflows/autorelease-tag.yml: -------------------------------------------------------------------------------- 1 | name: 🔖 Release Tag 2 | 3 | on: 4 | workflow_run: 5 | workflows: ["💡Fingerprint Update"] 6 | types: 7 | - completed 8 | workflow_dispatch: 9 | 10 | jobs: 11 | tag: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | with: 16 | fetch-depth: 0 17 | 18 | - name: Get Commit Count 19 | id: get_commit 20 | run: git rev-list `git rev-list --tags --no-walk --max-count=1`..HEAD --count | xargs -I {} echo COMMIT_COUNT={} >> $GITHUB_OUTPUT 21 | 22 | - name: Create release and tag 23 | if: ${{ steps.get_commit.outputs.COMMIT_COUNT > 0 }} 24 | id: tag_version 25 | uses: mathieudutour/github-tag-action@v6.1 26 | with: 27 | github_token: ${{ secrets.PDTEAMX_PAT }} 28 | 29 | - name: Create a GitHub release 30 | if: ${{ steps.get_commit.outputs.COMMIT_COUNT > 0 }} 31 | uses: actions/create-release@v1 32 | env: 33 | GITHUB_TOKEN: ${{ secrets.PDTEAMX_PAT }} 34 | with: 35 | tag_name: ${{ steps.tag_version.outputs.new_tag }} 36 | release_name: Release ${{ steps.tag_version.outputs.new_tag }} 37 | body: ${{ steps.tag_version.outputs.changelog }} -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | 9 | # Maintain dependencies for go modules 10 | - package-ecosystem: "gomod" 11 | directory: "/" 12 | schedule: 13 | interval: "weekly" 14 | target-branch: "main" 15 | commit-message: 16 | prefix: "chore" 17 | include: "scope" 18 | labels: 19 | - "Type: Maintenance" 20 | allow: 21 | - dependency-name: "github.com/projectdiscovery/*" 22 | 23 | # # Maintain dependencies for docker 24 | # - package-ecosystem: "docker" 25 | # directory: "/" 26 | # schedule: 27 | # interval: "weekly" 28 | # target-branch: "dev" 29 | # commit-message: 30 | # prefix: "chore" 31 | # include: "scope" 32 | # labels: 33 | # - "Type: Maintenance" 34 | # 35 | # # Maintain dependencies for GitHub Actions 36 | # - package-ecosystem: "github-actions" 37 | # directory: "/" 38 | # schedule: 39 | # interval: "weekly" 40 | # target-branch: "dev" 41 | # commit-message: 42 | # prefix: "chore" 43 | # include: "scope" 44 | # labels: 45 | # - "Type: Maintenance" 46 | -------------------------------------------------------------------------------- /internal/testutils/testutils.go: -------------------------------------------------------------------------------- 1 | package testutils 2 | 3 | import ( 4 | "strings" 5 | 6 | errorutils "github.com/projectdiscovery/utils/errors" 7 | ) 8 | 9 | type TestCase struct { 10 | Target string 11 | Args string 12 | Expected []string 13 | CompareFunc func(target string, got []string) error 14 | } 15 | 16 | var TestCases = []TestCase{ 17 | {Target: "52.60.165.183", Expected: []string{"52.60.165.183"}, Args: "-nc"}, 18 | {Target: "projectdiscovery.io", Expected: []string{"projectdiscovery.io"}, Args: "-nc"}, 19 | {Target: "gslink.hackerone.com", Expected: []string{"gslink.hackerone.com"}, Args: "-nc"}, 20 | {Target: "52.60.165.183", Expected: []string{"52.60.165.183 [cloud] [aws]"}, Args: "-resp -nc"}, 21 | {Target: "52.60.165.183", Expected: []string{"52.60.165.183 [cloud] [aws]"}, Args: "-resp -cloud -nc"}, 22 | {Target: "104.16.51.111", Expected: []string{"104.16.51.111 [waf] [cloudflare]"}, Args: "-resp -waf -nc"}, 23 | {Target: "54.192.171.16", Expected: []string{"54.192.171.16 [cdn] [cloudfront]"}, Args: "-resp -cdn -nc"}, 24 | {Target: "185.199.109.153", Expected: []string{}, Args: "-nc"}, 25 | {Target: "185.199.109.153", Expected: []string{}, Args: "-resp -nc"}, 26 | {Target: "54.192.171.16", Expected: []string{"54.192.171.16 [cdn] [cloudfront]"}, Args: "-resp -mcdn cloudfront -nc"}, 27 | {Target: "54.192.171.16", Expected: []string{}, Args: "-resp -fcdn cloudfront -mcloud aws -nc"}, 28 | {Target: "projectdiscovery.io", Expected: nil, Args: "-resp -nc", CompareFunc: func(target string, got []string) error { 29 | cdn := "cloudflare" 30 | if len(got) == 1 && strings.Contains(got[0], cdn) { 31 | return nil 32 | } 33 | return errorutils.New("expected %v belong to %v cdn but got: %v", target, cdn, got) 34 | }}, 35 | } 36 | -------------------------------------------------------------------------------- /cmd/functional-test/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "os" 8 | "strings" 9 | 10 | "github.com/logrusorgru/aurora" 11 | "github.com/pkg/errors" 12 | "github.com/projectdiscovery/cdncheck/internal/testutils" 13 | ) 14 | 15 | var ( 16 | debug = os.Getenv("DEBUG") == "true" 17 | success = aurora.Green("[✓]").String() 18 | failed = aurora.Red("[✘]").String() 19 | errored = false 20 | devCdncheckBinary = flag.String("dev", "", "Dev Branch Cdncheck Binary") 21 | ) 22 | 23 | func main() { 24 | flag.Parse() 25 | if err := runFunctionalTests(); err != nil { 26 | log.Fatalf("Could not run functional tests: %s\n", err) 27 | } 28 | if errored { 29 | os.Exit(1) 30 | } 31 | } 32 | 33 | func runFunctionalTests() error { 34 | for _, testcase := range testutils.TestCases { 35 | if err := runIndividualTestCase(testcase); err != nil { 36 | errored = true 37 | fmt.Fprintf(os.Stderr, "%s Test \"%s\" failed: %s\n", failed, testcase.Target, err) 38 | } else { 39 | fmt.Printf("%s Test \"%s\" passed!\n", success, testcase.Target) 40 | } 41 | } 42 | return nil 43 | } 44 | 45 | func runIndividualTestCase(testcase testutils.TestCase) error { 46 | argsParts := strings.Fields(testcase.Args) 47 | devOutput, err := testutils.RunCdncheckBinaryAndGetResults(testcase.Target, *devCdncheckBinary, debug, argsParts) 48 | if err != nil { 49 | return errors.Wrap(err, "could not run Cdncheck dev test") 50 | } 51 | if testcase.CompareFunc != nil { 52 | return testcase.CompareFunc(testcase.Target, devOutput) 53 | } 54 | if !testutils.CompareOutput(devOutput, testcase.Expected) { 55 | return errors.Errorf("expected output %s, got %s", testcase.Expected, devOutput) 56 | } 57 | return nil 58 | } 59 | -------------------------------------------------------------------------------- /types.go: -------------------------------------------------------------------------------- 1 | package cdncheck 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/yl2chen/cidranger" 7 | ) 8 | 9 | // InputCompiled contains a compiled list of input structure 10 | type InputCompiled struct { 11 | // CDN contains a list of ranges for CDN cidrs 12 | CDN map[string][]string `yaml:"cdn,omitempty" json:"cdn,omitempty"` 13 | // WAF contains a list of ranges for WAF cidrs 14 | WAF map[string][]string `yaml:"waf,omitempty" json:"waf,omitempty"` 15 | // Cloud contains a list of ranges for Cloud cidrs 16 | Cloud map[string][]string `yaml:"cloud,omitempty" json:"cloud,omitempty"` 17 | // Common contains a list of suffixes for major sources 18 | Common map[string][]string `yaml:"common,omitempty" json:"common,omitempty"` 19 | } 20 | 21 | // providerScraper is a structure for scraping providers 22 | type providerScraper struct { 23 | rangers map[string]cidranger.Ranger 24 | } 25 | 26 | // newProviderScraper returns a new provider scraper instance 27 | func newProviderScraper(ranges map[string][]string) *providerScraper { 28 | scraper := &providerScraper{rangers: make(map[string]cidranger.Ranger)} 29 | 30 | for provider, items := range ranges { 31 | ranger := cidranger.NewPCTrieRanger() 32 | for _, cidr := range items { 33 | if _, network, err := net.ParseCIDR(cidr); err == nil { 34 | _ = ranger.Insert(cidranger.NewBasicRangerEntry(*network)) 35 | } 36 | } 37 | scraper.rangers[provider] = ranger 38 | } 39 | return scraper 40 | } 41 | 42 | // Match returns true if the IP matches provided CIDR ranges 43 | func (p *providerScraper) Match(ip net.IP) (bool, string, error) { 44 | for provider, ranger := range p.rangers { 45 | if contains, err := ranger.Contains(ip); contains { 46 | return true, provider, err 47 | } 48 | } 49 | return false, "", nil 50 | } 51 | -------------------------------------------------------------------------------- /other.go: -------------------------------------------------------------------------------- 1 | package cdncheck 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/pkg/errors" 7 | "github.com/weppos/publicsuffix-go/publicsuffix" 8 | ) 9 | 10 | var suffixToSource map[string]string 11 | 12 | // cdnWappalyzerTechnologies contains a map of wappalyzer technologies to cdns 13 | var cdnWappalyzerTechnologies = map[string]string{ 14 | "imperva": "imperva", 15 | "incapsula": "incapsula", 16 | "cloudflare": "cloudflare", 17 | "cloudfront": "amazon", 18 | "akamai": "akamai", 19 | } 20 | 21 | // CheckFQDN checks if fqdns are known cloud ones 22 | func (c *Client) CheckSuffix(fqdns ...string) (isCDN bool, provider string, itemType string, err error) { 23 | c.Once.Do(func() { 24 | suffixToSource = make(map[string]string) 25 | for source, suffixes := range generatedData.Common { 26 | for _, suffix := range suffixes { 27 | suffixToSource[suffix] = source 28 | } 29 | } 30 | }) 31 | for _, fqdn := range fqdns { 32 | parsed, err := publicsuffix.Parse(fqdn) 33 | if err != nil { 34 | return false, "", "", errors.Wrap(err, "could not parse fqdn") 35 | } 36 | if discovered, ok := suffixToSource[parsed.TLD]; ok { 37 | return true, discovered, "waf", nil 38 | } 39 | domain := parsed.SLD + "." + parsed.TLD 40 | if discovered, ok := suffixToSource[domain]; ok { 41 | return true, discovered, "waf", nil 42 | } 43 | } 44 | return false, "", "", nil 45 | } 46 | 47 | // CheckWappalyzer checks if the wappalyzer detection are a part of CDN 48 | func (c *Client) CheckWappalyzer(data map[string]struct{}) (isCDN bool, provider string, err error) { 49 | for technology := range data { 50 | if strings.Contains(technology, ":") { 51 | if parts := strings.SplitN(technology, ":", 2); len(parts) == 2 { 52 | technology = parts[0] 53 | } 54 | } 55 | technology = strings.ToLower(technology) 56 | if discovered, ok := cdnWappalyzerTechnologies[technology]; ok { 57 | return true, discovered, nil 58 | } 59 | } 60 | return false, "", nil 61 | } 62 | -------------------------------------------------------------------------------- /other_test.go: -------------------------------------------------------------------------------- 1 | package cdncheck 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/projectdiscovery/retryabledns" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestCheckSuffix(t *testing.T) { 11 | client := New() 12 | 13 | valid, provider, _, err := client.CheckSuffix("test.cloudfront.net") 14 | require.Nil(t, err, "could not check cname") 15 | require.True(t, valid, "could not get valid cname") 16 | require.Equal(t, "amazon", provider, "could not get correct provider") 17 | 18 | valid, _, _, err = client.CheckSuffix("test.provider.net") 19 | require.Nil(t, err, "could not check cname") 20 | require.False(t, valid, "could get valid cname") 21 | } 22 | 23 | func TestCheckWappalyzer(t *testing.T) { 24 | client := New() 25 | 26 | valid, provider, err := client.CheckWappalyzer(map[string]struct{}{"imperva": {}}) 27 | require.Nil(t, err, "could not check wappalyzer") 28 | require.True(t, valid, "could not get valid cname") 29 | require.Equal(t, "imperva", provider, "could not get correct provider") 30 | 31 | valid, provider, err = client.CheckWappalyzer(map[string]struct{}{"imperva:4.5.6": {}}) 32 | require.Nil(t, err, "could not check wappalyzer") 33 | require.True(t, valid, "could not get valid cname") 34 | require.Equal(t, "imperva", provider, "could not get correct provider") 35 | 36 | valid, _, err = client.CheckWappalyzer(map[string]struct{}{"php": {}}) 37 | require.Nil(t, err, "could not check cname") 38 | require.False(t, valid, "could get valid cname") 39 | } 40 | 41 | func TestCheckDomainWithFallback(t *testing.T) { 42 | client := New() 43 | 44 | valid, provider, itemType, err := client.CheckDomainWithFallback("www.gap.com") 45 | require.Nil(t, err, "could not check") 46 | require.True(t, valid, "could not check domain") 47 | require.Equal(t, "akamai", provider, "could not get correct provider") 48 | require.Equal(t, "waf", itemType, "could not get correct itemType") 49 | } 50 | 51 | func TestCheckDNSResponse(t *testing.T) { 52 | client := New() 53 | defaultResolvers := []string{"8.8.8.8", "8.8.0.0"} 54 | defaultMaxRetries := 3 55 | retryabledns, _ := retryabledns.New(defaultResolvers, defaultMaxRetries) 56 | dnsData, _ := retryabledns.Resolve("hackerone.com") 57 | 58 | valid, provider, itemType, err := client.CheckDNSResponse(dnsData) 59 | 60 | require.Nil(t, err, "could not check cname") 61 | require.True(t, valid, "could not get valid cname") 62 | require.Equal(t, "cloudflare", provider, "could not get correct provider") 63 | require.Equal(t, "waf", itemType, "could not get correct itemType") 64 | 65 | dnsData, _ = retryabledns.CNAME("www.gap.com") 66 | 67 | valid, provider, itemType, err = client.CheckDNSResponse(dnsData) 68 | require.Nil(t, err, "could not check") 69 | require.True(t, valid, "could not check domain") 70 | require.Equal(t, "akamai", provider, "could not get correct provider") 71 | require.Equal(t, "waf", itemType, "could not get correct itemType") 72 | 73 | } 74 | -------------------------------------------------------------------------------- /cmd/generate-index/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "fmt" 7 | "log" 8 | "os" 9 | 10 | "github.com/pkg/errors" 11 | "github.com/projectdiscovery/cdncheck" 12 | "github.com/projectdiscovery/cdncheck/generate" 13 | "gopkg.in/yaml.v3" 14 | ) 15 | 16 | var ( 17 | input = flag.String("input", "provider.yaml", "provider file for processing") 18 | output = flag.String("output", "sources_data.json", "output file for generated sources") 19 | token = flag.String("token", "", "Token for the ipinfo service") 20 | ) 21 | 22 | func main() { 23 | flag.Parse() 24 | 25 | if err := process(); err != nil { 26 | log.Fatalf("[error] Could not process: %s\n", err) 27 | } 28 | } 29 | 30 | func process() error { 31 | options := &generate.Options{} 32 | options.ParseFromEnv() 33 | if *token != "" && options.IPInfoToken == "" { 34 | options.IPInfoToken = *token 35 | } 36 | 37 | categories, err := parseCategoriesFromFile() 38 | if err != nil { 39 | return err 40 | } 41 | 42 | compiled, err := categories.Compile(options) 43 | if err != nil { 44 | return err 45 | } 46 | 47 | outputFile, err := os.Create(*output) 48 | if err != nil { 49 | return errors.Wrap(err, "could not create output file") 50 | } 51 | defer outputFile.Close() 52 | 53 | data := cdncheck.InputCompiled{} 54 | if len(compiled.Common) > 0 { 55 | for provider, items := range compiled.Common { 56 | fmt.Printf("[common/fqdn] Defined %d items for %s\n", len(items), provider) 57 | } 58 | data.Common = compiled.Common 59 | } 60 | if len(compiled.CDN) > 0 { 61 | for provider, items := range compiled.CDN { 62 | fmt.Printf("[cdn] Got %d items for %s\n", len(items), provider) 63 | } 64 | data.CDN = compiled.CDN 65 | } 66 | 67 | if len(compiled.WAF) > 0 { 68 | for provider, items := range compiled.WAF { 69 | fmt.Printf("[waf] Got %d items for %s\n", len(items), provider) 70 | } 71 | data.WAF = compiled.WAF 72 | } 73 | 74 | if len(compiled.Cloud) > 0 { 75 | for provider, items := range compiled.Cloud { 76 | fmt.Printf("[cloud] Got %d items for %s\n", len(items), provider) 77 | } 78 | data.Cloud = compiled.Cloud 79 | } 80 | jsonData, err := json.Marshal(data) 81 | if err != nil { 82 | return errors.Wrap(err, "could not marshal json") 83 | } 84 | _, err = outputFile.Write(jsonData) 85 | if err != nil { 86 | return errors.Wrap(err, "could not write to output file") 87 | } 88 | return nil 89 | } 90 | 91 | func parseCategoriesFromFile() (*generate.Categories, error) { 92 | file, err := os.Open(*input) 93 | if err != nil { 94 | return nil, errors.Wrap(err, "could not read input.yaml file") 95 | } 96 | defer file.Close() 97 | 98 | categories := &generate.Categories{} 99 | if err := yaml.NewDecoder(file).Decode(categories); err != nil { 100 | return nil, errors.Wrap(err, "could not decode input.yaml file") 101 | } 102 | return categories, nil 103 | } 104 | -------------------------------------------------------------------------------- /cmd/generate-index/provider.yaml: -------------------------------------------------------------------------------- 1 | # provider.yaml contains the inputs for the generate-index 2 | # command. It is used to generate compiled CIDR ranges for checking. 3 | 4 | # cdn contains the inputs for cdn checking 5 | cdn: 6 | # asn contains the ASN numbers for providers 7 | asn: 8 | leaseweb: 9 | - AS60626 10 | 11 | # urls contains a list of URLs for CDN providers 12 | urls: 13 | cloudfront: 14 | - https://d7uri8nf7uskq.cloudfront.net/tools/list-cloudfront-ips 15 | fastly: 16 | - https://api.fastly.com/public-ip-list 17 | google: 18 | - https://www.gstatic.com/ipranges/goog.json 19 | stackpath: 20 | - https://k3t9x2h3.map2.ssl.hwcdn.net/ipblocks.txt 21 | 22 | # waf contains the inputs for WAF CIDR checking 23 | waf: 24 | # asn contains the ASN numbers for providers 25 | asn: 26 | akamai: 27 | - AS12222 28 | - AS16625 29 | - AS16702 30 | - AS17204 31 | - AS18680 32 | - AS18717 33 | - AS20189 34 | - AS20940 35 | - AS21342 36 | - AS21357 37 | - AS21399 38 | - AS22207 39 | - AS22452 40 | - AS23454 41 | - AS23455 42 | - AS23903 43 | - AS24319 44 | - AS26008 45 | - AS30675 46 | - AS31107 47 | - AS31108 48 | - AS31109 49 | - AS31110 50 | - AS31377 51 | - AS33047 52 | - AS33905 53 | - AS34164 54 | - AS34850 55 | - AS35204 56 | - AS35993 57 | - AS35994 58 | - AS36183 59 | - AS39836 60 | - AS43639 61 | - AS55409 62 | - AS55770 63 | - AS63949 64 | - AS133103 65 | - AS393560 66 | 67 | sucuri: 68 | - AS30148 69 | 70 | 71 | 72 | # urls contains a list of URLs for WAF providers 73 | urls: 74 | cloudflare: 75 | - https://api.cloudflare.com/client/v4/ips 76 | 77 | # cloud contains the inputs for cloud CIDR checking 78 | cloud: 79 | # urls contains a list of URLs for cloud providers 80 | urls: 81 | aws: 82 | - https://ip-ranges.amazonaws.com/ip-ranges.json 83 | google: 84 | - https://www.gstatic.com/ipranges/cloud.json 85 | oracle: 86 | - https://docs.oracle.com/en-us/iaas/tools/public_ip_ranges.json 87 | azure: 88 | - https://www.microsoft.com/en-us/download/confirmation.aspx?id=56519 89 | zscaler: 90 | - https://api.config.zscaler.com/zscaler.net/cenr/json 91 | office365: 92 | - https://endpoints.office.com/endpoints/worldwide?clientrequestid=b10c5ed1-bad1-445f-b386-b919946339a7 93 | 94 | 95 | # common contains common items to all previous categories 96 | common: 97 | fqdn: 98 | amazon: 99 | - cloudfront.net 100 | - amazonaws.com 101 | akamai: 102 | - edgekey.net 103 | - akamaiedge.net 104 | - akamaitechnologies.com 105 | - akamaihd.net 106 | - edgesuite.net 107 | cloudflare: 108 | - cloudflare.com 109 | fastly: 110 | - fastly.net 111 | edgecast: 112 | - edgecastcdn.net 113 | - edgesuite.net 114 | incapsula: 115 | - impervadns.net 116 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/projectdiscovery/cdncheck 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/PuerkitoBio/goquery v1.8.1 7 | github.com/ipinfo/go/v2 v2.9.2 8 | github.com/logrusorgru/aurora v2.0.3+incompatible 9 | github.com/pkg/errors v0.9.1 10 | github.com/projectdiscovery/goflags v0.1.23 11 | github.com/projectdiscovery/gologger v1.1.11 12 | github.com/projectdiscovery/mapcidr v1.1.11 13 | github.com/projectdiscovery/retryabledns v1.0.38 14 | github.com/projectdiscovery/utils v0.0.57 15 | github.com/stretchr/testify v1.8.4 16 | github.com/weppos/publicsuffix-go v0.30.1 17 | github.com/yl2chen/cidranger v1.0.2 18 | gopkg.in/yaml.v3 v3.0.1 19 | ) 20 | 21 | require ( 22 | aead.dev/minisign v0.2.0 // indirect 23 | github.com/Masterminds/semver/v3 v3.2.1 // indirect 24 | github.com/VividCortex/ewma v1.2.0 // indirect 25 | github.com/alecthomas/chroma v0.10.0 // indirect 26 | github.com/andybalholm/cascadia v1.3.1 // indirect 27 | github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect 28 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 29 | github.com/aymerick/douceur v0.2.0 // indirect 30 | github.com/charmbracelet/glamour v0.6.0 // indirect 31 | github.com/cheggaaa/pb/v3 v3.1.4 // indirect 32 | github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08 // indirect 33 | github.com/davecgh/go-spew v1.1.1 // indirect 34 | github.com/denisbrodbeck/machineid v1.0.1 // indirect 35 | github.com/dlclark/regexp2 v1.8.1 // indirect 36 | github.com/dsnet/compress v0.0.1 // indirect 37 | github.com/fatih/color v1.15.0 // indirect 38 | github.com/golang/protobuf v1.5.3 // indirect 39 | github.com/golang/snappy v0.0.4 // indirect 40 | github.com/google/go-github/v30 v30.1.0 // indirect 41 | github.com/google/go-querystring v1.1.0 // indirect 42 | github.com/gorilla/css v1.0.0 // indirect 43 | github.com/json-iterator/go v1.1.12 // indirect 44 | github.com/kr/pretty v0.3.1 // indirect 45 | github.com/lucasb-eyer/go-colorful v1.2.0 // indirect 46 | github.com/mattn/go-colorable v0.1.13 // indirect 47 | github.com/mattn/go-isatty v0.0.19 // indirect 48 | github.com/mattn/go-runewidth v0.0.14 // indirect 49 | github.com/mholt/archiver v3.1.1+incompatible // indirect 50 | github.com/microcosm-cc/bluemonday v1.0.25 // indirect 51 | github.com/miekg/dns v1.1.56 // indirect 52 | github.com/minio/selfupdate v0.6.0 // indirect 53 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 54 | github.com/modern-go/reflect2 v1.0.2 // indirect 55 | github.com/muesli/reflow v0.3.0 // indirect 56 | github.com/muesli/termenv v0.15.1 // indirect 57 | github.com/nwaples/rardecode v1.1.3 // indirect 58 | github.com/olekukonko/tablewriter v0.0.5 // indirect 59 | github.com/patrickmn/go-cache v2.1.0+incompatible // indirect 60 | github.com/pierrec/lz4 v2.6.1+incompatible // indirect 61 | github.com/pmezard/go-difflib v1.0.0 // indirect 62 | github.com/projectdiscovery/blackrock v0.0.1 // indirect 63 | github.com/rivo/uniseg v0.4.4 // indirect 64 | github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect 65 | github.com/ulikunitz/xz v0.5.11 // indirect 66 | github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect 67 | github.com/yuin/goldmark v1.5.4 // indirect 68 | github.com/yuin/goldmark-emoji v1.0.1 // indirect 69 | go.uber.org/multierr v1.11.0 // indirect 70 | golang.org/x/crypto v0.13.0 // indirect 71 | golang.org/x/exp v0.0.0-20230420155640-133eef4313cb // indirect 72 | golang.org/x/mod v0.12.0 // indirect 73 | golang.org/x/net v0.15.0 // indirect 74 | golang.org/x/oauth2 v0.11.0 // indirect 75 | golang.org/x/sync v0.3.0 // indirect 76 | golang.org/x/sys v0.12.0 // indirect 77 | golang.org/x/text v0.13.0 // indirect 78 | golang.org/x/tools v0.13.0 // indirect 79 | google.golang.org/appengine v1.6.7 // indirect 80 | google.golang.org/protobuf v1.31.0 // indirect 81 | gopkg.in/djherbis/times.v1 v1.3.0 // indirect 82 | ) 83 | -------------------------------------------------------------------------------- /cdncheck.go: -------------------------------------------------------------------------------- 1 | package cdncheck 2 | 3 | import ( 4 | "net" 5 | "strings" 6 | "sync" 7 | 8 | "github.com/projectdiscovery/retryabledns" 9 | ) 10 | 11 | var ( 12 | DefaultCDNProviders string 13 | DefaultWafProviders string 14 | DefaultCloudProviders string 15 | ) 16 | 17 | // DefaultResolvers trusted (taken from fastdialer) 18 | var DefaultResolvers = []string{ 19 | "180.76.76.76:53", 20 | "112.124.47.27:53", 21 | "1.1.1.1:53", 22 | "223.5.5.5:53", 23 | "223.6.6.6:53", 24 | "1.0.0.1:53", 25 | "8.8.8.8:53", 26 | "8.8.4.4:53", 27 | } 28 | 29 | // Client checks for CDN based IPs which should be excluded 30 | // during scans since they belong to third party firewalls. 31 | type Client struct { 32 | sync.Once 33 | cdn *providerScraper 34 | waf *providerScraper 35 | cloud *providerScraper 36 | retriabledns *retryabledns.Client 37 | } 38 | 39 | // New creates cdncheck client with default options 40 | // NewWithOpts should be preferred over this function 41 | func New() *Client { 42 | client, _ := NewWithOpts(3, []string{}) 43 | return client 44 | } 45 | 46 | // NewWithOpts creates cdncheck client with custom options 47 | func NewWithOpts(MaxRetries int, resolvers []string) (*Client, error) { 48 | if MaxRetries <= 0 { 49 | MaxRetries = 3 50 | } 51 | if len(resolvers) == 0 { 52 | resolvers = DefaultResolvers 53 | } 54 | retryabledns, err := retryabledns.New(resolvers, MaxRetries) 55 | if err != nil { 56 | return nil, err 57 | } 58 | client := &Client{ 59 | cdn: newProviderScraper(generatedData.CDN), 60 | waf: newProviderScraper(generatedData.WAF), 61 | cloud: newProviderScraper(generatedData.Cloud), 62 | retriabledns: retryabledns, 63 | } 64 | return client, nil 65 | } 66 | 67 | // CheckCDN checks if an IP is contained in the cdn denylist 68 | func (c *Client) CheckCDN(ip net.IP) (matched bool, value string, err error) { 69 | matched, value, err = c.cdn.Match(ip) 70 | return matched, value, err 71 | } 72 | 73 | // CheckWAF checks if an IP is contained in the waf denylist 74 | func (c *Client) CheckWAF(ip net.IP) (matched bool, value string, err error) { 75 | matched, value, err = c.waf.Match(ip) 76 | return matched, value, err 77 | } 78 | 79 | // CheckCloud checks if an IP is contained in the cloud denylist 80 | func (c *Client) CheckCloud(ip net.IP) (matched bool, value string, err error) { 81 | matched, value, err = c.cloud.Match(ip) 82 | return matched, value, err 83 | } 84 | 85 | // Check checks if ip belongs to one of CDN, WAF and Cloud . It is generic method for Checkxxx methods 86 | func (c *Client) Check(ip net.IP) (matched bool, value string, itemType string, err error) { 87 | if matched, value, err = c.cdn.Match(ip); err == nil && matched && value != "" { 88 | return matched, value, "cdn", nil 89 | } 90 | if matched, value, err = c.waf.Match(ip); err == nil && matched && value != "" { 91 | return matched, value, "waf", nil 92 | } 93 | if matched, value, err = c.cloud.Match(ip); err == nil && matched && value != "" { 94 | return matched, value, "cloud", nil 95 | } 96 | return false, "", "", err 97 | } 98 | 99 | // Check Domain with fallback checks if domain belongs to one of CDN, WAF and Cloud . It is generic method for Checkxxx methods 100 | // Since input is domain, as a fallback it queries CNAME records and checks if domain is WAF 101 | func (c *Client) CheckDomainWithFallback(domain string) (matched bool, value string, itemType string, err error) { 102 | dnsData, err := c.retriabledns.Resolve(domain) 103 | if err != nil { 104 | return false, "", "", err 105 | } 106 | matched, value, itemType, err = c.CheckDNSResponse(dnsData) 107 | if err != nil { 108 | return false, "", "", err 109 | } 110 | if matched { 111 | return matched, value, itemType, nil 112 | } 113 | // resolve cname 114 | dnsData, err = c.retriabledns.CNAME(domain) 115 | if err != nil { 116 | return false, "", "", err 117 | } 118 | return c.CheckDNSResponse(dnsData) 119 | } 120 | 121 | // CheckDNSResponse is same as CheckDomainWithFallback but takes DNS response as input 122 | func (c *Client) CheckDNSResponse(dnsResponse *retryabledns.DNSData) (matched bool, value string, itemType string, err error) { 123 | if dnsResponse.A != nil { 124 | for _, ip := range dnsResponse.A { 125 | ipAddr := net.ParseIP(ip) 126 | if ipAddr == nil { 127 | continue 128 | } 129 | matched, value, itemType, err := c.Check(ipAddr) 130 | if err != nil { 131 | return false, "", "", err 132 | } 133 | if matched { 134 | return matched, value, itemType, nil 135 | } 136 | } 137 | } 138 | if dnsResponse.CNAME != nil { 139 | matched, discovered, itemType, err := c.CheckSuffix(dnsResponse.CNAME...) 140 | if err != nil { 141 | return false, "", itemType, err 142 | } 143 | if matched { 144 | // for now checkSuffix only checks for wafs 145 | return matched, discovered, itemType, nil 146 | } 147 | } 148 | return false, "", "", err 149 | } 150 | 151 | func mapKeys(m map[string][]string) string { 152 | keys := make([]string, 0, len(m)) 153 | for k := range m { 154 | keys = append(keys, k) 155 | } 156 | return strings.Join(keys, ", ") 157 | } 158 | -------------------------------------------------------------------------------- /generate/input.go: -------------------------------------------------------------------------------- 1 | package generate 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "log" 9 | "net/http" 10 | "regexp" 11 | 12 | "github.com/PuerkitoBio/goquery" 13 | "github.com/ipinfo/go/v2/ipinfo" 14 | "github.com/projectdiscovery/cdncheck" 15 | stringsutil "github.com/projectdiscovery/utils/strings" 16 | ) 17 | 18 | var cidrRegex = regexp.MustCompile(`[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\/[0-9]{1,3}`) 19 | 20 | // Compile returns the compiled form of an input structure 21 | func (c *Categories) Compile(options *Options) (*cdncheck.InputCompiled, error) { 22 | compiled := &cdncheck.InputCompiled{ 23 | CDN: make(map[string][]string), 24 | WAF: make(map[string][]string), 25 | Cloud: make(map[string][]string), 26 | Common: make(map[string][]string), 27 | } 28 | // Fetch input items specified 29 | if c.CDN != nil { 30 | if err := c.CDN.fetchInputItem(options, compiled.CDN); err != nil { 31 | log.Printf("[err] could not fetch cdn item: %s\n", err) 32 | } 33 | } 34 | if c.WAF != nil { 35 | if err := c.WAF.fetchInputItem(options, compiled.WAF); err != nil { 36 | log.Printf("[err] could not fetch waf item: %s\n", err) 37 | } 38 | } 39 | if c.Cloud != nil { 40 | if err := c.Cloud.fetchInputItem(options, compiled.Cloud); err != nil { 41 | log.Printf("[err] could not fetch cloud item: %s\n", err) 42 | } 43 | } 44 | if c.Common != nil { 45 | compiled.Common = c.Common.FQDN 46 | } 47 | 48 | // Fetch custom scraper data and merge 49 | for dataType, scraper := range scraperTypeToScraperMap { 50 | var data map[string][]string 51 | 52 | switch dataType { 53 | case "cdn": 54 | data = compiled.CDN 55 | case "waf": 56 | data = compiled.WAF 57 | case "cloud": 58 | data = compiled.Cloud 59 | default: 60 | panic(fmt.Sprintf("invalid datatype %s specified", dataType)) 61 | } 62 | for _, item := range scraper { 63 | if response, err := item.scraper(http.DefaultClient); err != nil { 64 | log.Printf("[err] could not scrape %s item: %s\n", item.name, err) 65 | } else { 66 | data[item.name] = response 67 | } 68 | } 69 | } 70 | return compiled, nil 71 | } 72 | 73 | // fetchInputItem fetches input items and writes data to map 74 | func (c *Category) fetchInputItem(options *Options, data map[string][]string) error { 75 | for provider, cidrs := range c.CIDR { 76 | data[provider] = cidrs 77 | } 78 | for provider, urls := range c.URLs { 79 | for _, item := range urls { 80 | if cidrs, err := getCIDRFromURL(item); err != nil { 81 | return fmt.Errorf("could not get url %s: %s", item, err) 82 | } else { 83 | data[provider] = cidrs 84 | } 85 | } 86 | } 87 | // Only scrape ASN if we have an ID 88 | if !options.HasAuthInfo() { 89 | return nil 90 | } 91 | for provider, asn := range c.ASN { 92 | for _, item := range asn { 93 | if cidrs, err := getIpInfoASN(http.DefaultClient, options.IPInfoToken, item); err != nil { 94 | return fmt.Errorf("could not get asn %s: %s", item, err) 95 | } else { 96 | data[provider] = cidrs 97 | } 98 | } 99 | } 100 | return nil 101 | } 102 | 103 | var errNoCidrFound = errors.New("no cidrs found for url") 104 | 105 | // getIpInfoASN returns cidrs for an ASN from ipinfo using a token 106 | func getIpInfoASN(httpClient *http.Client, token string, asn string) ([]string, error) { 107 | if token == "" { 108 | return nil, errors.New("ipinfo auth token not specified") 109 | } 110 | ipinfoClient := ipinfo.NewClient(httpClient, nil, token) 111 | info, err := ipinfoClient.GetASNDetails(asn) 112 | if err != nil { 113 | return nil, err 114 | } 115 | if info == nil { 116 | return nil, errNoCidrFound 117 | } 118 | var cidrs []string 119 | for _, prefix := range info.Prefixes { 120 | cidrs = append(cidrs, prefix.Netblock) 121 | } 122 | if len(cidrs) == 0 { 123 | return nil, errNoCidrFound 124 | } 125 | return cidrs, nil 126 | } 127 | 128 | // getCIDRFromURL scrapes CIDR ranges for a URL using a regex 129 | func getCIDRFromURL(URL string) ([]string, error) { 130 | retried := false 131 | retry: 132 | req, err := http.NewRequest(http.MethodGet, URL, nil) 133 | if err != nil { 134 | return nil, err 135 | } 136 | resp, err := http.DefaultClient.Do(req) 137 | if err != nil { 138 | return nil, err 139 | } 140 | defer resp.Body.Close() 141 | 142 | data, err := io.ReadAll(resp.Body) 143 | if err != nil { 144 | return nil, err 145 | } 146 | // if the body type is HTML, retry with the first json link in the page (special case for Azure download page to find changing URLs) 147 | if resp.Header.Get("Content-Type") == "text/html" && !retried { 148 | var extractedURL string 149 | docReader, err := goquery.NewDocumentFromReader(bytes.NewReader(data)) 150 | if err != nil { 151 | return nil, err 152 | } 153 | docReader.Find("a").Each(func(i int, item *goquery.Selection) { 154 | src, ok := item.Attr("href") 155 | if ok && stringsutil.ContainsAny(src, "ServiceTags_Public_") && extractedURL == "" { 156 | extractedURL = src 157 | } 158 | }) 159 | URL = extractedURL 160 | retried = true 161 | goto retry 162 | } 163 | 164 | body := string(data) 165 | 166 | cidrs := cidrRegex.FindAllString(body, -1) 167 | if len(cidrs) == 0 { 168 | return nil, errNoCidrFound 169 | } 170 | return cidrs, nil 171 | } 172 | -------------------------------------------------------------------------------- /internal/runner/options.go: -------------------------------------------------------------------------------- 1 | package runner 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "time" 7 | 8 | "github.com/logrusorgru/aurora" 9 | "github.com/projectdiscovery/cdncheck" 10 | "github.com/projectdiscovery/goflags" 11 | "github.com/projectdiscovery/gologger" 12 | "github.com/projectdiscovery/gologger/formatter" 13 | "github.com/projectdiscovery/gologger/levels" 14 | fileutil "github.com/projectdiscovery/utils/file" 15 | updateutils "github.com/projectdiscovery/utils/update" 16 | ) 17 | 18 | type Output struct { 19 | aurora *aurora.Aurora 20 | Timestamp time.Time `json:"timestamp,omitempty"` 21 | Input string `json:"input"` 22 | IP string `json:"ip"` 23 | Cdn bool `json:"cdn,omitempty"` 24 | CdnName string `json:"cdn_name,omitempty"` 25 | Cloud bool `json:"cloud,omitempty"` 26 | CloudName string `json:"cloud_name,omitempty"` 27 | Waf bool `json:"waf,omitempty"` 28 | WafName string `json:"waf_name,omitempty"` 29 | itemType string 30 | } 31 | 32 | func (o *Output) String() string { 33 | sw := *o.aurora 34 | commonName := "[%s]" 35 | itemType := fmt.Sprintf("[%s]", o.itemType) 36 | switch o.itemType { 37 | case "cdn": 38 | commonName = fmt.Sprintf(commonName, o.CdnName) 39 | itemType = sw.BrightBlue(itemType).String() 40 | case "cloud": 41 | commonName = fmt.Sprintf(commonName, o.CloudName) 42 | itemType = sw.BrightGreen(itemType).String() 43 | case "waf": 44 | commonName = fmt.Sprintf(commonName, o.WafName) 45 | itemType = sw.Yellow(itemType).String() 46 | } 47 | commonName = sw.BrightYellow(commonName).String() 48 | return fmt.Sprintf("%s %s %s", o.Input, itemType, commonName) 49 | } 50 | func (o *Output) StringIP() string { 51 | return o.IP 52 | } 53 | 54 | type Options struct { 55 | Inputs goflags.StringSlice 56 | Response bool 57 | HasStdin bool 58 | Output string 59 | Version bool 60 | Json bool 61 | Cdn bool 62 | Cloud bool 63 | Waf bool 64 | Exclude bool 65 | Verbose bool 66 | NoColor bool 67 | Silent bool 68 | Debug bool 69 | DisableUpdateCheck bool 70 | MatchCdn goflags.StringSlice 71 | MatchCloud goflags.StringSlice 72 | MatchWaf goflags.StringSlice 73 | FilterCdn goflags.StringSlice 74 | FilterCloud goflags.StringSlice 75 | FilterWaf goflags.StringSlice 76 | Resolvers goflags.StringSlice 77 | OnResult func(r Output) 78 | MaxRetries int 79 | } 80 | 81 | // configureOutput configures the output logging levels to be displayed on the screen 82 | func configureOutput(options *Options) { 83 | if options.Silent { 84 | gologger.DefaultLogger.SetMaxLevel(levels.LevelSilent) 85 | } else if options.Verbose { 86 | gologger.DefaultLogger.SetMaxLevel(levels.LevelWarning) 87 | } else if options.Debug { 88 | gologger.DefaultLogger.SetMaxLevel(levels.LevelDebug) 89 | } else { 90 | gologger.DefaultLogger.SetMaxLevel(levels.LevelInfo) 91 | } 92 | 93 | if options.NoColor { 94 | gologger.DefaultLogger.SetFormatter(formatter.NewCLI(true)) 95 | } 96 | } 97 | 98 | func ParseOptions() *Options { 99 | opts, err := readFlags() 100 | if err != nil { 101 | gologger.Fatal().Msgf("Program Exiting: %s\n", err) 102 | } 103 | return opts 104 | } 105 | 106 | // readFlags reads the flags and options for the utility 107 | func readFlags() (*Options, error) { 108 | opts := &Options{} 109 | opts.HasStdin = fileutil.HasStdin() 110 | 111 | flagSet := goflags.NewFlagSet() 112 | flagSet.SetDescription("cdncheck is a tool for identifying the technology associated with dns / ip network addresses.") 113 | 114 | flagSet.CreateGroup("input", "INPUT", 115 | flagSet.StringSliceVarP(&opts.Inputs, "input", "i", nil, "list of ip / dns to process", goflags.FileCommaSeparatedStringSliceOptions), 116 | ) 117 | 118 | flagSet.CreateGroup("detection", "DETECTION", 119 | flagSet.BoolVarP(&opts.Cdn, "cdn", "", false, "display only cdn in cli output"), 120 | flagSet.BoolVarP(&opts.Cloud, "cloud", "", false, "display only cloud in cli output"), 121 | flagSet.BoolVarP(&opts.Waf, "waf", "", false, "display only waf in cli output"), 122 | ) 123 | 124 | flagSet.CreateGroup("matcher", "MATCHER", 125 | flagSet.StringSliceVarP(&opts.MatchCdn, "match-cdn", "mcdn", nil, fmt.Sprintf("match host with specified cdn provider (%s)", cdncheck.DefaultCDNProviders), goflags.CommaSeparatedStringSliceOptions), 126 | flagSet.StringSliceVarP(&opts.MatchCloud, "match-cloud", "mcloud", nil, fmt.Sprintf("match host with specified cloud provider (%s)", cdncheck.DefaultCloudProviders), goflags.CommaSeparatedStringSliceOptions), 127 | flagSet.StringSliceVarP(&opts.MatchWaf, "match-waf", "mwaf", nil, fmt.Sprintf("match host with specified waf provider (%s)", cdncheck.DefaultWafProviders), goflags.CommaSeparatedStringSliceOptions), 128 | ) 129 | 130 | flagSet.CreateGroup("filter", "FILTER", 131 | flagSet.StringSliceVarP(&opts.FilterCdn, "filter-cdn", "fcdn", nil, fmt.Sprintf("filter host with specified cdn provider (%s)", cdncheck.DefaultCDNProviders), goflags.CommaSeparatedStringSliceOptions), 132 | flagSet.StringSliceVarP(&opts.FilterCloud, "filter-cloud", "fcloud", nil, fmt.Sprintf("filter host with specified cloud provider (%s)", cdncheck.DefaultCloudProviders), goflags.CommaSeparatedStringSliceOptions), 133 | flagSet.StringSliceVarP(&opts.FilterWaf, "filter-waf", "fwaf", nil, fmt.Sprintf("filter host with specified waf provider (%s)", cdncheck.DefaultWafProviders), goflags.CommaSeparatedStringSliceOptions), 134 | ) 135 | 136 | flagSet.CreateGroup("output", "OUTPUT", 137 | flagSet.BoolVarP(&opts.Response, "resp", "", true, "display technology name in cli output"), 138 | flagSet.StringVarP(&opts.Output, "output", "o", "", "write output in plain format to file"), 139 | flagSet.BoolVarP(&opts.Verbose, "verbose", "v", false, "display verbose output"), 140 | flagSet.BoolVarP(&opts.Json, "jsonl", "j", false, "write output in json(line) format"), 141 | flagSet.BoolVarP(&opts.NoColor, "no-color", "nc", false, "disable colors in cli output"), 142 | flagSet.BoolVarP(&opts.Version, "version", "", false, "display version of the project"), 143 | flagSet.BoolVar(&opts.Silent, "silent", false, "only display results in output"), 144 | ) 145 | 146 | flagSet.CreateGroup("config", "CONFIG", 147 | flagSet.StringSliceVarP(&opts.Resolvers, "resolver", "r", nil, "list of resolvers to use (file or comma separated)", goflags.CommaSeparatedStringSliceOptions), 148 | flagSet.BoolVarP(&opts.Exclude, "exclude", "e", false, "exclude detected ip from output"), 149 | flagSet.IntVar(&opts.MaxRetries, "retry", 2, "maximum number of retries for dns resolution (must be at least 1)"), 150 | ) 151 | 152 | flagSet.CreateGroup("update", "UPDATE", 153 | flagSet.CallbackVarP(GetUpdateCallback(), "update", "up", "update cdncheck to latest version"), 154 | flagSet.BoolVarP(&opts.DisableUpdateCheck, "disable-update-check", "duc", true, "disable automatic cdncheck update check"), 155 | ) 156 | 157 | if err := flagSet.Parse(); err != nil { 158 | gologger.Fatal().Msgf("Could not parse flags: %s", err) 159 | os.Exit(0) 160 | } 161 | 162 | // configure output option 163 | configureOutput(opts) 164 | // shows banner 165 | showBanner() 166 | 167 | if opts.Version { 168 | gologger.Info().Msgf("Current version: %s", version) 169 | os.Exit(0) 170 | } 171 | 172 | if !opts.DisableUpdateCheck { 173 | latestVersion, err := updateutils.GetToolVersionCallback("cdncheck", version)() 174 | if err != nil { 175 | if opts.Verbose { 176 | gologger.Error().Msgf("cdncheck version check failed: %v", err.Error()) 177 | } 178 | } else { 179 | gologger.Info().Msgf("Current cdncheck version %v %v", version, updateutils.GetVersionDescription(version, latestVersion)) 180 | } 181 | } 182 | 183 | return opts, nil 184 | } 185 | -------------------------------------------------------------------------------- /internal/runner/runner.go: -------------------------------------------------------------------------------- 1 | package runner 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io" 7 | "net" 8 | "os" 9 | "strings" 10 | "sync" 11 | "time" 12 | 13 | "github.com/logrusorgru/aurora" 14 | "github.com/pkg/errors" 15 | "github.com/projectdiscovery/cdncheck" 16 | "github.com/projectdiscovery/gologger" 17 | "github.com/projectdiscovery/mapcidr" 18 | iputils "github.com/projectdiscovery/utils/ip" 19 | ) 20 | 21 | type Runner struct { 22 | options *Options 23 | cdnclient *cdncheck.Client 24 | aurora *aurora.Aurora 25 | writer *OutputWriter 26 | } 27 | 28 | func NewRunner(options *Options) *Runner { 29 | standardWriter := aurora.NewAurora(!options.NoColor) 30 | client, err := cdncheck.NewWithOpts(options.MaxRetries, options.Resolvers) 31 | if err != nil { 32 | gologger.Fatal().Msgf("failed to create cdncheck client: %v", err) 33 | } 34 | runner := &Runner{ 35 | options: options, 36 | cdnclient: client, 37 | aurora: &standardWriter, 38 | } 39 | return runner 40 | } 41 | 42 | func (r *Runner) Run() error { 43 | err := r.configureOutput() 44 | if err != nil { 45 | return errors.Wrap(err, "could not configure output") 46 | } 47 | defer r.writer.Close() 48 | output := make(chan Output, 1) 49 | wg := &sync.WaitGroup{} 50 | wg.Add(1) 51 | go r.process(output, wg) 52 | wg.Add(1) 53 | go r.waitForData(output, wg) 54 | wg.Wait() 55 | return nil 56 | } 57 | 58 | func (r *Runner) SetWriter(writer io.Writer) error { 59 | outputWriter, err := NewOutputWriter() 60 | if err != nil { 61 | return err 62 | } 63 | outputWriter.AddWriters(writer) 64 | r.writer = outputWriter 65 | return nil 66 | } 67 | 68 | func (r *Runner) process(output chan Output, wg *sync.WaitGroup) { 69 | defer wg.Done() 70 | defer close(output) 71 | for _, target := range r.options.Inputs { 72 | r.processInputItem(target, output) 73 | } 74 | if r.options.HasStdin { 75 | scanner := bufio.NewScanner(os.Stdin) 76 | for scanner.Scan() { 77 | text := scanner.Text() 78 | if text != "" { 79 | r.processInputItem(text, output) 80 | } 81 | } 82 | } 83 | } 84 | 85 | func (r *Runner) waitForData(output chan Output, wg *sync.WaitGroup) { 86 | defer wg.Done() 87 | var cdnCount, wafCount, cloudCount int 88 | for receivedData := range output { 89 | if receivedData.Cdn { 90 | cdnCount++ 91 | } else if receivedData.Waf { 92 | wafCount++ 93 | } else if receivedData.Cloud { 94 | cloudCount++ 95 | } 96 | 97 | if r.options.OnResult != nil { 98 | r.options.OnResult(receivedData) 99 | } 100 | 101 | if r.options.Json { 102 | r.writer.WriteJSON(receivedData) 103 | } else if r.options.Response && !r.options.Exclude { 104 | r.writer.WriteString(receivedData.String()) 105 | } else { 106 | r.writer.WriteString(receivedData.Input) 107 | } 108 | } 109 | 110 | // show summary to user 111 | sw := *r.aurora 112 | if (cdnCount + wafCount + cloudCount) < 1 { 113 | gologger.Info().Msgf("No results found.") 114 | return 115 | } 116 | var builder strings.Builder 117 | builder.WriteString(fmt.Sprintf("Found result: %v", (cdnCount + cloudCount + wafCount))) 118 | builder.WriteString(" (") 119 | if cdnCount > 0 { 120 | builder.WriteString(fmt.Sprintf("%s %v", sw.BrightBlue("CDN:").String(), cdnCount)) 121 | } 122 | if cloudCount > 0 { 123 | if cdnCount > 0 { 124 | builder.WriteString(", ") 125 | } 126 | builder.WriteString(fmt.Sprintf("%s %v", sw.BrightGreen("CLOUD:").String(), cloudCount)) 127 | } 128 | if wafCount > 0 { 129 | if cdnCount > 0 || cloudCount > 0 { 130 | builder.WriteString(", ") 131 | } 132 | builder.WriteString(fmt.Sprintf("%s %v", sw.Yellow("WAF:").String(), wafCount)) 133 | } 134 | builder.WriteString(")") 135 | gologger.Info().Msg(builder.String()) 136 | } 137 | 138 | func (r *Runner) configureOutput() error { 139 | if r.writer != nil { 140 | return nil 141 | } 142 | outputWriter, err := NewOutputWriter() 143 | if err != nil { 144 | return err 145 | } 146 | if r.options.OnResult != nil { 147 | r.writer = outputWriter 148 | return nil 149 | } 150 | outputWriter.AddWriters(os.Stdout) 151 | if r.options.Output != "" { 152 | outputFile, err := os.Create(r.options.Output) 153 | if err != nil { 154 | return err 155 | } 156 | outputWriter.AddWriters(outputFile) 157 | } 158 | r.writer = outputWriter 159 | return nil 160 | } 161 | 162 | // processInputItem processes a single input item 163 | func (r *Runner) processInputItem(input string, output chan Output) { 164 | // CIDR input 165 | if _, ipRange, _ := net.ParseCIDR(input); ipRange != nil { 166 | cidrInputs, err := mapcidr.IPAddressesAsStream(input) 167 | if err != nil { 168 | if r.options.Verbose { 169 | gologger.Error().Msgf("Could not parse cidr %s: %s", input, err) 170 | } 171 | return 172 | } 173 | for cidr := range cidrInputs { 174 | r.processInputItemSingle(cidr, output) 175 | } 176 | } else { 177 | // Normal input 178 | r.processInputItemSingle(input, output) 179 | } 180 | } 181 | 182 | func (r *Runner) processInputItemSingle(item string, output chan Output) { 183 | data := Output{ 184 | aurora: r.aurora, 185 | Input: item, 186 | } 187 | 188 | if iputils.IsIPv6(item) { 189 | // TODO: IPv6 support refer issue #59 190 | if r.options.Verbose { 191 | gologger.Error().Msgf("IPv6 is not supported: %s", item) 192 | } 193 | return 194 | } 195 | 196 | var matched bool 197 | var provider, itemType string 198 | var err error 199 | 200 | if iputils.IsIP(item) { 201 | targetIP := net.ParseIP(item) 202 | matched, provider, itemType, err = r.cdnclient.Check(targetIP) 203 | } else { 204 | matched, provider, itemType, err = r.cdnclient.CheckDomainWithFallback(item) 205 | } 206 | if err != nil && r.options.Verbose { 207 | gologger.Error().Msgf("Could not check domain cdn %s: %s", item, err) 208 | } 209 | 210 | data.itemType = itemType 211 | data.IP = item 212 | data.Timestamp = time.Now() 213 | 214 | if r.options.Exclude { 215 | if !matched { 216 | output <- data 217 | } 218 | return 219 | } 220 | 221 | switch itemType { 222 | case "cdn": 223 | data.Cdn = matched 224 | data.CdnName = provider 225 | case "cloud": 226 | data.Cloud = matched 227 | data.CloudName = provider 228 | case "waf": 229 | data.Waf = matched 230 | data.WafName = provider 231 | } 232 | if skipped := filterIP(r.options, data); skipped { 233 | return 234 | } 235 | if matched := matchIP(r.options, data); !matched { 236 | return 237 | } 238 | switch { 239 | case r.options.Cdn && data.itemType == "cdn", r.options.Cloud && data.itemType == "cloud", r.options.Waf && data.itemType == "waf": 240 | { 241 | output <- data 242 | } 243 | case (!r.options.Cdn && !r.options.Waf && !r.options.Cloud) && matched: 244 | { 245 | output <- data 246 | } 247 | } 248 | } 249 | 250 | func matchIP(options *Options, data Output) bool { 251 | if len(options.MatchCdn) == 0 && len(options.MatchCloud) == 0 && len(options.MatchWaf) == 0 { 252 | return true 253 | } 254 | if len(options.MatchCdn) > 0 && data.itemType == "cdn" { 255 | matched := false 256 | for _, filter := range options.MatchCdn { 257 | if filter == data.CdnName { 258 | matched = true 259 | } 260 | } 261 | if matched { 262 | return true 263 | } 264 | } 265 | if len(options.MatchCloud) > 0 && data.itemType == "cloud" { 266 | matched := false 267 | for _, filter := range options.MatchCloud { 268 | if filter == data.CloudName { 269 | matched = true 270 | } 271 | } 272 | if matched { 273 | return true 274 | } 275 | } 276 | if len(options.MatchWaf) > 0 && data.itemType == "waf" { 277 | matched := false 278 | for _, filter := range options.MatchWaf { 279 | if filter == data.WafName { 280 | matched = true 281 | } 282 | } 283 | if matched { 284 | return true 285 | } 286 | } 287 | return false 288 | } 289 | 290 | func filterIP(options *Options, data Output) bool { 291 | if len(options.FilterCdn) == 0 && len(options.FilterCloud) == 0 && len(options.FilterWaf) == 0 { 292 | return false 293 | } 294 | if len(options.FilterCdn) > 0 && data.itemType == "cdn" { 295 | for _, filter := range options.FilterCdn { 296 | if filter == data.CdnName { 297 | return true 298 | } 299 | } 300 | } 301 | if len(options.FilterCloud) > 0 && data.itemType == "cloud" { 302 | for _, filter := range options.FilterCloud { 303 | if filter == data.CloudName { 304 | return true 305 | } 306 | } 307 | } 308 | if len(options.FilterWaf) > 0 && data.itemType == "waf" { 309 | for _, filter := range options.FilterWaf { 310 | if filter == data.WafName { 311 | return true 312 | } 313 | } 314 | } 315 | return false 316 | } 317 | -------------------------------------------------------------------------------- /cmd/generate-index/sources_data.json: -------------------------------------------------------------------------------- 1 | {"cdn":{"cloudfront":["120.52.22.96/27","205.251.249.0/24","180.163.57.128/26","204.246.168.0/22","111.13.171.128/26","18.160.0.0/15","205.251.252.0/23","54.192.0.0/16","204.246.173.0/24","54.230.200.0/21","120.253.240.192/26","116.129.226.128/26","130.176.0.0/17","108.156.0.0/14","99.86.0.0/16","205.251.200.0/21","223.71.71.128/25","13.32.0.0/15","120.253.245.128/26","13.224.0.0/14","70.132.0.0/18","15.158.0.0/16","111.13.171.192/26","13.249.0.0/16","18.238.0.0/15","18.244.0.0/15","205.251.208.0/20","65.9.128.0/18","130.176.128.0/18","58.254.138.0/25","54.230.208.0/20","3.160.0.0/14","116.129.226.0/25","52.222.128.0/17","18.164.0.0/15","111.13.185.32/27","64.252.128.0/18","205.251.254.0/24","54.230.224.0/19","71.152.0.0/17","216.137.32.0/19","204.246.172.0/24","18.172.0.0/15","120.52.39.128/27","118.193.97.64/26","223.71.71.96/27","18.154.0.0/15","54.240.128.0/18","205.251.250.0/23","180.163.57.0/25","52.46.0.0/18","223.71.11.0/27","52.82.128.0/19","54.230.0.0/17","54.230.128.0/18","54.239.128.0/18","130.176.224.0/20","36.103.232.128/26","52.84.0.0/15","143.204.0.0/16","144.220.0.0/16","120.52.153.192/26","119.147.182.0/25","120.232.236.0/25","111.13.185.64/27","54.182.0.0/16","58.254.138.128/26","120.253.245.192/27","54.239.192.0/19","18.68.0.0/16","18.64.0.0/14","120.52.12.64/26","99.84.0.0/16","130.176.192.0/19","52.124.128.0/17","204.246.164.0/22","13.35.0.0/16","204.246.174.0/23","36.103.232.0/25","119.147.182.128/26","118.193.97.128/25","120.232.236.128/26","204.246.176.0/20","65.8.0.0/16","65.9.0.0/17","108.138.0.0/15","120.253.241.160/27","64.252.64.0/18","13.113.196.64/26","13.113.203.0/24","52.199.127.192/26","13.124.199.0/24","3.35.130.128/25","52.78.247.128/26","13.233.177.192/26","15.207.13.128/25","15.207.213.128/25","52.66.194.128/26","13.228.69.0/24","52.220.191.0/26","13.210.67.128/26","13.54.63.128/26","43.218.56.128/26","43.218.56.192/26","43.218.56.64/26","43.218.71.0/26","99.79.169.0/24","18.192.142.0/23","35.158.136.0/24","52.57.254.0/24","13.48.32.0/24","18.200.212.0/23","52.212.248.0/26","3.10.17.128/25","3.11.53.0/24","52.56.127.0/25","15.188.184.0/24","52.47.139.0/24","3.29.40.128/26","3.29.40.192/26","3.29.40.64/26","3.29.57.0/26","18.229.220.192/26","54.233.255.128/26","3.231.2.0/25","3.234.232.224/27","3.236.169.192/26","3.236.48.0/23","34.195.252.0/24","34.226.14.0/24","13.59.250.0/26","18.216.170.128/25","3.128.93.0/24","3.134.215.0/24","52.15.127.128/26","3.101.158.0/23","52.52.191.128/26","34.216.51.0/25","34.223.12.224/27","34.223.80.192/26","35.162.63.192/26","35.167.191.128/26","44.227.178.0/24","44.234.108.128/25","44.234.90.252/30"],"fastly":["23.235.32.0/20","43.249.72.0/22","103.244.50.0/24","103.245.222.0/23","103.245.224.0/24","104.156.80.0/20","140.248.64.0/18","140.248.128.0/17","146.75.0.0/17","151.101.0.0/16","157.52.64.0/18","167.82.0.0/17","167.82.128.0/20","167.82.160.0/20","167.82.224.0/20","172.111.64.0/18","185.31.16.0/22","199.27.72.0/21","199.232.0.0/16"],"google":["8.8.4.0/24","8.8.8.0/24","8.34.208.0/20","8.35.192.0/20","23.236.48.0/20","23.251.128.0/19","34.0.0.0/15","34.2.0.0/16","34.3.0.0/23","34.3.3.0/24","34.3.4.0/24","34.3.8.0/21","34.3.16.0/20","34.3.32.0/19","34.3.64.0/18","34.3.128.0/17","34.4.0.0/14","34.8.0.0/13","34.16.0.0/12","34.32.0.0/11","34.64.0.0/10","34.128.0.0/10","35.184.0.0/13","35.192.0.0/14","35.196.0.0/15","35.198.0.0/16","35.199.0.0/17","35.199.128.0/18","35.200.0.0/13","35.208.0.0/12","35.224.0.0/12","35.240.0.0/13","64.15.112.0/20","64.233.160.0/19","66.22.228.0/23","66.102.0.0/20","66.249.64.0/19","70.32.128.0/19","72.14.192.0/18","74.114.24.0/21","74.125.0.0/16","104.154.0.0/15","104.196.0.0/14","104.237.160.0/19","107.167.160.0/19","107.178.192.0/18","108.59.80.0/20","108.170.192.0/18","108.177.0.0/17","130.211.0.0/16","136.112.0.0/12","142.250.0.0/15","146.148.0.0/17","162.216.148.0/22","162.222.176.0/21","172.110.32.0/21","172.217.0.0/16","172.253.0.0/16","173.194.0.0/16","173.255.112.0/20","192.158.28.0/22","192.178.0.0/15","193.186.4.0/24","199.36.154.0/23","199.36.156.0/24","199.192.112.0/22","199.223.232.0/21","207.223.160.0/20","208.65.152.0/22","208.68.108.0/22","208.81.188.0/22","208.117.224.0/19","209.85.128.0/17","216.58.192.0/19","216.73.80.0/20","216.239.32.0/19"]},"waf":{"cloudflare":["173.245.48.0/20","103.21.244.0/22","103.22.200.0/22","103.31.4.0/22","141.101.64.0/18","108.162.192.0/18","190.93.240.0/20","188.114.96.0/20","197.234.240.0/22","198.41.128.0/17","162.158.0.0/15","104.16.0.0/13","104.24.0.0/14","172.64.0.0/13","131.0.72.0/22"],"incapsula":["199.83.128.0/21","198.143.32.0/19","149.126.72.0/21","103.28.248.0/22","185.11.124.0/22","192.230.64.0/18","45.64.64.0/22","107.154.0.0/16","45.60.0.0/16","45.223.0.0/16","131.125.128.0/17"]},"cloud":{"zscaler":["147.161.174.0/23","147.161.172.0/23","165.225.240.0/23","136.226.202.0/23","136.226.204.0/23","136.226.206.0/23","136.226.198.0/23","136.226.208.0/23","136.226.200.0/23","136.226.210.0/23","136.226.212.0/23","147.161.132.0/23","185.46.212.0/23","147.161.156.0/23","147.161.158.0/23","147.161.148.0/23","136.226.206.0/23","147.161.156.0/23","136.226.208.0/23","165.225.12.0/23","147.161.158.0/23","136.226.210.0/23","136.226.212.0/23","136.226.198.0/23","136.226.200.0/23","136.226.202.0/23","136.226.204.0/23","196.23.154.64/27","136.226.216.0/23","136.226.210.0/23","136.226.212.0/23","147.161.154.0/23","165.225.194.0/23","147.161.156.0/23","136.226.198.0/23","147.161.158.0/23","136.226.200.0/23","136.226.202.0/23","136.226.204.0/23","136.226.206.0/23","136.226.208.0/23","147.161.160.0/23","147.161.228.0/23","147.161.230.0/23","136.226.198.0/23","136.226.200.0/23","136.226.202.0/23","136.226.204.0/23","136.226.206.0/23","147.161.156.0/23","136.226.208.0/23","147.161.158.0/23","136.226.210.0/23","136.226.212.0/23","147.161.226.0/23","136.226.200.0/23","136.226.202.0/23","136.226.204.0/23","136.226.206.0/23","136.226.208.0/23","136.226.210.0/23","147.161.156.0/23","165.225.72.0/22","147.161.234.0/23","136.226.212.0/23","147.161.254.0/23","147.161.158.0/23","165.225.26.0/23","147.161.164.0/23","136.226.198.0/23","136.226.206.0/23","136.226.208.0/23","136.226.210.0/23","147.161.134.0/23","136.226.212.0/23","147.161.136.0/23","147.161.156.0/23","147.161.138.0/23","147.161.158.0/23","136.226.198.0/23","136.226.200.0/23","136.226.202.0/23","136.226.204.0/23","136.226.210.0/23","136.226.212.0/23","147.161.186.0/23","136.226.198.0/23","136.226.200.0/23","136.226.202.0/23","136.226.204.0/23","147.161.156.0/23","136.226.206.0/23","147.161.158.0/23","136.226.208.0/23","197.98.201.0/24","147.161.162.0/23","154.113.23.0/24","147.161.158.0/23","165.225.80.0/22","136.226.198.0/23","147.161.166.0/23","136.226.200.0/23","136.226.202.0/23","136.226.204.0/23","136.226.206.0/23","136.226.208.0/23","147.161.224.0/23","136.226.210.0/23","136.226.212.0/23","147.161.156.0/23","136.226.200.0/23","136.226.202.0/23","136.226.204.0/23","136.226.206.0/23","136.226.208.0/23","136.226.210.0/23","147.161.140.0/23","136.226.212.0/23","147.161.142.0/23","147.161.144.0/23","147.161.156.0/23","147.161.158.0/23","136.226.198.0/23","136.226.206.0/23","136.226.214.0/23","147.161.156.0/23","136.226.208.0/23","147.161.158.0/23","136.226.210.0/23","165.225.92.0/23","136.226.212.0/23","136.226.198.0/23","136.226.200.0/23","136.226.202.0/23","147.161.190.0/23","136.226.204.0/23","136.226.210.0/23","147.161.236.0/23","136.226.212.0/23","147.161.156.0/23","165.225.196.0/23","147.161.158.0/23","136.226.198.0/23","165.225.198.0/23","136.226.200.0/23","136.226.202.0/23","136.226.204.0/23","136.226.206.0/23","136.226.208.0/23","136.226.198.0/23","136.226.202.0/23","147.161.178.0/23","136.226.206.0/23","147.161.180.0/23","136.226.210.0/23","147.161.182.0/23","147.161.156.0/23","136.226.200.0/23","147.161.158.0/23","136.226.204.0/23","136.226.208.0/23","136.226.212.0/23","136.226.208.0/23","136.226.210.0/23","136.226.212.0/23","147.161.244.0/23","136.226.198.0/23","136.226.200.0/23","136.226.202.0/23","136.226.204.0/23","165.225.202.0/23","147.161.156.0/23","147.161.158.0/23","136.226.206.0/23","165.225.90.0/23","147.161.158.0/23","136.226.198.0/23","147.161.176.0/23","136.226.200.0/23","136.226.202.0/23","136.226.204.0/23","147.161.250.0/23","136.226.206.0/23","136.226.208.0/23","147.161.168.0/23","136.226.210.0/23","147.161.170.0/23","147.161.156.0/23","136.226.212.0/23","213.52.102.0/24","136.226.210.0/23","147.161.146.0/23","136.226.198.0/23","136.226.200.0/23","136.226.202.0/23","136.226.206.0/23","136.226.204.0/23","136.226.208.0/23","136.226.212.0/23","136.226.204.0/23","165.225.76.0/23","147.161.152.0/23","136.226.206.0/23","165.225.20.0/23","147.161.156.0/23","147.161.184.0/23","136.226.208.0/23","147.161.158.0/23","136.226.210.0/23","136.226.212.0/23","136.226.198.0/23","147.161.232.0/23","136.226.200.0/23","136.226.202.0/23","136.226.208.0/23","136.226.210.0/23","136.226.212.0/23","165.225.204.0/23","147.161.156.0/23","147.161.158.0/23","136.226.198.0/23","136.226.200.0/23","136.226.202.0/23","136.226.204.0/23","136.226.206.0/23","136.226.198.0/23","165.225.192.0/23","136.226.200.0/23","136.226.202.0/23","136.226.204.0/23","147.161.150.0/23","136.226.206.0/23","147.161.156.0/23","136.226.208.0/23","147.161.158.0/23","136.226.210.0/23","147.161.188.0/23","136.226.212.0/23","94.188.248.64/26","136.226.198.0/23","136.226.200.0/23","136.226.202.0/23","136.226.204.0/23","136.226.206.0/23","136.226.208.0/23","165.225.200.0/23","136.226.210.0/23","136.226.212.0/23","147.161.130.0/23","147.161.156.0/23","147.161.158.0/23","147.161.156.0/23","136.226.204.0/23","147.161.158.0/23","136.226.206.0/23","136.226.208.0/23","136.226.210.0/23","147.161.248.0/23","136.226.212.0/23","165.225.206.0/23","136.226.198.0/23","136.226.200.0/23","136.226.202.0/23","136.226.208.0/23","136.226.210.0/23","136.226.192.0/23","136.226.212.0/23","147.161.156.0/23","147.161.158.0/23","147.161.246.0/23","136.226.198.0/23","136.226.200.0/23","136.226.202.0/23","165.225.94.0/23","136.226.204.0/23","136.226.206.0/23","136.226.112.0/23","136.226.114.0/23","104.129.204.0/23","136.226.116.0/23","136.226.102.0/23","136.226.104.0/23","136.226.106.0/23","136.226.108.0/23","136.226.110.0/23","136.226.2.0/23","136.226.82.0/23","104.129.206.0/23","136.226.104.0/23","136.226.106.0/23","136.226.108.0/23","136.226.110.0/23","136.226.70.0/23","136.226.112.0/23","136.226.72.0/23","136.226.114.0/23","136.226.74.0/23","136.226.116.0/23","136.226.102.0/23","136.226.110.0/23","136.226.112.0/23","136.226.114.0/23","136.226.116.0/23","128.177.125.0/24","104.129.196.0/23","165.225.0.0/23","165.225.56.0/22","165.225.60.0/22","137.83.154.0/24","136.226.102.0/23","165.225.2.0/24","136.226.104.0/23","136.226.106.0/23","136.226.108.0/23","136.226.84.0/23","165.225.36.0/23","136.226.116.0/23","136.226.100.0/23","136.226.102.0/23","136.226.104.0/23","165.225.34.0/23","136.226.106.0/23","165.225.216.0/23","136.226.108.0/23","136.226.110.0/23","136.226.112.0/23","165.225.32.0/23","136.226.114.0/23","136.226.86.0/23","136.226.110.0/23","136.226.114.0/23","136.226.102.0/23","136.226.104.0/23","136.226.106.0/23","136.226.108.0/23","136.226.112.0/23","165.225.10.0/23","136.226.116.0/23","136.226.104.0/23","136.226.106.0/23","136.226.66.0/23","136.226.108.0/23","136.226.110.0/23","136.226.112.0/23","136.226.114.0/23","136.226.64.0/23","136.226.116.0/23","104.129.198.0/23","136.226.102.0/23","136.226.0.0/23","136.226.110.0/23","136.226.112.0/23","136.226.114.0/23","136.226.116.0/23","136.226.58.0/23","136.226.102.0/23","136.226.104.0/23","136.226.106.0/23","165.225.222.0/23","136.226.108.0/23","165.225.212.0/23","136.226.116.0/23","136.226.90.0/23","165.225.38.0/23","136.226.80.0/23","165.225.220.0/23","136.226.102.0/23","136.226.104.0/23","136.226.106.0/23","136.226.60.0/23","136.226.108.0/23","136.226.110.0/23","136.226.112.0/23","136.226.114.0/23","165.225.218.0/23","136.226.102.0/23","136.226.104.0/23","136.226.78.0/23","104.129.192.0/23","136.226.106.0/23","165.225.242.0/23","136.226.108.0/23","136.226.110.0/23","165.225.246.0/23","136.226.112.0/23","136.226.114.0/23","136.226.116.0/23","64.215.22.0/24","165.225.214.0/23","147.161.128.0/23","136.226.62.0/23","136.226.104.0/23","165.225.14.0/23","136.226.106.0/23","136.226.108.0/23","136.226.110.0/23","136.226.112.0/23","136.226.114.0/23","136.226.116.0/23","136.226.54.0/23","165.225.50.0/23","136.226.56.0/23","136.226.102.0/23","136.226.76.0/23","165.225.208.0/23","165.225.210.0/23","136.226.110.0/23","104.129.194.0/23","136.226.112.0/23","165.225.8.0/23","136.226.48.0/23","136.226.68.0/23","136.226.114.0/23","136.226.50.0/23","136.226.116.0/23","136.226.52.0/23","136.226.102.0/23","165.225.48.0/24","136.226.104.0/23","136.226.106.0/23","136.226.108.0/23","124.248.141.0/24","136.226.248.0/23","147.161.216.0/23","211.144.19.0/24","220.243.154.0/23","147.161.218.0/23","147.161.220.0/23","165.225.104.0/24","165.225.122.0/23","136.226.252.0/23","136.226.244.0/23","136.226.242.0/23","165.225.116.0/23","165.225.234.0/23","136.226.228.0/23","112.137.170.0/24","165.225.226.0/23","147.161.212.0/23","165.225.106.0/23","136.226.254.0/23","165.225.120.0/23","136.226.232.0/23","136.226.250.0/23","165.225.124.0/23","136.226.230.0/23","147.161.196.0/23","147.161.192.0/23","147.161.194.0/23","165.225.228.0/23","58.220.95.0/24","140.210.152.0/23","165.225.112.0/23","165.225.230.0/23","136.226.236.0/23","136.226.234.0/23","165.225.114.0/23","165.225.232.0/23","147.161.214.0/23","136.226.240.0/23","165.225.102.0/24","221.122.91.0/24","147.161.198.0/23","136.226.238.0/23","165.225.110.0/23","165.225.96.0/23"]},"common":{"akamai":["edgekey.net","akamaiedge.net","akamaitechnologies.com","akamaihd.net"],"amazon":["cloudfront.net","amazonaws.com"],"cloudflare":["cloudflare.com"],"edgecast":["edgecastcdn.net"],"fastly":["fastly.net"],"incapsula":["impervadns.net"]}} -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | aead.dev/minisign v0.2.0 h1:kAWrq/hBRu4AARY6AlciO83xhNnW9UaC8YipS2uhLPk= 2 | aead.dev/minisign v0.2.0/go.mod h1:zdq6LdSd9TbuSxchxwhpA9zEb9YXcVGoE8JakuiGaIQ= 3 | cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= 4 | github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= 5 | github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= 6 | github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g= 7 | github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM= 8 | github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ= 9 | github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= 10 | github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= 11 | github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek= 12 | github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s= 13 | github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c= 14 | github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= 15 | github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= 16 | github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= 17 | github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= 18 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= 19 | github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= 20 | github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= 21 | github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= 22 | github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= 23 | github.com/charmbracelet/glamour v0.6.0 h1:wi8fse3Y7nfcabbbDuwolqTqMQPMnVPeZhDM273bISc= 24 | github.com/charmbracelet/glamour v0.6.0/go.mod h1:taqWV4swIMMbWALc0m7AfE9JkPSU8om2538k9ITBxOc= 25 | github.com/cheggaaa/pb/v3 v3.1.4 h1:DN8j4TVVdKu3WxVwcRKu0sG00IIU6FewoABZzXbRQeo= 26 | github.com/cheggaaa/pb/v3 v3.1.4/go.mod h1:6wVjILNBaXMs8c21qRiaUM8BR82erfgau1DQ4iUXmSA= 27 | github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= 28 | github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08 h1:ox2F0PSMlrAAiAdknSRMDrAr8mfxPCfSZolH+/qQnyQ= 29 | github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08/go.mod h1:pCxVEbcm3AMg7ejXyorUXi6HQCzOIBf7zEDVPtw0/U4= 30 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 31 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 32 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 33 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 34 | github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ= 35 | github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI= 36 | github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= 37 | github.com/dlclark/regexp2 v1.8.1 h1:6Lcdwya6GjPUNsBct8Lg/yRPwMhABj269AAzdGSiR+0= 38 | github.com/dlclark/regexp2 v1.8.1/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= 39 | github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q= 40 | github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo= 41 | github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= 42 | github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= 43 | github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= 44 | github.com/frankban/quicktest v1.11.3 h1:8sXhOn0uLys67V8EsXLc6eszDs8VXWxL3iRvebPhedY= 45 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 46 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 47 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 48 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 49 | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= 50 | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 51 | github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= 52 | github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 53 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 54 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 55 | github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 56 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 57 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 58 | github.com/google/go-github/v30 v30.1.0 h1:VLDx+UolQICEOKu2m4uAoMti1SxuEBAl7RSEG16L+Oo= 59 | github.com/google/go-github/v30 v30.1.0/go.mod h1:n8jBpHl45a/rlBUtRJMOG4GhNADUQFEufcolZ95JfU8= 60 | github.com/google/go-github/v50 v50.2.0/go.mod h1:VBY8FB6yPIjrtKhozXv4FQupxKLS6H4m6xFZlT43q8Q= 61 | github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= 62 | github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= 63 | github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= 64 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 65 | github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= 66 | github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= 67 | github.com/ipinfo/go/v2 v2.9.2 h1:wih7S6ifXAdGE7OH5fgTfC/yA/lFYRKaG5z4FNiE+MY= 68 | github.com/ipinfo/go/v2 v2.9.2/go.mod h1:tRDkYfM20b1XzNqorn1Q1O6Xtg7uzw3Wn3I2R0SyJh4= 69 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 70 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 71 | github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= 72 | github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= 73 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 74 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 75 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 76 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 77 | github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= 78 | github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= 79 | github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= 80 | github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 81 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 82 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 83 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 84 | github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= 85 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 86 | github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 87 | github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= 88 | github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= 89 | github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 90 | github.com/mholt/archiver v3.1.1+incompatible h1:1dCVxuqs0dJseYEhi5pl7MYPH9zDa1wBi7mF09cbNkU= 91 | github.com/mholt/archiver v3.1.1+incompatible/go.mod h1:Dh2dOXnSdiLxRiPoVfIr/fI1TwETms9B8CTWfeh7ROU= 92 | github.com/microcosm-cc/bluemonday v1.0.21/go.mod h1:ytNkv4RrDrLJ2pqlsSI46O6IVXmZOBBD4SaJyDwwTkM= 93 | github.com/microcosm-cc/bluemonday v1.0.25 h1:4NEwSfiJ+Wva0VxN5B8OwMicaJvD8r9tlJWm9rtloEg= 94 | github.com/microcosm-cc/bluemonday v1.0.25/go.mod h1:ZIOjCQp1OrzBBPIJmfX4qDYFuhU02nx4bn030ixfHLE= 95 | github.com/miekg/dns v1.1.56 h1:5imZaSeoRNvpM9SzWNhEcP9QliKiz20/dA2QabIGVnE= 96 | github.com/miekg/dns v1.1.56/go.mod h1:cRm6Oo2C8TY9ZS/TqsSrseAcncm74lfK5G+ikN2SWWY= 97 | github.com/minio/selfupdate v0.6.0 h1:i76PgT0K5xO9+hjzKcacQtO7+MjJ4JKA8Ak8XQ9DDwU= 98 | github.com/minio/selfupdate v0.6.0/go.mod h1:bO02GTIPCMQFTEvE5h4DjYB58bCoZ35XLeBf0buTDdM= 99 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 100 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 101 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 102 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 103 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 104 | github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= 105 | github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= 106 | github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc= 107 | github.com/muesli/termenv v0.15.1 h1:UzuTb/+hhlBugQz28rpzey4ZuKcZ03MeKsoG7IJZIxs= 108 | github.com/muesli/termenv v0.15.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ= 109 | github.com/nwaples/rardecode v1.1.3 h1:cWCaZwfM5H7nAD6PyEdcVnczzV8i/JtotnyW/dD9lEc= 110 | github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= 111 | github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= 112 | github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= 113 | github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= 114 | github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= 115 | github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM= 116 | github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= 117 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 118 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 119 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 120 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 121 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 122 | github.com/projectdiscovery/blackrock v0.0.1 h1:lHQqhaaEFjgf5WkuItbpeCZv2DUIE45k0VbGJyft6LQ= 123 | github.com/projectdiscovery/blackrock v0.0.1/go.mod h1:ANUtjDfaVrqB453bzToU+YB4cUbvBRpLvEwoWIwlTss= 124 | github.com/projectdiscovery/goflags v0.1.23 h1:/mbzTf0T76CyCBQ+ofDGjpBI+ExhpxTA49MJtgAoa2U= 125 | github.com/projectdiscovery/goflags v0.1.23/go.mod h1:15Hc92q689vj6bJv0NdLb9NT/PTrPtT9ItSZBUAJULs= 126 | github.com/projectdiscovery/gologger v1.1.11 h1:8vsz9oJlDT9euw6xlj7F7dZ6RWItVIqVwn4Mr6uzky8= 127 | github.com/projectdiscovery/gologger v1.1.11/go.mod h1:UR2bgXl7zraOxYGnUwuO917hifWrwMJ0feKnVqMQkzY= 128 | github.com/projectdiscovery/mapcidr v1.1.11 h1:CVOwq2YoyeKzqEU/a1hv1hPUHDdTCyy7Ej82gD8gKPU= 129 | github.com/projectdiscovery/mapcidr v1.1.11/go.mod h1:i6vt5jApyht30bEErJkmXAvUDJEyO0XschRY7Il+XAo= 130 | github.com/projectdiscovery/retryabledns v1.0.38 h1:PR5pM0702/0upq30R0k/AnXhcAFqHGC6ShGof8rmT+w= 131 | github.com/projectdiscovery/retryabledns v1.0.38/go.mod h1:MtqDQkV1brf0prql8RtyDrmd1Y39MTGIupzeiFOXRuo= 132 | github.com/projectdiscovery/utils v0.0.57 h1:pbisqwN+nLcEF7E5gGo8CHKFwTM12P6zDN2W+F0BuFA= 133 | github.com/projectdiscovery/utils v0.0.57/go.mod h1:5ub86JF91NnI3nTMIzEpL/pfsNb0jtHznzKi9hv03X4= 134 | github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 135 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 136 | github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= 137 | github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 138 | github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= 139 | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 140 | github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA= 141 | github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU= 142 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 143 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 144 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 145 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 146 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 147 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 148 | github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= 149 | github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= 150 | github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= 151 | github.com/weppos/publicsuffix-go v0.30.1 h1:8q+QwBS1MY56Zjfk/50ycu33NN8aa1iCCEQwo/71Oos= 152 | github.com/weppos/publicsuffix-go v0.30.1/go.mod h1:s41lQh6dIsDWIC1OWh7ChWJXLH0zkJ9KHZVqA7vHyuQ= 153 | github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= 154 | github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= 155 | github.com/yl2chen/cidranger v1.0.2 h1:lbOWZVCG1tCRX4u24kuM1Tb4nHqWkDxwLdoS+SevawU= 156 | github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g= 157 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 158 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 159 | github.com/yuin/goldmark v1.5.2/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 160 | github.com/yuin/goldmark v1.5.4 h1:2uY/xC0roWy8IBEGLgB1ywIoEJFGmRrX21YQcvGZzjU= 161 | github.com/yuin/goldmark v1.5.4/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 162 | github.com/yuin/goldmark-emoji v1.0.1 h1:ctuWEyzGBwiucEqxzwe0SOYDXPAucOrE9NQC18Wa1os= 163 | github.com/yuin/goldmark-emoji v1.0.1/go.mod h1:2w1E6FEWLcDQkoTE+7HU6QF1F6SLlNGjRIBbIZQFqkQ= 164 | go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= 165 | go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 166 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 167 | golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= 168 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 169 | golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 170 | golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= 171 | golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= 172 | golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= 173 | golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= 174 | golang.org/x/exp v0.0.0-20230420155640-133eef4313cb h1:rhjz/8Mbfa8xROFiH+MQphmAmgqRM0bOMnytznhWEXk= 175 | golang.org/x/exp v0.0.0-20230420155640-133eef4313cb/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= 176 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 177 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 178 | golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= 179 | golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 180 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 181 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 182 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 183 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 184 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 185 | golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 186 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 187 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 188 | golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= 189 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 190 | golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 191 | golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= 192 | golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 193 | golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= 194 | golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= 195 | golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= 196 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 197 | golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= 198 | golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU= 199 | golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= 200 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 201 | golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 202 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 203 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 204 | golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= 205 | golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 206 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 207 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 208 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 209 | golang.org/x/sys v0.0.0-20210228012217-479acdf4ea46/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 210 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 211 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 212 | golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 213 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 214 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 215 | golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 216 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 217 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 218 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 219 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 220 | golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 221 | golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= 222 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 223 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 224 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 225 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 226 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 227 | golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= 228 | golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= 229 | golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= 230 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 231 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 232 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 233 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 234 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 235 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 236 | golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 237 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 238 | golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 239 | golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= 240 | golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 241 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 242 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 243 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 244 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 245 | golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= 246 | golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 247 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 248 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 249 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 250 | google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= 251 | google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 252 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 253 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 254 | google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 255 | google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 256 | google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= 257 | google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 258 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 259 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 260 | gopkg.in/djherbis/times.v1 v1.3.0 h1:uxMS4iMtH6Pwsxog094W0FYldiNnfY/xba00vq6C2+o= 261 | gopkg.in/djherbis/times.v1 v1.3.0/go.mod h1:AQlg6unIsrsCEdQYhTzERy542dz6SFdQFZFv6mUY0P8= 262 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 263 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 264 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 265 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 266 | --------------------------------------------------------------------------------