diff --git a/.github/workflows/auto-merge.yaml b/.github/workflows/auto-merge.yaml new file mode 100644 index 00000000..e73bcf69 --- /dev/null +++ b/.github/workflows/auto-merge.yaml @@ -0,0 +1,20 @@ +name: πŸ”€ Auto merge PR + +on: + workflow_run: + workflows: ["♾️ Compatibility Checks"] + types: [completed] + +permissions: + contents: write + pull-requests: write + +jobs: + auto-merge-dependabot: + runs-on: ubuntu-latest + if: ${{ github.event.workflow_run.conclusion == 'success' }} + steps: + - uses: projectdiscovery/actions/pr/approve@v1 + - uses: projectdiscovery/actions/pr/merge@v1 + with: + auto: "true" diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index d21fc57f..7d25ddc1 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -8,20 +8,25 @@ on: workflow_dispatch: jobs: + lint: + name: Lint Test + if: ${{ !endsWith(github.actor, '[bot]') }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: projectdiscovery/actions/setup/go@v1 + - uses: projectdiscovery/actions/golangci-lint/v2@v1 + build: name: Test Builds + needs: [lint] runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, windows-latest, macOS-latest] steps: - - name: Set up Go - uses: actions/setup-go@v4 - with: - go-version: 1.24.x - - - name: Check out code - uses: actions/checkout@v3 + - uses: actions/checkout@v4 + - uses: projectdiscovery/actions/setup/go@v1 - name: Build run: go build . @@ -29,7 +34,6 @@ jobs: - name: Test run: go test ./... - working-directory: . env: GH_ACTION: true PDCP_API_KEY: "${{ secrets.PDCP_API_KEY }}" @@ -40,9 +44,7 @@ jobs: PDCP_API_KEY: "${{ secrets.PDCP_API_KEY }}" run: bash run.sh working-directory: integration_tests/ - + - name: Race Condition Tests - run: go run -race . + run: go build -race . working-directory: cmd/dnsx/ - - diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 28638657..e67d61af 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -5,7 +5,6 @@ on: pull_request: paths: - '**.go' - - '**.mod' branches: - dev @@ -22,20 +21,11 @@ jobs: fail-fast: false matrix: language: [ 'go' ] - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] steps: - - name: Checkout repository - uses: actions/checkout@v3 - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v2 - with: - languages: ${{ matrix.language }} - - - name: Autobuild - uses: github/codeql-action/autobuild@v2 - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 \ No newline at end of file + - uses: actions/checkout@v4 + - uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + - uses: github/codeql-action/autobuild@v3 + - uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/compat-checks.yaml b/.github/workflows/compat-checks.yaml new file mode 100644 index 00000000..2f4f6e80 --- /dev/null +++ b/.github/workflows/compat-checks.yaml @@ -0,0 +1,19 @@ +name: ♾️ Compatibility Checks + +on: + pull_request: + types: [opened, synchronize] + branches: + - dev + +jobs: + check: + if: github.actor == 'dependabot[bot]' + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + - uses: projectdiscovery/actions/setup/go/compat-checks@v1 + with: + go-version-file: 'go.mod' diff --git a/.github/workflows/dep-auto-merge.yml b/.github/workflows/dep-auto-merge.yml deleted file mode 100644 index 84b26e1f..00000000 --- a/.github/workflows/dep-auto-merge.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: πŸ€– dep auto merge - -on: - pull_request: - branches: - - dev - workflow_dispatch: - -permissions: - pull-requests: write - issues: write - repository-projects: write - -jobs: - automerge: - runs-on: ubuntu-latest - if: github.actor == 'dependabot[bot]' - steps: - - uses: actions/checkout@v3 - with: - token: ${{ secrets.DEPENDABOT_PAT }} - - - uses: ahmadnassri/action-dependabot-auto-merge@v2 - with: - github-token: ${{ secrets.DEPENDABOT_PAT }} - target: all \ No newline at end of file diff --git a/.github/workflows/dockerhub-push.yml b/.github/workflows/dockerhub-push.yml index d24ed0f6..836c297a 100644 --- a/.github/workflows/dockerhub-push.yml +++ b/.github/workflows/dockerhub-push.yml @@ -11,30 +11,24 @@ jobs: docker: runs-on: ubuntu-latest-16-cores steps: - - name: Git Checkout - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Get Github tag id: meta run: | curl --silent "https://api.github.com/repos/projectdiscovery/dnsx/releases/latest" | jq -r .tag_name | xargs -I {} echo TAG={} >> $GITHUB_OUTPUT - - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + - uses: docker/setup-qemu-action@v3 + - uses: docker/setup-buildx-action@v3 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - - name: Login to DockerHub - uses: docker/login-action@v2 + - uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_TOKEN }} - - name: Build and push - uses: docker/build-push-action@v4 + - uses: docker/build-push-action@v5 with: context: . platforms: linux/amd64,linux/arm64,linux/arm push: true - tags: projectdiscovery/dnsx:latest,projectdiscovery/dnsx:${{ steps.meta.outputs.TAG }} \ No newline at end of file + tags: projectdiscovery/dnsx:latest,projectdiscovery/dnsx:${{ steps.meta.outputs.TAG }} diff --git a/.github/workflows/functional-test.yml b/.github/workflows/functional-test.yml index b93449bd..24cb0c0f 100644 --- a/.github/workflows/functional-test.yml +++ b/.github/workflows/functional-test.yml @@ -2,10 +2,12 @@ name: πŸ§ͺ Functional Test on: pull_request: + paths: + - '**.go' + - '**.mod' workflow_dispatch: - -jobs: +jobs: functional: name: Functional Test runs-on: ${{ matrix.os }} @@ -13,13 +15,8 @@ jobs: matrix: os: [ubuntu-latest, windows-latest, macOS-latest] steps: - - name: Set up Go - uses: actions/setup-go@v4 - with: - go-version: 1.24.x - - - name: Check out code - uses: actions/checkout@v3 + - uses: actions/checkout@v4 + - uses: projectdiscovery/actions/setup/go@v1 - name: Functional Tests run: | diff --git a/.github/workflows/lint-test.yml b/.github/workflows/lint-test.yml deleted file mode 100644 index 68e043c6..00000000 --- a/.github/workflows/lint-test.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: πŸ™πŸ» Lint Test - -on: - pull_request: - workflow_dispatch: - -jobs: - lint: - name: Lint Test - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v3 - - name: "Set up Go" - uses: actions/setup-go@v4 - with: - go-version: 1.24.x - - name: Run golangci-lint - uses: golangci/golangci-lint-action@v3.6.0 - with: - version: latest - args: --timeout 5m - working-directory: . \ No newline at end of file diff --git a/.github/workflows/release-binary.yml b/.github/workflows/release-binary.yml index 3c35d2b3..d952afcd 100644 --- a/.github/workflows/release-binary.yml +++ b/.github/workflows/release-binary.yml @@ -3,31 +3,22 @@ name: πŸŽ‰ Release Binary on: push: tags: - - v* + - '*' workflow_dispatch: -jobs: - release: +jobs: + release: runs-on: ubuntu-latest-16-cores - steps: - - name: "Check out code" - uses: actions/checkout@v3 - with: + steps: + - uses: actions/checkout@v4 + with: fetch-depth: 0 - - - name: "Set up Go" - uses: actions/setup-go@v4 - with: - go-version: 1.24.x - - - name: "Create release on GitHub" - uses: goreleaser/goreleaser-action@v4 - with: - args: "release --clean" - version: latest - workdir: . + - uses: projectdiscovery/actions/setup/go@v1 + - uses: projectdiscovery/actions/goreleaser@v1 + with: + release: true env: GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" SLACK_WEBHOOK: "${{ secrets.RELEASE_SLACK_WEBHOOK }}" DISCORD_WEBHOOK_ID: "${{ secrets.DISCORD_WEBHOOK_ID }}" - DISCORD_WEBHOOK_TOKEN: "${{ secrets.DISCORD_WEBHOOK_TOKEN }}" \ No newline at end of file + DISCORD_WEBHOOK_TOKEN: "${{ secrets.DISCORD_WEBHOOK_TOKEN }}" diff --git a/.github/workflows/release-test.yml b/.github/workflows/release-test.yml index 5a9c968f..796ce036 100644 --- a/.github/workflows/release-test.yml +++ b/.github/workflows/release-test.yml @@ -5,26 +5,14 @@ on: paths: - '**.go' - '**.mod' - - '**.yml' workflow_dispatch: jobs: release-test: runs-on: ubuntu-latest-16-cores steps: - - name: "Check out code" - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - name: Set up Go - uses: actions/setup-go@v4 + - uses: actions/checkout@v4 with: - go-version: 1.24.x - - - name: release test - uses: goreleaser/goreleaser-action@v4 - with: - args: "release --clean --snapshot" - version: latest - workdir: . \ No newline at end of file + fetch-depth: 0 + - uses: projectdiscovery/actions/setup/go@v1 + - uses: projectdiscovery/actions/goreleaser@v1 diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml new file mode 100644 index 00000000..da02c094 --- /dev/null +++ b/.github/workflows/stale.yaml @@ -0,0 +1,40 @@ +name: πŸ’€ Stale + +on: + schedule: + - cron: "0 0 * * 0" # Weekly + +jobs: + stale: + runs-on: ubuntu-latest + permissions: + actions: write + contents: write + issues: write + pull-requests: write + steps: + - uses: actions/stale@v9 + with: + days-before-stale: 90 + days-before-close: 7 + stale-issue-label: "Status: Stale" + stale-pr-label: "Status: Stale" + stale-issue-message: > + This issue has been automatically marked as stale because it has not + had recent activity. It will be closed in 7 days if no further + activity occurs. Thank you for your contributions! + stale-pr-message: > + This pull request has been automatically marked as stale due to + inactivity. It will be closed in 7 days if no further activity + occurs. Please update if you wish to keep it open. + close-issue-message: > + This issue has been automatically closed due to inactivity. If you + think this is a mistake or would like to continue the discussion, + please comment or feel free to reopen it. + close-pr-message: > + This pull request has been automatically closed due to inactivity. + If you think this is a mistake or would like to continue working on + it, please comment or feel free to reopen it. + close-issue-label: "Status: Abandoned" + close-pr-label: "Status: Abandoned" + exempt-issue-labels: "Type: Enhancement" diff --git a/cmd/integration-test/dns.go b/cmd/integration-test/dns.go index f44a2ae5..e4b3ab06 100644 --- a/cmd/integration-test/dns.go +++ b/cmd/integration-test/dns.go @@ -1,6 +1,7 @@ package main import ( + "encoding/json" "net" "strings" @@ -9,8 +10,9 @@ import ( ) var dnsTestcases = map[string]testutils.TestCase{ - "DNS A Request": &dnsARequest{question: "projectdiscovery.io", expectedOutput: "projectdiscovery.io"}, - "DNS AAAA Request": &dnsAAAARequest{question: "projectdiscovery.io", expectedOutput: "projectdiscovery.io"}, + "DNS A Request": &dnsARequest{question: "projectdiscovery.io", expectedOutput: "projectdiscovery.io"}, + "DNS AAAA Request": &dnsAAAARequest{question: "projectdiscovery.io", expectedOutput: "projectdiscovery.io"}, + "DNS Filter Additional Section": &dnsFilterAdditionalSection{question: "anyinvaliddomain.projectdiscovery.io"}, } type dnsARequest struct { @@ -133,3 +135,105 @@ func buildAnswer(r *dns.Msg, ans answer) *dns.Msg { } return &msg } + +// dnsFilterAdditionalSection verifies that A records from AUTHORITY/ADDITIONAL +// sections are not incorrectly treated as answer records for the queried domain. +// The fix for this lives in retryabledns (ParseFromMsg only parses Answer section). +type dnsFilterAdditionalSection struct { + question string +} + +func (h *dnsFilterAdditionalSection) Execute() error { + srv := &dns.Server{ + Handler: &dnshandlerWithAdditional{question: h.question}, + Addr: "127.0.0.1:15001", + Net: "udp", + } + go srv.ListenAndServe() //nolint + defer srv.Shutdown() //nolint + + var extra []string + extra = append(extra, "-r", "127.0.0.1:15001") + extra = append(extra, "-a", "-resp", "-json") + + results, err := testutils.RunDnsxAndGetResults(h.question, debug, extra...) + if err != nil { + return err + } + + for _, result := range results { + var jsonData map[string]interface{} + if err := json.Unmarshal([]byte(result), &jsonData); err != nil { + continue + } + + if aField, ok := jsonData["a"].([]interface{}); ok { + for _, ip := range aField { + s, ok := ip.(string) + if !ok { + continue + } + ipStr := strings.ToLower(s) + if ipStr == "192.112.36.4" || + ipStr == "198.97.190.53" || + ipStr == "198.41.0.4" { + return errIncorrectResult("(no root server IPs in 'a' field)", result) + } + } + } + } + + return nil +} + +type dnshandlerWithAdditional struct { + question string +} + +func (t *dnshandlerWithAdditional) ServeDNS(w dns.ResponseWriter, r *dns.Msg) { + if len(r.Question) == 0 { + return + } + question := strings.TrimSuffix(r.Question[0].Name, ".") + if !strings.EqualFold(question, t.question) { + return + } + + msg := dns.Msg{} + msg.SetReply(r) + msg.Authoritative = true + + msg.Answer = []dns.RR{} + + msg.Ns = []dns.RR{ + &dns.NS{ + Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: 79449}, + Ns: "g.root-servers.net.", + }, + &dns.NS{ + Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: 79449}, + Ns: "h.root-servers.net.", + }, + &dns.NS{ + Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: 79449}, + Ns: "a.root-servers.net.", + }, + } + + msg.Extra = []dns.RR{ + &dns.A{ + Hdr: dns.RR_Header{Name: "g.root-servers.net.", Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 77741}, + A: net.ParseIP("192.112.36.4"), + }, + &dns.A{ + Hdr: dns.RR_Header{Name: "h.root-servers.net.", Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 77741}, + A: net.ParseIP("198.97.190.53"), + }, + &dns.A{ + Hdr: dns.RR_Header{Name: "a.root-servers.net.", Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 81186}, + A: net.ParseIP("198.41.0.4"), + }, + } + + w.WriteMsg(&msg) //nolint +} diff --git a/go.mod b/go.mod index 49349663..04e28811 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/projectdiscovery/hmap v0.0.100 github.com/projectdiscovery/mapcidr v1.1.97 github.com/projectdiscovery/ratelimit v0.0.83 - github.com/projectdiscovery/retryabledns v1.0.113 + github.com/projectdiscovery/retryabledns v1.0.114-0.20260321013547-428c4a196bd2 github.com/projectdiscovery/utils v0.9.1-0.20260320172110-ce6beb334710 github.com/stretchr/testify v1.11.1 ) diff --git a/go.sum b/go.sum index 7f1f610a..7032d22c 100644 --- a/go.sum +++ b/go.sum @@ -274,8 +274,8 @@ github.com/projectdiscovery/networkpolicy v0.1.34 h1:TRwNbgMwdx3NC190TKSLwtTvr0J github.com/projectdiscovery/networkpolicy v0.1.34/go.mod h1:GJ20E7fJoA2vk8ZBSa1Cvc5WyP8RxglF5bZmYgK8jag= github.com/projectdiscovery/ratelimit v0.0.83 h1:hfb36QvznBrjA4FNfpFE8AYRVBYrfJh8qHVROLQgl54= github.com/projectdiscovery/ratelimit v0.0.83/go.mod h1:z076BrLkBb5yS7uhHNoCTf8X/BvFSGRxwQ8EzEL9afM= -github.com/projectdiscovery/retryabledns v1.0.113 h1:s+DAzdJ8XhLxRgt5636H0HG9OqHsGRjX9wTrLSTMqlQ= -github.com/projectdiscovery/retryabledns v1.0.113/go.mod h1:+DyanDr8naxQ2dRO9c4Ezo3NHHXhz8L0tTSRYWhiwyA= +github.com/projectdiscovery/retryabledns v1.0.114-0.20260321013547-428c4a196bd2 h1:iiOmSzVLnMHdCP9O1rh0BX/FETCx1/oTdJpwMSIvdvU= +github.com/projectdiscovery/retryabledns v1.0.114-0.20260321013547-428c4a196bd2/go.mod h1:+DyanDr8naxQ2dRO9c4Ezo3NHHXhz8L0tTSRYWhiwyA= github.com/projectdiscovery/retryablehttp-go v1.3.6 h1:dLb0/YVX+oX70gpWxN5GXT8pCKpn8fdXfwOq2TsXxNY= github.com/projectdiscovery/retryablehttp-go v1.3.6/go.mod h1:tKVxmL4ixWy1MjYk5GJvFL0Cp10fnQgSp2F6bSBEypI= github.com/projectdiscovery/utils v0.9.1-0.20260320172110-ce6beb334710 h1:i94YPA5/Y0vidyp1wPGZ+k+PzfDYsSmC7vpnRiNyoO0= diff --git a/internal/runner/healthcheck.go b/internal/runner/healthcheck.go index 50364c31..c0b990ca 100644 --- a/internal/runner/healthcheck.go +++ b/internal/runner/healthcheck.go @@ -14,11 +14,11 @@ func DoHealthCheck(options *Options, flagSet *goflags.FlagSet) string { // RW permissions on config file cfgFilePath, _ := flagSet.GetConfigFilePath() var test strings.Builder - test.WriteString(fmt.Sprintf("Version: %s\n", version)) - test.WriteString(fmt.Sprintf("Operative System: %s\n", runtime.GOOS)) - test.WriteString(fmt.Sprintf("Architecture: %s\n", runtime.GOARCH)) - test.WriteString(fmt.Sprintf("Go Version: %s\n", runtime.Version())) - test.WriteString(fmt.Sprintf("Compiler: %s\n", runtime.Compiler)) + fmt.Fprintf(&test, "Version: %s\n", version) + fmt.Fprintf(&test, "Operative System: %s\n", runtime.GOOS) + fmt.Fprintf(&test, "Architecture: %s\n", runtime.GOARCH) + fmt.Fprintf(&test, "Go Version: %s\n", runtime.Version()) + fmt.Fprintf(&test, "Compiler: %s\n", runtime.Compiler) var testResult string ok, err := fileutil.IsReadable(cfgFilePath) @@ -30,7 +30,7 @@ func DoHealthCheck(options *Options, flagSet *goflags.FlagSet) string { if err != nil { testResult += fmt.Sprintf(" (%s)", err) } - test.WriteString(fmt.Sprintf("Config file \"%s\" Read => %s\n", cfgFilePath, testResult)) + fmt.Fprintf(&test, "Config file \"%s\" Read => %s\n", cfgFilePath, testResult) ok, err = fileutil.IsWriteable(cfgFilePath) if ok { testResult = "Ok" @@ -40,7 +40,7 @@ func DoHealthCheck(options *Options, flagSet *goflags.FlagSet) string { if err != nil { testResult += fmt.Sprintf(" (%s)", err) } - test.WriteString(fmt.Sprintf("Config file \"%s\" Write => %s\n", cfgFilePath, testResult)) + fmt.Fprintf(&test, "Config file \"%s\" Write => %s\n", cfgFilePath, testResult) c4, err := net.Dial("tcp4", "scanme.sh:80") if err == nil && c4 != nil { _ = c4.Close() @@ -49,7 +49,7 @@ func DoHealthCheck(options *Options, flagSet *goflags.FlagSet) string { if err != nil { testResult = fmt.Sprintf("Ko (%s)", err) } - test.WriteString(fmt.Sprintf("IPv4 connectivity to scanme.sh:80 => %s\n", testResult)) + fmt.Fprintf(&test, "IPv4 connectivity to scanme.sh:80 => %s\n", testResult) c6, err := net.Dial("tcp6", "scanme.sh:80") if err == nil && c6 != nil { _ = c6.Close() @@ -58,7 +58,7 @@ func DoHealthCheck(options *Options, flagSet *goflags.FlagSet) string { if err != nil { testResult = fmt.Sprintf("Ko (%s)", err) } - test.WriteString(fmt.Sprintf("IPv6 connectivity to scanme.sh:80 => %s\n", testResult)) + fmt.Fprintf(&test, "IPv6 connectivity to scanme.sh:80 => %s\n", testResult) u, err := net.Dial("udp", "scanme.sh:53") if err == nil && c6 != nil { _ = u.Close() @@ -67,7 +67,7 @@ func DoHealthCheck(options *Options, flagSet *goflags.FlagSet) string { if err != nil { testResult = fmt.Sprintf("Ko (%s)", err) } - test.WriteString(fmt.Sprintf("UDP connectivity to scanme.sh:53 => %s\n", testResult)) + fmt.Fprintf(&test, "UDP connectivity to scanme.sh:53 => %s\n", testResult) return test.String() } diff --git a/internal/runner/runner.go b/internal/runner/runner.go index 52b99c01..8a6f61c6 100644 --- a/internal/runner/runner.go +++ b/internal/runner/runner.go @@ -223,7 +223,7 @@ func (r *Runner) InputWorkerStream() { gologger.Error().Msgf("Could not open hosts file '%s': %s", r.options.Hosts, err) return } - defer f.Close() + defer f.Close() //nolint:errcheck sc = bufio.NewScanner(f) } else if fileutil.HasStdin() { sc = bufio.NewScanner(os.Stdin)