diff --git a/.dockerignore b/.dockerignore index 2337f81..d53d723 100644 --- a/.dockerignore +++ b/.dockerignore @@ -67,7 +67,6 @@ tmp .golangci.yml .goreleaser.yml .vscode -docs/*.md LICENSE README.md codecov.yml diff --git a/.github/actions/upload-artifact-resilient/action.yml b/.github/actions/upload-artifact-resilient/action.yml new file mode 100644 index 0000000..d1f1d4a --- /dev/null +++ b/.github/actions/upload-artifact-resilient/action.yml @@ -0,0 +1,120 @@ +# ------------------------------------------------------------------------------------ +# Upload Artifact with Resilience (Composite Action) (GoFortress) +# +# Purpose: Provide resilient artifact uploads with step-level retry logic to handle +# transient GitHub infrastructure failures (including non-retryable 403 errors from +# CDN/proxy intermediaries during artifact finalization). +# +# This action handles: +# - Step-level retry (3 attempts) to recover from non-retryable errors (e.g., 403) +# - Escalating delays (10s, 30s) between retries for transient infrastructure issues +# - overwrite: true on all attempts to handle partially-finalized artifacts +# - ACTIONS_UPLOAD_RETRY_COUNT=3 for defense-in-depth against 5xx errors +# - Configurable continue-on-error for critical vs non-critical artifacts +# +# Maintainer: @mrz1836 +# +# ------------------------------------------------------------------------------------ + +name: "Upload Artifact with Resilience" +description: "Uploads GitHub Actions artifacts with step-level retry logic for transient infrastructure failures" + +inputs: + artifact-name: + description: "Name of the artifact (will be displayed in GitHub UI)" + required: true + artifact-path: + description: "Path to the artifact file(s) to upload" + required: true + retention-days: + description: "Number of days to retain the artifact (1-90 days)" + required: false + default: "7" + if-no-files-found: + description: "Behavior when no files match the path (warn, error, ignore)" + required: false + default: "ignore" + compression-level: + description: "Compression level for the artifact (0-9, 6 is default)" + required: false + default: "6" + continue-on-error: + description: "Continue workflow if all upload attempts fail (true for non-critical artifacts)" + required: false + default: "true" + +runs: + using: "composite" + steps: + # ------------------------------------------------------------------ + # Attempt 1 + # ------------------------------------------------------------------ + - name: "📤 Upload ${{ inputs.artifact-name }} (attempt 1)" + id: attempt1 + continue-on-error: true + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + with: + name: ${{ inputs.artifact-name }} + path: ${{ inputs.artifact-path }} + retention-days: ${{ inputs.retention-days }} + if-no-files-found: ${{ inputs.if-no-files-found }} + compression-level: ${{ inputs.compression-level }} + overwrite: true + env: + ACTIONS_UPLOAD_RETRY_COUNT: 3 + + # ------------------------------------------------------------------ + # Delay before retry + # ------------------------------------------------------------------ + - name: "⏳ Wait before retry (${{ inputs.artifact-name }})" + if: steps.attempt1.outcome == 'failure' + shell: bash + run: | + echo "::warning::Upload attempt 1 for '${{ inputs.artifact-name }}' failed, retrying in 10s..." + sleep 10 + + # ------------------------------------------------------------------ + # Attempt 2 + # ------------------------------------------------------------------ + - name: "📤 Upload ${{ inputs.artifact-name }} (attempt 2)" + id: attempt2 + if: steps.attempt1.outcome == 'failure' + continue-on-error: true + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + with: + name: ${{ inputs.artifact-name }} + path: ${{ inputs.artifact-path }} + retention-days: ${{ inputs.retention-days }} + if-no-files-found: ${{ inputs.if-no-files-found }} + compression-level: ${{ inputs.compression-level }} + overwrite: true + env: + ACTIONS_UPLOAD_RETRY_COUNT: 3 + + # ------------------------------------------------------------------ + # Delay before final retry + # ------------------------------------------------------------------ + - name: "⏳ Wait before final retry (${{ inputs.artifact-name }})" + if: steps.attempt1.outcome == 'failure' && steps.attempt2.outcome == 'failure' + shell: bash + run: | + echo "::warning::Upload attempt 2 for '${{ inputs.artifact-name }}' failed, retrying in 30s..." + sleep 30 + + # ------------------------------------------------------------------ + # Attempt 3 (final -- continue-on-error depends on criticality input) + # ------------------------------------------------------------------ + - name: "📤 Upload ${{ inputs.artifact-name }} (attempt 3 - final)" + id: attempt3 + if: steps.attempt1.outcome == 'failure' && steps.attempt2.outcome == 'failure' + continue-on-error: ${{ inputs.continue-on-error == 'true' }} + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + with: + name: ${{ inputs.artifact-name }} + path: ${{ inputs.artifact-path }} + retention-days: ${{ inputs.retention-days }} + if-no-files-found: ${{ inputs.if-no-files-found }} + compression-level: ${{ inputs.compression-level }} + overwrite: true + env: + ACTIONS_UPLOAD_RETRY_COUNT: 3 diff --git a/.github/env/10-coverage.env b/.github/env/10-coverage.env index 5125fef..a3049b7 100644 --- a/.github/env/10-coverage.env +++ b/.github/env/10-coverage.env @@ -32,7 +32,7 @@ GO_COVERAGE_PROVIDER=internal CODECOV_TOKEN_REQUIRED=false # Go Coverage Tool Version -GO_COVERAGE_VERSION=v1.3.5 +GO_COVERAGE_VERSION=v1.3.7 GO_COVERAGE_USE_LOCAL=false # ================================================================================================ diff --git a/.github/env/10-mage-x.env b/.github/env/10-mage-x.env index 01ef67f..c7c85bd 100644 --- a/.github/env/10-mage-x.env +++ b/.github/env/10-mage-x.env @@ -36,7 +36,7 @@ # ================================================================================================ # MAGE-X version -MAGE_X_VERSION=v1.20.4 +MAGE_X_VERSION=v1.20.7 # For mage-x development, set to 'true' to use local version instead of downloading from releases MAGE_X_USE_LOCAL=false @@ -61,7 +61,7 @@ MAGE_X_FORMAT_EXCLUDE_PATHS=vendor,node_modules,.git,.idea MAGE_X_GITLEAKS_VERSION=8.30.0 MAGE_X_GOFUMPT_VERSION=v0.9.2 -MAGE_X_GOLANGCI_LINT_VERSION=v2.9.0 +MAGE_X_GOLANGCI_LINT_VERSION=v2.10.1 MAGE_X_GORELEASER_VERSION=v2.13.3 MAGE_X_GOVULNCHECK_VERSION=v1.1.4 MAGE_X_GO_SECONDARY_VERSION=1.24.x diff --git a/.github/env/10-pre-commit.env b/.github/env/10-pre-commit.env index 9b7477c..7a20e6b 100644 --- a/.github/env/10-pre-commit.env +++ b/.github/env/10-pre-commit.env @@ -26,7 +26,7 @@ # 🪝 PRE-COMMIT TOOL VERSION # ================================================================================================ -GO_PRE_COMMIT_VERSION=v1.6.1 +GO_PRE_COMMIT_VERSION=v1.6.2 GO_PRE_COMMIT_USE_LOCAL=false # ================================================================================================ @@ -52,7 +52,7 @@ GO_PRE_COMMIT_ALL_FILES=true # 🛠️ TOOL VERSIONS # ================================================================================================ -GO_PRE_COMMIT_GOLANGCI_LINT_VERSION=v2.9.0 +GO_PRE_COMMIT_GOLANGCI_LINT_VERSION=v2.10.1 GO_PRE_COMMIT_FUMPT_VERSION=v0.9.2 GO_PRE_COMMIT_GOIMPORTS_VERSION=latest GO_PRE_COMMIT_GITLEAKS_VERSION=v8.30.0 diff --git a/.github/workflows/fortress-benchmarks.yml b/.github/workflows/fortress-benchmarks.yml index 7f636dc..6208934 100644 --- a/.github/workflows/fortress-benchmarks.yml +++ b/.github/workflows/fortress-benchmarks.yml @@ -390,19 +390,19 @@ jobs: # -------------------------------------------------------------------- - name: 📤 Upload benchmark statistics if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + uses: ./.github/actions/upload-artifact-resilient with: - name: bench-stats-${{ matrix.os }}-${{ matrix.go-version }} - path: bench-stats-${{ matrix.os }}-${{ matrix.go-version }}.json - retention-days: 7 + artifact-name: bench-stats-${{ matrix.os }}-${{ matrix.go-version }} + artifact-path: bench-stats-${{ matrix.os }}-${{ matrix.go-version }}.json + retention-days: "7" # -------------------------------------------------------------------- # Upload raw benchmark results # -------------------------------------------------------------------- - name: 📤 Upload benchmark results if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + uses: ./.github/actions/upload-artifact-resilient with: - name: bench-results-${{ matrix.os }}-${{ matrix.go-version }} - path: bench-results-${{ matrix.os }}-${{ matrix.go-version }}.txt - retention-days: 7 # Keep raw results longer for analysis + artifact-name: bench-results-${{ matrix.os }}-${{ matrix.go-version }} + artifact-path: bench-results-${{ matrix.os }}-${{ matrix.go-version }}.txt + retention-days: "7" diff --git a/.github/workflows/fortress-code-quality.yml b/.github/workflows/fortress-code-quality.yml index a50be5c..63d92e6 100644 --- a/.github/workflows/fortress-code-quality.yml +++ b/.github/workflows/fortress-code-quality.yml @@ -226,11 +226,11 @@ jobs: # -------------------------------------------------------------------- - name: 📤 Upload go vet results if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + uses: ./.github/actions/upload-artifact-resilient with: - name: govet-results - path: govet-output.log - retention-days: 7 + artifact-name: govet-results + artifact-path: govet-output.log + retention-days: "7" if-no-files-found: ignore # -------------------------------------------------------------------- @@ -513,11 +513,11 @@ jobs: # -------------------------------------------------------------------- - name: 📤 Upload lint results if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + uses: ./.github/actions/upload-artifact-resilient with: - name: lint-results - path: lint-output.log - retention-days: 7 + artifact-name: lint-results + artifact-path: lint-output.log + retention-days: "7" if-no-files-found: ignore # -------------------------------------------------------------------- @@ -765,11 +765,11 @@ jobs: # -------------------------------------------------------------------- - name: 📤 Upload format check results if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + uses: ./.github/actions/upload-artifact-resilient with: - name: format-check-results - path: format-output.log - retention-days: 7 + artifact-name: format-check-results + artifact-path: format-output.log + retention-days: "7" if-no-files-found: ignore # -------------------------------------------------------------------- diff --git a/.github/workflows/fortress-completion-statistics.yml b/.github/workflows/fortress-completion-statistics.yml index 72fd9ee..aaf0e08 100644 --- a/.github/workflows/fortress-completion-statistics.yml +++ b/.github/workflows/fortress-completion-statistics.yml @@ -685,11 +685,11 @@ jobs: # -------------------------------------------------------------------- - name: 📤 Upload LOC Stats JSON if: always() && hashFiles('loc-stats.json') != '' - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + uses: ./.github/actions/upload-artifact-resilient with: - name: loc-stats - path: loc-stats.json - retention-days: 7 + artifact-name: loc-stats + artifact-path: loc-stats.json + retention-days: "7" - name: 📤 Upload Statistics Section id: upload-section diff --git a/.github/workflows/fortress-coverage.yml b/.github/workflows/fortress-coverage.yml index e4d8274..bfc1331 100644 --- a/.github/workflows/fortress-coverage.yml +++ b/.github/workflows/fortress-coverage.yml @@ -2367,22 +2367,22 @@ jobs: # -------------------------------------------------------------------- - name: 📤 Upload performance cache statistics if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + uses: ./.github/actions/upload-artifact-resilient with: - name: cache-stats-coverage - path: cache-stats-coverage.json - retention-days: 1 + artifact-name: cache-stats-coverage + artifact-path: cache-stats-coverage.json + retention-days: "1" # -------------------------------------------------------------------- # Upload coverage statistics for completion report # -------------------------------------------------------------------- - name: 📤 Upload coverage statistics if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + uses: ./.github/actions/upload-artifact-resilient with: - name: coverage-stats-internal - path: coverage-stats-internal-*.json - retention-days: 1 + artifact-name: coverage-stats-internal + artifact-path: coverage-stats-internal-*.json + retention-days: "7" # -------------------------------------------------------------------- # Upload coverage history for future runs (WORKING SYSTEM - PRESERVED) @@ -2410,13 +2410,12 @@ jobs: - name: 📤 Upload coverage history artifacts # Upload history for all branches to preserve trend data if: github.event_name == 'push' - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + uses: ./.github/actions/upload-artifact-resilient with: - name: coverage-history-${{ inputs.commit-sha }} - path: .github/coverage/history/*.json - retention-days: 90 - compression-level: 9 - continue-on-error: true + artifact-name: coverage-history-${{ inputs.commit-sha }} + artifact-path: .github/coverage/history/*.json + retention-days: "90" + compression-level: "9" # ---------------------------------------------------------------------------------- # Upload Coverage to Codecov (External Provider) # ---------------------------------------------------------------------------------- @@ -2594,8 +2593,8 @@ jobs: - name: 📤 Upload coverage statistics (Codecov) if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + uses: ./.github/actions/upload-artifact-resilient with: - name: coverage-stats-codecov - path: coverage-stats-codecov-*.json - retention-days: 7 + artifact-name: coverage-stats-codecov + artifact-path: coverage-stats-codecov-*.json + retention-days: "7" diff --git a/.github/workflows/fortress-security-scans.yml b/.github/workflows/fortress-security-scans.yml index b905908..0e9d477 100644 --- a/.github/workflows/fortress-security-scans.yml +++ b/.github/workflows/fortress-security-scans.yml @@ -219,11 +219,11 @@ jobs: # -------------------------------------------------------------------- - name: 📤 Upload Nancy scan results if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + uses: ./.github/actions/upload-artifact-resilient with: - name: nancy-scan-results - path: nancy-output.log - retention-days: 7 + artifact-name: nancy-scan-results + artifact-path: nancy-output.log + retention-days: "7" if-no-files-found: ignore # -------------------------------------------------------------------- @@ -458,11 +458,11 @@ jobs: # -------------------------------------------------------------------- - name: 📤 Upload govulncheck results if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + uses: ./.github/actions/upload-artifact-resilient with: - name: govulncheck-scan-results - path: govulncheck-output.log - retention-days: 7 + artifact-name: govulncheck-scan-results + artifact-path: govulncheck-output.log + retention-days: "7" if-no-files-found: ignore # -------------------------------------------------------------------- diff --git a/.github/workflows/fortress-test-fuzz.yml b/.github/workflows/fortress-test-fuzz.yml index 5188d68..a184b63 100644 --- a/.github/workflows/fortress-test-fuzz.yml +++ b/.github/workflows/fortress-test-fuzz.yml @@ -251,11 +251,11 @@ jobs: # -------------------------------------------------------------------- - name: 📤 Upload fuzz test outputs if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + uses: ./.github/actions/upload-artifact-resilient with: - name: test-results-fuzz-${{ inputs.primary-runner }}-${{ inputs.go-primary-version }} - path: | + artifact-name: test-results-fuzz-${{ inputs.primary-runner }}-${{ inputs.go-primary-version }} + artifact-path: | .mage-x/ci-results-fuzz.jsonl fuzz-output.log - retention-days: 1 + retention-days: "7" if-no-files-found: ignore diff --git a/.github/workflows/fortress-test-matrix.yml b/.github/workflows/fortress-test-matrix.yml index 4abb264..e2bcd22 100644 --- a/.github/workflows/fortress-test-matrix.yml +++ b/.github/workflows/fortress-test-matrix.yml @@ -401,13 +401,13 @@ jobs: # -------------------------------------------------------------------- - name: 📤 Upload CI results if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + uses: ./.github/actions/upload-artifact-resilient with: - name: ci-results-${{ matrix.os }}-${{ matrix.go-version }} - path: | + artifact-name: ci-results-${{ matrix.os }}-${{ matrix.go-version }} + artifact-path: | .mage-x/ci-results.jsonl test-output.log - retention-days: 1 + retention-days: "7" if-no-files-found: ignore # -------------------------------------------------------------------- @@ -442,8 +442,9 @@ jobs: # -------------------------------------------------------------------- - name: 📤 Upload coverage data if: inputs.code-coverage-enabled == 'true' && hashFiles('coverage.txt') != '' && matrix.os == inputs.primary-runner && matrix.go-version == inputs.go-primary-version - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + uses: ./.github/actions/upload-artifact-resilient with: - name: coverage-data - path: coverage.txt - retention-days: 1 + artifact-name: coverage-data + artifact-path: coverage.txt + retention-days: "7" + continue-on-error: "false" diff --git a/.github/workflows/fortress-test-validation.yml b/.github/workflows/fortress-test-validation.yml index 49c616b..8112a6d 100644 --- a/.github/workflows/fortress-test-validation.yml +++ b/.github/workflows/fortress-test-validation.yml @@ -412,9 +412,9 @@ jobs: # -------------------------------------------------------------------- - name: 📤 Upload validation summary if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + uses: ./.github/actions/upload-artifact-resilient with: - name: validation-summary - path: ci-results/ - retention-days: 1 + artifact-name: validation-summary + artifact-path: ci-results/ + retention-days: "7" if-no-files-found: ignore diff --git a/.github/workflows/pull-request-management-fork.yml b/.github/workflows/pull-request-management-fork.yml index f010f33..fa01561 100644 --- a/.github/workflows/pull-request-management-fork.yml +++ b/.github/workflows/pull-request-management-fork.yml @@ -118,7 +118,7 @@ jobs: name: 🌍 Load Environment (Base Repo) runs-on: ubuntu-latest # Only run for fork PRs - same-repo PRs are handled by pull-request-management.yml - if: ${{ github.event.pull_request.head.repo.full_name != github.repository }} + if: ${{ github.event.pull_request.head.repo && github.event.pull_request.head.repo.full_name != github.repository }} # No write perms here permissions: contents: read @@ -178,7 +178,7 @@ jobs: name: 🔍 Detect Fork PR runs-on: ubuntu-latest # Only run for fork PRs - same-repo PRs are handled by pull-request-management.yml - if: ${{ github.event.pull_request.head.repo.full_name != github.repository }} + if: ${{ github.event.pull_request.head.repo && github.event.pull_request.head.repo.full_name != github.repository }} permissions: contents: read outputs: @@ -481,7 +481,7 @@ jobs: name: 📊 Summary runs-on: ubuntu-latest # Only run for fork PRs, but always show summary regardless of job status - if: always() && github.event.pull_request.head.repo.full_name != github.repository + if: always() && github.event.pull_request.head.repo && github.event.pull_request.head.repo.full_name != github.repository needs: [load-env, detect-fork, handle-fork, clean-cache] steps: - name: 📄 Write summary diff --git a/arcade.go b/arcade.go index bc9b104..b033dc2 100644 --- a/arcade.go +++ b/arcade.go @@ -459,7 +459,7 @@ func (a *Arcade) buildMerklePathsForSubtree( var txOffset uint64 for i, h := range txHashes { if h == trackedHash { - txOffset = uint64(i) //nolint:gosec // safe: i is from slice iteration + txOffset = uint64(i) break } } @@ -473,7 +473,7 @@ func (a *Arcade) buildMerklePathsForSubtree( hashCopy := h isTxid := true mp.AddLeaf(0, &transaction.PathElement{ - Offset: uint64(i), //nolint:gosec // safe: i is from slice iteration + Offset: uint64(i), Hash: &hashCopy, Txid: &isTxid, }) @@ -494,7 +494,7 @@ func (a *Arcade) buildMerklePathsForSubtree( } hashCopy := subHash mp.AddLeaf(internalHeight, &transaction.PathElement{ - Offset: subtreeBaseOffset + uint64(i), //nolint:gosec // safe: i is from slice iteration + Offset: subtreeBaseOffset + uint64(i), Hash: &hashCopy, }) } @@ -976,7 +976,7 @@ func (a *Arcade) fetchBlockSubtrees(ctx context.Context, url string) ([]chainhas return nil, fmt.Errorf("failed to create request: %w", err) } - resp, err := a.httpClient.Do(req) + resp, err := a.httpClient.Do(req) //nolint:gosec // G704: URL is from configured datahub URLs, not user-controlled input if err != nil { return nil, fmt.Errorf("failed to fetch: %w", err) } @@ -1058,7 +1058,7 @@ func (a *Arcade) fetchHashes(ctx context.Context, url string) ([]chainhash.Hash, return nil, fmt.Errorf("failed to create request: %w", err) } - resp, err := a.httpClient.Do(req) + resp, err := a.httpClient.Do(req) //nolint:gosec // G704: URL is from configured datahub URLs, not user-controlled input if err != nil { return nil, fmt.Errorf("failed to fetch: %w", err) } diff --git a/client/client.go b/client/client.go index 5c2b1b5..7a9e961 100644 --- a/client/client.go +++ b/client/client.go @@ -70,7 +70,7 @@ func (c *Client) SubmitTransaction(ctx context.Context, rawTx []byte, opts *mode req.Header.Set("Content-Type", "application/octet-stream") c.setSubmitHeaders(req, opts) - resp, err := c.httpClient.Do(req) + resp, err := c.httpClient.Do(req) //nolint:gosec // G704: URL is built from configured baseURL, not user-controlled input if err != nil { return nil, fmt.Errorf("failed to submit transaction: %w", err) } @@ -113,7 +113,7 @@ func (c *Client) SubmitTransactions(ctx context.Context, rawTxs [][]byte, opts * req.Header.Set("Content-Type", "application/json") c.setSubmitHeaders(req, opts) - resp, err := c.httpClient.Do(req) + resp, err := c.httpClient.Do(req) //nolint:gosec // G704: URL is built from configured baseURL, not user-controlled input if err != nil { return nil, fmt.Errorf("failed to submit transactions: %w", err) } @@ -138,7 +138,7 @@ func (c *Client) GetStatus(ctx context.Context, txid string) (*models.Transactio return nil, fmt.Errorf("failed to create request: %w", err) } - resp, err := c.httpClient.Do(req) + resp, err := c.httpClient.Do(req) //nolint:gosec // G704: URL is built from configured baseURL, not user-controlled input if err != nil { return nil, fmt.Errorf("failed to get status: %w", err) } @@ -176,7 +176,7 @@ func (c *Client) GetPolicy(ctx context.Context) (*models.Policy, error) { return nil, fmt.Errorf("failed to create request: %w", err) } - resp, err := c.httpClient.Do(req) + resp, err := c.httpClient.Do(req) //nolint:gosec // G704: URL is built from configured baseURL, not user-controlled input if err != nil { return nil, fmt.Errorf("failed to get policy: %w", err) } diff --git a/client/sse.go b/client/sse.go index b73f596..e3385d7 100644 --- a/client/sse.go +++ b/client/sse.go @@ -199,7 +199,7 @@ func (m *sseManager) connectSSE(conn *sseConnection) error { req.Header.Set("Last-Event-ID", conn.lastEventID) } - resp, err := m.client.httpClient.Do(req) + resp, err := m.client.httpClient.Do(req) //nolint:gosec // G704: URL is built from configured baseURL, not user-controlled input if err != nil { return fmt.Errorf("failed to connect: %w", err) } diff --git a/config/config.go b/config/config.go index a915a55..bb04aaf 100644 --- a/config/config.go +++ b/config/config.go @@ -116,7 +116,7 @@ type EventsConfig struct { type TeranodeConfig struct { BroadcastURLs []string `mapstructure:"broadcast_urls"` // URLs for submitting transactions DataHubURLs []string `mapstructure:"datahub_urls"` // URLs for fetching block/subtree data (fallback) - AuthToken string `mapstructure:"auth_token"` + AuthToken string `mapstructure:"auth_token"` //nolint:gosec // G117: this is a config field name, not a hardcoded secret Timeout time.Duration `mapstructure:"timeout"` } diff --git a/examples/sse_client.go b/examples/sse_client.go index 1d1f51e..5128714 100644 --- a/examples/sse_client.go +++ b/examples/sse_client.go @@ -28,14 +28,14 @@ func main() { req.Header.Set("Accept", "text/event-stream") client := &http.Client{} - resp, err := client.Do(req) + resp, err := client.Do(req) //nolint:gosec // G704: URL is constructed from a localhost constant, not user input if err != nil { log.Fatal(err) } defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { - log.Fatalf("Failed to connect: %s", resp.Status) + log.Fatalf("Failed to connect: %s", resp.Status) //nolint:gosec // G706: resp.Status is from the HTTP response, not external user input } log.Println("Connected to SSE stream...") @@ -49,7 +49,7 @@ func main() { if line == "" { // Empty line signals end of event if eventData != "" { - log.Printf("[ID: %s] [Type: %s] %s\n", eventID, eventType, eventData) + log.Printf("[ID: %s] [Type: %s] %s\n", eventID, eventType, eventData) //nolint:gosec // G706: SSE event data is parsed from trusted server response in this example eventID, eventType, eventData = "", "", "" } continue diff --git a/handlers/webhook.go b/handlers/webhook.go index 907c50f..a3339e8 100644 --- a/handlers/webhook.go +++ b/handlers/webhook.go @@ -183,7 +183,7 @@ func (h *WebhookHandler) deliverWebhook(ctx context.Context, sub models.Submissi req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", sub.CallbackToken)) } - resp, err := h.httpClient.Do(req) + resp, err := h.httpClient.Do(req) //nolint:gosec // G704: URL is from a user-registered callback, validated at registration time if err != nil { h.logger.Error("Failed to deliver webhook", slog.String("submission_id", sub.SubmissionID), diff --git a/service/embedded/embedded.go b/service/embedded/embedded.go index 2e6b649..0f23934 100644 --- a/service/embedded/embedded.go +++ b/service/embedded/embedded.go @@ -138,7 +138,7 @@ func (e *Embedded) SubmitTransaction(ctx context.Context, rawTx []byte, opts *mo for _, output := range tx.Outputs { outputSats += output.Satoshis } - actualFee := int64(inputSats) - int64(outputSats) //nolint:gosec // safe: subtraction of uint64 values + actualFee := int64(inputSats) - int64(outputSats) txSize := tx.Size() var feePerKB float64 if txSize > 0 { @@ -282,7 +282,7 @@ func (e *Embedded) SubmitTransactions(ctx context.Context, rawTxs [][]byte, opts for _, output := range tx.Outputs { outputSats += output.Satoshis } - actualFee := int64(inputSats) - int64(outputSats) //nolint:gosec // safe: subtraction of uint64 values + actualFee := int64(inputSats) - int64(outputSats) txSize := tx.Size() var feePerKB float64 if txSize > 0 { diff --git a/teranode/client.go b/teranode/client.go index ecb533c..caecb51 100644 --- a/teranode/client.go +++ b/teranode/client.go @@ -50,7 +50,7 @@ func (c *Client) SubmitTransaction(ctx context.Context, endpoint string, rawTx [ req.Header.Set("Authorization", "Bearer "+c.authToken) } - resp, err := c.httpClient.Do(req) + resp, err := c.httpClient.Do(req) //nolint:gosec // G704: URL is from configured teranode broadcast URLs, not user-controlled input if err != nil { return 0, fmt.Errorf("failed to submit transaction: %w", err) }