From e86b6ef056baf8fec71cd5338f1da130e3cb0e57 Mon Sep 17 00:00:00 2001 From: Abhisek Datta Date: Thu, 15 May 2025 16:50:59 +0530 Subject: [PATCH] feat: Refactor PMG to Maintain Separation of Concerns and Clean Architecture (#19) * feat: Add separate package manager and resolver * fix: Npm dependency resolver * feat: Add analyzer for malysis query * feat: Add package manager guard as the orchestrator * feat: Add PMG to orchestrate installation * Add concurrent scan execution * Introduce package manager interaction abstraction * feat: Add UI port for guard * Remove refactored source files * Update README * fix: CI script for multi-arch build * ci: goreleaser CI fix * fix: npm command parser to extract package names * feat: Introduce global config primitive * fix: Close results channel for clean goroutine exit * ci: Add container image releaser * test: Improve test for npm resolver * refactor: Analyzer to generalise * Improve UI with additional info * fix: Goreleaser config * fix: npm resolver bug * fix: Fail when command exec workflow fails * fix: Bug with transitive dependency resolution * fix: Synchronize common data update in dependency resolver * chore: Improve log handling * docs: Update README * fix: UI text wrapping * fix: UI handling bugs * feat: Use concurrent dependency resolver --- .github/workflows/ci.yml | 77 ++ .github/workflows/container.yml | 70 ++ .github/workflows/goreleaser.yml | 41 +- .goreleaser.yaml | 20 +- .tool-versions | 2 + Dockerfile | 29 + README.md | 195 +++-- analyzer/analyzer.go | 47 ++ analyzer/malysis_query.go | 80 +++ cmd/npm/common.go | 62 ++ cmd/npm/npm.go | 41 +- cmd/npm/pnpm.go | 41 +- config/config.go | 33 + go.mod | 236 +++++-- go.sum | 664 ++++++++++++------ guard/guard.go | 258 +++++++ .../ui/colors.go | 16 +- internal/ui/spinner.go | 53 ++ internal/ui/ui.go | 150 ++++ internal/ui/ui_test.go | 79 +++ lefthook.yml | 17 + main.go | 55 +- packagemanager/dependency_resolver.go | 187 +++++ packagemanager/npm.go | 156 ++++ packagemanager/npm_resolver.go | 84 +++ packagemanager/npm_resolver_test.go | 167 +++++ packagemanager/npm_test.go | 123 ++++ packagemanager/packagemanager.go | 54 ++ pkg/analyser/analysis.go | 90 --- pkg/analyser/client.go | 16 - pkg/common/extractor.go | 87 --- pkg/common/grpc.go | 25 - pkg/common/utils/command.go | 18 - pkg/common/utils/config.go | 51 -- pkg/common/utils/interpreter.go | 14 - pkg/common/utils/markdown.go | 54 -- pkg/common/utils/npm.go | 64 -- pkg/common/utils/security_confirmation.go | 35 - pkg/common/utils/utils.go | 21 - pkg/common/utils/utils_test.go | 212 ------ pkg/models/models.go | 26 - pkg/registry/client.go | 110 --- pkg/registry/factory.go | 38 - pkg/registry/fetcher.go | 84 --- pkg/registry/npm_fetcher.go | 175 ----- pkg/wrapper/errors.go | 11 - pkg/wrapper/npm_base.go | 182 ----- 47 files changed, 2562 insertions(+), 1788 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/container.yml create mode 100644 .tool-versions create mode 100644 Dockerfile create mode 100644 analyzer/analyzer.go create mode 100644 analyzer/malysis_query.go create mode 100644 cmd/npm/common.go create mode 100644 config/config.go create mode 100644 guard/guard.go rename pkg/common/utils/terminal_colors.go => internal/ui/colors.go (51%) create mode 100644 internal/ui/spinner.go create mode 100644 internal/ui/ui.go create mode 100644 internal/ui/ui_test.go create mode 100644 lefthook.yml create mode 100644 packagemanager/dependency_resolver.go create mode 100644 packagemanager/npm.go create mode 100644 packagemanager/npm_resolver.go create mode 100644 packagemanager/npm_resolver_test.go create mode 100644 packagemanager/npm_test.go create mode 100644 packagemanager/packagemanager.go delete mode 100644 pkg/analyser/analysis.go delete mode 100644 pkg/analyser/client.go delete mode 100644 pkg/common/extractor.go delete mode 100644 pkg/common/grpc.go delete mode 100644 pkg/common/utils/command.go delete mode 100644 pkg/common/utils/config.go delete mode 100644 pkg/common/utils/interpreter.go delete mode 100644 pkg/common/utils/markdown.go delete mode 100644 pkg/common/utils/npm.go delete mode 100644 pkg/common/utils/security_confirmation.go delete mode 100644 pkg/common/utils/utils.go delete mode 100644 pkg/common/utils/utils_test.go delete mode 100644 pkg/models/models.go delete mode 100644 pkg/registry/client.go delete mode 100644 pkg/registry/factory.go delete mode 100644 pkg/registry/fetcher.go delete mode 100644 pkg/registry/npm_fetcher.go delete mode 100644 pkg/wrapper/errors.go delete mode 100644 pkg/wrapper/npm_base.go diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..2265238 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,77 @@ +name: CI +on: + pull_request: + branches: + - main + push: + branches: + - main + +permissions: + contents: read + +jobs: + run-test: + timeout-minutes: 15 + runs-on: ubuntu-latest + steps: + - name: Checkout Source + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + + - name: Set up Go + uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5 + with: + go-version: 1.24 + check-latest: true + + - name: Build and Test + run: | + go mod tidy + go build + go test -coverprofile=coverage.txt -v ./... + + - name: Upload Coverage + if: (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) || github.event_name == 'push' + uses: codecov/codecov-action@0565863a31f2c772f9f0395002a31e3f06189574 # v5 + with: + token: ${{ secrets.CODECOV_TOKEN }} + + goreleaser-test: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Checkout Source + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + + - name: Setup Go + uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5 + with: + go-version: 1.24 + check-latest: true + + - name: Run Goreleaser + uses: goreleaser/goreleaser-action@286f3b13b1b49da4ac219696163fb8c1c93e1200 # v6.0.0 + with: + distribution: goreleaser + version: "~> v2" + args: build --clean --snapshot + + build-container-test: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Checkout Source + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + + - name: Setup QEMU + uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3 + + - name: Setup Docker Buildx + uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3 + + - name: Build Container Image + run: | + docker buildx build --platform linux/amd64 --load \ + -t build-container-test:latest . + + diff --git a/.github/workflows/container.yml b/.github/workflows/container.yml new file mode 100644 index 0000000..86df601 --- /dev/null +++ b/.github/workflows/container.yml @@ -0,0 +1,70 @@ +name: Container Image Releaser + +on: + push: + tags: + - "v[0-9]+.[0-9]+.[0-9]+" + branches: + - "main" + +concurrency: ci-container-release + +permissions: + contents: read + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + build: + timeout-minutes: 30 + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + id-token: write + + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + with: + submodules: true + fetch-depth: 0 + + - name: Registry Login + uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup QEMU + uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3 + + - name: Setup Docker Buildx + uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3 + + - name: Build and Push Container Image + run: | + # Get the tag if this was a tag push event + if [[ "${{ github.ref_type }}" == "tag" ]]; then + TAG=${{ github.ref_name }} + # Validate tag format (must be vX.Y.Z) + if [[ $TAG =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + # Build and push with both version tag and latest + docker buildx build --push --platform linux/amd64,linux/arm64 \ + -t $REGISTRY/$IMAGE_NAME:$TAG \ + -t $REGISTRY/$IMAGE_NAME:latest \ + . + else + echo "Invalid tag format. Must be in format vX.Y.Z (e.g. v1.2.3)" + exit 1 + fi + else + # For non-tag pushes, just use latest tag + docker buildx build --push --platform linux/amd64,linux/arm64 \ + -t $REGISTRY/$IMAGE_NAME:latest \ + . + fi + + diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml index daf1270..e96a25b 100644 --- a/.github/workflows/goreleaser.yml +++ b/.github/workflows/goreleaser.yml @@ -3,7 +3,7 @@ name: Release Automation on: push: tags: - - "v[0-9]+.[0-9]+.[0-9]+" + - 'v[0-9]+.[0-9]+.[0-9]+' concurrency: ci-release-automation @@ -16,7 +16,7 @@ jobs: outputs: hashes: ${{ steps.hash.outputs.hashes }} permissions: - contents: write # for goreleaser/goreleaser-action to create a GitHub release + contents: write # for goreleaser/goreleaser-action to create a GitHub release packages: write # for goreleaser/goreleaser-action to publish docker images runs-on: ubuntu-latest env: @@ -24,49 +24,24 @@ jobs: DOCKER_CLI_EXPERIMENTAL: "enabled" steps: - name: Checkout - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 with: fetch-depth: 0 - - uses: docker/setup-qemu-action@e81a89b1732b9c48d79cd809d8d81d79c4647a18 # v2 - - uses: docker/setup-buildx-action@8c0edbc76e98fa90f69d9a2c020dcb50019dc325 # v2 + - name: Set up Go - uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 + uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5 with: go-version: 1.24 check-latest: true - - name: ghcr-login - uses: docker/login-action@dd4fa0671be5250ee6f50aedf4cb05514abda2c7 # v1 - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - name: Run GoReleaser id: run-goreleaser - uses: goreleaser/goreleaser-action@286f3b13b1b49da4ac219696163fb8c1c93e1200 # v6.0.0 + uses: goreleaser/goreleaser-action@5742e2a039330cbb23ebf35f046f814d4c6ff811 # v5 with: distribution: goreleaser - version: "~> v2" + version: '~> v2' args: release --clean env: GITHUB_TOKEN: ${{ secrets.GORELEASER_GITHUB_TOKEN }} - - name: Generate subject - id: hash - env: - ARTIFACTS: "${{ steps.run-goreleaser.outputs.artifacts }}" - run: | - set -euo pipefail - checksum_file=$(echo "$ARTIFACTS" | jq -r '.[] | select (.type=="Checksum") | .path') - echo "hashes=$(cat $checksum_file | base64 -w0)" >> "$GITHUB_OUTPUT" - provenance: - needs: [goreleaser] - permissions: - actions: read # To read the workflow path. - id-token: write # To sign the provenance. - contents: write # To add assets to a release. - uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@f7dd8c54c2067bafc12ca7a55595d5ee9b75204a # v2.1.0 - with: - base64-subjects: "${{ needs.goreleaser.outputs.hashes }}" - upload-assets: true - private-repository: false + diff --git a/.goreleaser.yaml b/.goreleaser.yaml index b8ba758..ae6040a 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -32,9 +32,21 @@ changelog: - "^docs:" - "^test:" -release: - footer: >- +checksum: + name_template: 'checksums.txt' + algorithm: sha256 + +universal_binaries: + - replace: true + +brews: + - name: pmg + homepage: https://github.com/safedep/pmg + description: "PMG protects developers from malicious packages" + license: "Apache-2.0" + repository: + owner: safedep + name: homebrew-tap + branch: main - --- - Released by [GoReleaser](https://github.com/goreleaser/goreleaser). diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..8efd111 --- /dev/null +++ b/.tool-versions @@ -0,0 +1,2 @@ +golang 1.24.1 +gitleaks 8.21.2 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..6f59233 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,29 @@ +FROM --platform=$BUILDPLATFORM golang:1.24-bullseye@sha256:3c669c8fed069d80d199073b806243c4bf79ad117b797b96f18177ad9c521cff AS build +# Original: golang:1.24-bullseye + +WORKDIR /build + +COPY go.mod go.sum ./ + +RUN go mod download + +COPY . . + +RUN make + +FROM debian:11-slim@sha256:e4b93db6aad977a95aa103917f3de8a2b16ead91cf255c3ccdb300c5d20f3015 +# Original: debian:11-slim + +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +ARG TARGETPLATFORM + +LABEL org.opencontainers.image.source=https://github.com/safedep/pmg +LABEL org.opencontainers.image.description="Package Manager Guard to protect against malicious open source packages" +LABEL org.opencontainers.image.licenses=Apache-2.0 + +COPY --from=build /build/bin/pmg /usr/local/bin/pmg + +ENTRYPOINT ["pmg"] diff --git a/README.md b/README.md index 467e55a..ac1b15b 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,77 @@ -# PMG (Package Manager Guard) +# Package Manager Guard (PMG) +🤖 PMG protects developers from getting compromised by malicious packages. +See [example](https://safedep.io/malicious-npm-package-express-cookie-parser/) -PMG is a security-focused wrapper for package managers that helps detect and prevent the installation of potentially malicious packages. +- Wraps your favorite package manager (eg. `npm`) +- Blocks malicious packages at install time +- No configuration required, just install and use +## TL;DR + +Set up `pmg` to protect you development environment from malicious packages: + +``` +echo "alias npm='pmg npm'" >> ~/.zshrc +echo "alias pnpm='pmg pnpm'" >> ~/.zshrc +source ~/.zshrc +``` + +Continue using your favorite package manager as usual: + +``` +npm install +pnpm add +``` ## 📑 Table of Contents -- [Features](#features) -- [Supported Ecosystems](#supported-ecosystems) -- [Installation](#installation) -- [Prerequisites](#prerequisites) -- [Configuration](#configuration) -- [Usage](#usage) - - [NPM Packages](#npm-packages) - - [PNPM Packages](#pnpm-packages) - - [Common Flags](#common-flags) -- [Contributing](#contributing) +- [Package Manager Guard (PMG)](#package-manager-guard-pmg) + - [TL;DR](#tldr) + - [📑 Table of Contents](#-table-of-contents) + - [Features](#features) + - [Supported Package Managers](#supported-package-managers) + - [Installation](#installation) + - [Binaries](#binaries) + - [Build from Source](#build-from-source) + - [Usage](#usage) + - [Silent Mode](#silent-mode) + - [Verbose Mode](#verbose-mode) + - [Debugging](#debugging) + - [PMG in Action](#pmg-in-action) + - [Malicious Package Detection](#malicious-package-detection) + - [Bulk Package Analysis](#bulk-package-analysis) + - [Contributing](#contributing) + - [Limitations](#limitations) ## Features -- 🚫 Malware detection and prevention -- 🌲 Deep dependency analysis + +- 🚫 Malicious package identification using [SafeDep Cloud](https://docs.safedep.io/cloud/malware-analysis) +- 🌲 Deep dependency analysis and transitive dependency resolution - ⚡ Fast and efficient package verification - 🔄 Seamless integration with existing package managers -## Supported Ecosystems -Currently, PMG supports the following package ecosystems: +## Supported Package Managers -| Ecosystem | Status | Command | -|-----------|--------|---------| -| NPM | ✅ Active | `pmg npm install ` | -| PNPM | ✅ Active | `pmg pnpm add ` | -| PyPI | 🚧 Planned | Coming soon | -| Go | 🚧 Planned | Coming soon | +PMG supports the following package managers: + +| Package Manager | Status | Command | +| --------------- | --------- | --------------------------- | +| `npm` | ✅ Active | `pmg npm install ` | +| `pnpm` | ✅ Active | `pmg pnpm add ` | +| `yarn` | 🚧 Planned | | +| `pip` | 🚧 Planned | | +| `poetry` | 🚧 Planned | | +| `uv` | 🚧 Planned | | + +> Want us to support your favorite package manager? [Open an issue](https://github.com/safedep/pmg/issues) and let us know! ## Installation -- Build from source + +### Binaries + +Download the latest binary from the [releases page](https://github.com/safedep/pmg/releases). + +### Build from Source > Ensure $(go env GOPATH)/bin is in your $PATH @@ -41,24 +79,63 @@ Currently, PMG supports the following package ecosystems: go install github.com/safedep/pmg@latest ``` -## Prerequisites -- Go 1.24 -- SafeDep API credentials (SAFEDEP_API_KEY and SAFEDEP_TENANT_ID) - -## Configuration - -### Environment Variables - -| Variable | Description | Required | -|----------|-------------|----------| -| `SAFEDEP_API_KEY` | Your SafeDep API key | Yes | -| `SAFEDEP_TENANT_ID` | Your SafeDep tenant ID | Yes | - -Get your API credentials by visiting [SafeDep Quickstart Guide](https://docs.safedep.io/cloud/quickstart). - ## Usage -### Security in Action +Install a package with `npm` or `pnpm`: + +```bash +pmg npm install +pmg pnpm add +``` + +Set shell alias for convenience: + +```bash +alias npm="pmg npm" +alias pnpm="pmg pnpm" +``` + +Continue using your favorite package manager as usual: + +```bash +npm install +``` + +```bash +pnpm add +``` + +### Silent Mode + +Use the `--silent` flag to run PMG in silent mode: + +```bash +pmg --silent npm install +``` + +### Verbose Mode + +Use the `--verbose` flag to run PMG in verbose mode: + +```bash +pmg --verbose npm install +``` + +### Debugging + +Use the `--debug` flag to enable debug mode: + +```bash +pmg --debug npm install +``` + +Store the debug logs in a file: + +```bash +pmg --debug --log /tmp/debug.json npm install +``` + +### PMG in Action #### Malicious Package Detection ![pmg scan malicious package](./docs/assets/pmg-malicious-pkg.png) @@ -66,30 +143,20 @@ Get your API credentials by visiting [SafeDep Quickstart Guide](https://docs.saf #### Bulk Package Analysis ![pmg scan & install multiple package](./docs/assets/pmg-scan-multiple-pkgs.png) -### NPM Packages -Install a package: -```bash -pmg npm install -``` - -Alternative commands: -```bash -pmg npm i # Short form -pmg npm add # Alternative syntax -``` - -### PNPM Packages -Install a package: -```bash -pmg pnpm add -``` - -### Common Flags -All standard package manager flags are supported: -```bash -pmg npm install --save-dev # Install as dev dependency -pmg pnpm add -D # Install as dev dependency -``` - ## Contributing + Refer to [CONTRIBUTING.md](CONTRIBUTING.md) + +## Limitations + +
+Approximate dependency version resolution +`pmg` resolves the transitive dependencies of a package to be installed. It does it by querying +package registry APIs such as `npmjs` and `pypi`. However, almost always, dependency versions are +specified as ranges instead of specific version. Different package managers have different ways of +resolving these ranges. It also depends on peer or host dependencies already available in the application. + +`pmg` is required to block a malicious package *before* it is installed. Hence it applies its own heuristic +to choose a version from a version range for evaluation. This is fine when all versions of a given package +is malicious. However, there is a possibility of inconsistency when a specific version of a package is malicious. +
diff --git a/analyzer/analyzer.go b/analyzer/analyzer.go new file mode 100644 index 0000000..c4d80c0 --- /dev/null +++ b/analyzer/analyzer.go @@ -0,0 +1,47 @@ +package analyzer + +import ( + "context" + + packagev1 "buf.build/gen/go/safedep/api/protocolbuffers/go/safedep/messages/package/v1" +) + +// A base interface for all analyzers +type Analyzer interface { + Name() string +} + +type Action int + +const ( + ActionUnknown Action = iota + ActionAllow + ActionConfirm + ActionBlock +) + +type PackageVersionAnalysisResult struct { + PackageVersion *packagev1.PackageVersion + + // Analyser specific analysis ID + AnalysisID string + + // Reference URL for the analysis + ReferenceURL string + + // The action to take as recommended by the analyzer + Action Action + + // Summary of the analysis + Summary string + + // Analyzer specific data + Data any +} + +// Contract for implementing package version specific analyzers +type PackageVersionAnalyzer interface { + Analyzer + + Analyze(ctx context.Context, packageVersion *packagev1.PackageVersion) (*PackageVersionAnalysisResult, error) +} diff --git a/analyzer/malysis_query.go b/analyzer/malysis_query.go new file mode 100644 index 0000000..568cd65 --- /dev/null +++ b/analyzer/malysis_query.go @@ -0,0 +1,80 @@ +package analyzer + +import ( + "context" + "fmt" + "net/http" + + "buf.build/gen/go/safedep/api/grpc/go/safedep/services/malysis/v1/malysisv1grpc" + malysisv1pb "buf.build/gen/go/safedep/api/protocolbuffers/go/safedep/messages/malysis/v1" + packagev1 "buf.build/gen/go/safedep/api/protocolbuffers/go/safedep/messages/package/v1" + malysisv1 "buf.build/gen/go/safedep/api/protocolbuffers/go/safedep/services/malysis/v1" + drygrpc "github.com/safedep/dry/adapters/grpc" + "google.golang.org/grpc" +) + +type MalysisQueryAnalyzerConfig struct{} + +type malysisQueryAnalyzer struct { + client malysisv1grpc.MalwareAnalysisServiceClient + Config MalysisQueryAnalyzerConfig +} + +var _ Analyzer = &malysisQueryAnalyzer{} +var _ PackageVersionAnalyzer = &malysisQueryAnalyzer{} + +func NewMalysisQueryAnalyzer(config MalysisQueryAnalyzerConfig) (*malysisQueryAnalyzer, error) { + client, err := drygrpc.GrpcClient("pmg-malysis-query", + "community-api.safedep.io", "443", "", http.Header{}, []grpc.DialOption{}) + if err != nil { + return nil, fmt.Errorf("failed to create gRPC client: %w", err) + } + + return &malysisQueryAnalyzer{ + client: malysisv1grpc.NewMalwareAnalysisServiceClient(client), + Config: config, + }, nil +} + +func (a *malysisQueryAnalyzer) Name() string { + return "malysis-query" +} + +func (a *malysisQueryAnalyzer) Analyze(ctx context.Context, + packageVersion *packagev1.PackageVersion) (*PackageVersionAnalysisResult, error) { + + res, err := a.client.QueryPackageAnalysis(ctx, &malysisv1.QueryPackageAnalysisRequest{ + Target: &malysisv1pb.PackageAnalysisTarget{ + PackageVersion: packageVersion, + }, + }) + if err != nil { + return nil, fmt.Errorf("failed to query package analysis: %w", err) + } + + // By default, the analyzer allows the package version + analysisResult := &PackageVersionAnalysisResult{ + PackageVersion: packageVersion, + ReferenceURL: malysisReportUrl(res.GetAnalysisId()), + Action: ActionAllow, + AnalysisID: res.GetAnalysisId(), + Summary: res.GetReport().GetInference().GetSummary(), + Data: res.GetReport(), + } + + // Mark the package version to be confirmed if it is malicious (not confirmed) + if res.GetReport().GetInference().GetIsMalware() { + analysisResult.Action = ActionConfirm + } + + // This is a confirmed malicious package, we must always block it + if res.GetVerificationRecord().GetIsMalware() { + analysisResult.Action = ActionBlock + } + + return analysisResult, nil +} + +func malysisReportUrl(analysisId string) string { + return fmt.Sprintf("https://platform.safedep.io/community/malysis/%s", analysisId) +} diff --git a/cmd/npm/common.go b/cmd/npm/common.go new file mode 100644 index 0000000..e9f3ddf --- /dev/null +++ b/cmd/npm/common.go @@ -0,0 +1,62 @@ +package npm + +import ( + "context" + "fmt" + + "github.com/safedep/pmg/analyzer" + "github.com/safedep/pmg/config" + "github.com/safedep/pmg/guard" + "github.com/safedep/pmg/internal/ui" + "github.com/safedep/pmg/packagemanager" +) + +func executeCommonFlow(ctx context.Context, config config.Config, pm packagemanager.PackageManager, args []string) error { + packageResolverConfig := packagemanager.NewDefaultNpmDependencyResolverConfig() + packageResolverConfig.IncludeTransitiveDependencies = config.Transitive + packageResolverConfig.TransitiveDepth = config.TransitiveDepth + packageResolverConfig.IncludeDevDependencies = config.IncludeDevDependencies + + packageResolver, err := packagemanager.NewNpmDependencyResolver(packageResolverConfig) + if err != nil { + return fmt.Errorf("failed to create npm dependency resolver: %w", err) + } + + malysisQueryAnalyzer, err := analyzer.NewMalysisQueryAnalyzer(analyzer.MalysisQueryAnalyzerConfig{}) + if err != nil { + return fmt.Errorf("failed to create malysis query analyzer: %w", err) + } + + interaction := guard.PackageManagerGuardInteraction{ + SetStatus: ui.SetStatus, + ClearStatus: ui.ClearStatus, + GetConfirmationOnMalware: ui.GetConfirmationOnMalware, + Block: ui.Block, + } + + proxy, err := guard.NewPackageManagerGuard(guard.DefaultPackageManagerGuardConfig(), + pm, packageResolver, []analyzer.PackageVersionAnalyzer{malysisQueryAnalyzer}, interaction) + if err != nil { + return fmt.Errorf("failed to create package manager guard: %w", err) + } + + return proxy.Run(ctx, args) +} + +func executeNpmFlow(ctx context.Context, config config.Config, args []string) error { + packageManager, err := packagemanager.NewNpmPackageManager(packagemanager.DefaultNpmPackageManagerConfig()) + if err != nil { + return fmt.Errorf("failed to create npm package manager: %w", err) + } + + return executeCommonFlow(ctx, config, packageManager, args) +} + +func executePnpmFlow(ctx context.Context, config config.Config, args []string) error { + packageManager, err := packagemanager.NewNpmPackageManager(packagemanager.DefaultPnpmPackageManagerConfig()) + if err != nil { + return fmt.Errorf("failed to create pnpm package manager: %w", err) + } + + return executeCommonFlow(ctx, config, packageManager, args) +} diff --git a/cmd/npm/npm.go b/cmd/npm/npm.go index a4b1937..c4d2689 100644 --- a/cmd/npm/npm.go +++ b/cmd/npm/npm.go @@ -2,51 +2,30 @@ package npm import ( _ "embed" - "fmt" - "os" - "github.com/safedep/pmg/pkg/common/utils" - "github.com/safedep/pmg/pkg/registry" - "github.com/safedep/pmg/pkg/wrapper" + "github.com/safedep/dry/log" + "github.com/safedep/pmg/config" + "github.com/safedep/pmg/internal/ui" "github.com/spf13/cobra" ) func NewNpmCommand() *cobra.Command { - cmd := &cobra.Command{ + return &cobra.Command{ Use: "npm [action] [package]", - Short: "Scan packages from npm registry", + Short: "Guard npm package manager", DisableFlagParsing: true, RunE: func(cmd *cobra.Command, args []string) error { - execPath, err := utils.GetExecutablePath(string(registry.RegistryNPM)) + config, err := config.FromContext(cmd.Context()) if err != nil { - fmt.Fprintf(os.Stderr, "npm not found: %v\n", err) - return err + ui.Fatalf("Failed to get config: %s", err) } - if len(args) >= 2 && utils.IsInstallCommand(string(registry.RegistryNPM), args[0]) { - // Parse arguments to separate flags and packages - flags, packages := utils.ParseNpmInstallArgs(args[1:]) - - // If no packages specified, just pass through to npm - if len(packages) == 0 { - return utils.ExecCmd(execPath, args, []string{}) - } - - // Create single wrapper instance for all packages - pmw := wrapper.NewPackageManagerWrapper(registry.RegistryNPM, flags, packages, args[0]) - if err := pmw.Wrap(); err != nil { - return err - } - - return nil + err = executeNpmFlow(cmd.Context(), config, args) + if err != nil { + log.Errorf("Failed to execute npm flow: %s", err) } - if err := utils.ExecCmd(execPath, args, []string{}); err != nil { - os.Exit(1) - } - os.Exit(0) return nil }, } - return cmd } diff --git a/cmd/npm/pnpm.go b/cmd/npm/pnpm.go index 2105e72..b506c20 100644 --- a/cmd/npm/pnpm.go +++ b/cmd/npm/pnpm.go @@ -2,51 +2,30 @@ package npm import ( _ "embed" - "fmt" - "os" - "github.com/safedep/pmg/pkg/common/utils" - "github.com/safedep/pmg/pkg/registry" - "github.com/safedep/pmg/pkg/wrapper" + "github.com/safedep/dry/log" + "github.com/safedep/pmg/config" + "github.com/safedep/pmg/internal/ui" "github.com/spf13/cobra" ) func NewPnpmCommand() *cobra.Command { - cmd := &cobra.Command{ + return &cobra.Command{ Use: "pnpm [action] [package]", - Short: "Scan packages from npm registry", + Short: "Guard pnpm package manager", DisableFlagParsing: true, RunE: func(cmd *cobra.Command, args []string) error { - execPath, err := utils.GetExecutablePath(string(registry.RegistryPNPM)) + config, err := config.FromContext(cmd.Context()) if err != nil { - fmt.Fprintf(os.Stderr, "pnpm not found: %v\n", err) - return err + ui.Fatalf("Failed to get config: %s", err) } - if len(args) >= 2 && utils.IsInstallCommand(string(registry.RegistryPNPM), args[0]) { - // Parse arguments to separate flags and packages - flags, packages := utils.ParseNpmInstallArgs(args[1:]) - - // If no packages specified, just pass through to npm - if len(packages) == 0 { - return utils.ExecCmd(execPath, args, []string{}) - } - - // Create single wrapper instance for all packages - pmw := wrapper.NewPackageManagerWrapper(registry.RegistryPNPM, flags, packages, args[0]) - if err := pmw.Wrap(); err != nil { - return err - } - - return nil + err = executePnpmFlow(cmd.Context(), config, args) + if err != nil { + log.Errorf("Failed to execute pnpm flow: %s", err) } - if err := utils.ExecCmd(execPath, args, []string{}); err != nil { - os.Exit(1) - } - os.Exit(0) return nil }, } - return cmd } diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..13796c3 --- /dev/null +++ b/config/config.go @@ -0,0 +1,33 @@ +package config + +import ( + "context" + "fmt" +) + +type configKey struct{} +type contextValue struct { + Config Config +} + +// Global configuration +type Config struct { + Transitive bool + TransitiveDepth int + IncludeDevDependencies bool +} + +// Inject config into context while protecting against context poisoning +func (c Config) Inject(ctx context.Context) context.Context { + return context.WithValue(ctx, configKey{}, &contextValue{Config: c}) +} + +// Extract config from context +func FromContext(ctx context.Context) (Config, error) { + c, ok := ctx.Value(configKey{}).(*contextValue) + if !ok { + return Config{}, fmt.Errorf("config not found in context") + } + + return c.Config, nil +} diff --git a/go.mod b/go.mod index 208635c..262f534 100644 --- a/go.mod +++ b/go.mod @@ -2,125 +2,213 @@ module github.com/safedep/pmg go 1.24.1 +tool github.com/golangci/golangci-lint/cmd/golangci-lint + require ( buf.build/gen/go/safedep/api/grpc/go v1.5.1-20250418165058-162f6b0cc319.2 buf.build/gen/go/safedep/api/protocolbuffers/go v1.36.6-20250418165058-162f6b0cc319.1 github.com/fatih/color v1.18.0 github.com/jedib0t/go-pretty/v6 v6.6.7 - github.com/safedep/dry v0.0.0-20250410092643-c7079e2f9442 - github.com/safedep/vet v1.10.1 + github.com/safedep/dry v0.0.0-20250514080944-bb77f30c7175 github.com/spf13/cobra v1.9.1 + github.com/stretchr/testify v1.10.0 google.golang.org/grpc v1.71.1 ) require ( - ariga.io/atlas v0.32.0 // indirect + 4d63.com/gocheckcompilerdirectives v1.3.0 // indirect + 4d63.com/gochecknoglobals v0.2.2 // indirect buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250307204501-0409229c3780.1 // indirect - entgo.io/ent v0.14.4 // indirect + github.com/4meepo/tagalign v1.4.2 // indirect + github.com/Abirdcfly/dupword v0.1.3 // indirect + github.com/Antonboom/errname v1.0.0 // indirect + github.com/Antonboom/nilnil v1.0.1 // indirect + github.com/Antonboom/testifylint v1.5.2 // indirect github.com/BurntSushi/toml v1.5.0 // indirect - github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 // indirect - github.com/CloudyKit/jet/v6 v6.3.1 // indirect - github.com/Joker/jade v1.1.3 // indirect + github.com/Crocmagnon/fatcontext v0.7.1 // indirect + github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect + github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.1 // indirect github.com/Masterminds/semver/v3 v3.3.1 // indirect - github.com/Shopify/goreferrer v0.0.0-20240724165105-aceaa0259138 // indirect - github.com/agext/levenshtein v1.2.3 // indirect - github.com/andybalholm/brotli v1.1.1 // indirect - github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect - github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect - github.com/aymerick/douceur v0.2.0 // indirect - github.com/bmatcuk/doublestar v1.3.4 // indirect - github.com/bytedance/sonic v1.13.2 // indirect - github.com/bytedance/sonic/loader v0.2.4 // indirect - github.com/cloudwego/base64x v0.1.5 // indirect - github.com/deepmap/oapi-codegen v1.16.3 // indirect - github.com/fatih/structs v1.1.0 // indirect - github.com/flosch/pongo2/v4 v4.0.2 // indirect - github.com/gabriel-vasile/mimetype v1.4.9 // indirect - github.com/gin-contrib/sse v1.1.0 // indirect - github.com/gin-gonic/gin v1.10.0 // indirect - github.com/gkampitakis/go-snaps v0.5.11 // indirect + github.com/OpenPeeDeeP/depguard/v2 v2.2.1 // indirect + github.com/alecthomas/go-check-sumtype v0.3.1 // indirect + github.com/alexkohler/nakedret/v2 v2.0.5 // indirect + github.com/alexkohler/prealloc v1.0.0 // indirect + github.com/alingse/asasalint v0.0.11 // indirect + github.com/alingse/nilnesserr v0.1.2 // indirect + github.com/ashanbrown/forbidigo v1.6.0 // indirect + github.com/ashanbrown/makezero v1.2.0 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/bkielbasa/cyclop v1.2.3 // indirect + github.com/blizzy78/varnamelen v0.8.0 // indirect + github.com/bombsimon/wsl/v4 v4.5.0 // indirect + github.com/breml/bidichk v0.3.2 // indirect + github.com/breml/errchkjson v0.4.0 // indirect + github.com/butuzov/ireturn v0.3.1 // indirect + github.com/butuzov/mirror v1.3.0 // indirect + github.com/catenacyber/perfsprint v0.8.2 // indirect + github.com/ccojocar/zxcvbn-go v1.0.2 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/charithe/durationcheck v0.0.10 // indirect + github.com/chavacava/garif v0.1.0 // indirect + github.com/ckaznocha/intrange v0.3.0 // indirect + github.com/curioswitch/go-reassign v0.3.0 // indirect + github.com/daixiang0/gci v0.13.5 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/denis-tingaikin/go-header v0.5.0 // indirect + github.com/dlclark/regexp2 v1.11.4 // indirect + github.com/ettle/strcase v0.2.0 // indirect + github.com/fatih/structtag v1.2.0 // indirect + github.com/firefart/nonamedreturns v1.0.5 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/fzipp/gocyclo v0.6.0 // indirect + github.com/ghostiam/protogetter v0.3.9 // indirect + github.com/go-critic/go-critic v0.12.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-openapi/inflect v0.21.2 // indirect - github.com/go-playground/locales v0.14.1 // indirect - github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.26.0 // indirect - github.com/go-test/deep v1.1.1 // indirect - github.com/goccy/go-json v0.10.5 // indirect - github.com/golang/snappy v1.0.0 // indirect - github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b // indirect + github.com/go-toolsmith/astcast v1.1.0 // indirect + github.com/go-toolsmith/astcopy v1.1.0 // indirect + github.com/go-toolsmith/astequal v1.2.0 // indirect + github.com/go-toolsmith/astfmt v1.1.0 // indirect + github.com/go-toolsmith/astp v1.1.0 // indirect + github.com/go-toolsmith/strparse v1.1.0 // indirect + github.com/go-toolsmith/typep v1.1.0 // indirect + github.com/go-viper/mapstructure/v2 v2.2.1 // indirect + github.com/go-xmlfmt/xmlfmt v1.1.3 // indirect + github.com/gobwas/glob v0.2.3 // indirect + github.com/gofrs/flock v0.12.1 // indirect + github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 // indirect + github.com/golangci/go-printf-func-name v0.1.0 // indirect + github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d // indirect + github.com/golangci/golangci-lint v1.64.7 // indirect + github.com/golangci/misspell v0.6.0 // indirect + github.com/golangci/plugin-module-register v0.1.1 // indirect + github.com/golangci/revgrep v0.8.0 // indirect + github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/go-github/v70 v70.0.0 // indirect github.com/google/go-querystring v1.1.0 // indirect - github.com/google/osv-scanner v1.9.2 // indirect - github.com/google/uuid v1.6.0 // indirect - github.com/gorilla/css v1.0.1 // indirect + github.com/google/pprof v0.0.0-20250418163039-24c5476c6587 // indirect + github.com/gordonklaus/ineffassign v0.1.0 // indirect + github.com/gostaticanalysis/analysisutil v0.7.1 // indirect + github.com/gostaticanalysis/comment v1.5.0 // indirect + github.com/gostaticanalysis/forcetypeassert v0.2.0 // indirect + github.com/gostaticanalysis/nilerr v0.1.1 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect - github.com/hashicorp/hcl/v2 v2.23.0 // indirect + github.com/hashicorp/go-immutable-radix/v2 v2.1.0 // indirect + github.com/hashicorp/go-version v1.7.0 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect + github.com/hexops/gotextdiff v1.0.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/iris-contrib/schema v0.0.6 // indirect - github.com/josharian/intern v1.0.0 // indirect - github.com/json-iterator/go v1.1.12 // indirect - github.com/kataras/blocks v0.0.11 // indirect - github.com/kataras/golog v0.1.13 // indirect - github.com/kataras/iris/v12 v12.2.11 // indirect - github.com/kataras/pio v0.0.14 // indirect - github.com/kataras/sitemap v0.0.6 // indirect - github.com/kataras/tunnel v0.0.4 // indirect + github.com/jgautheron/goconst v1.7.1 // indirect + github.com/jingyugao/rowserrcheck v1.1.1 // indirect + github.com/jjti/go-spancheck v0.6.4 // indirect + github.com/julz/importas v0.2.0 // indirect + github.com/karamaru-alpha/copyloopvar v1.2.1 // indirect + github.com/kisielk/errcheck v1.9.0 // indirect + github.com/kkHAIKE/contextcheck v1.1.6 // indirect github.com/klauspost/compress v1.18.0 // indirect - github.com/klauspost/cpuid/v2 v2.2.10 // indirect - github.com/labstack/echo/v4 v4.13.3 // indirect - github.com/labstack/gommon v0.4.2 // indirect - github.com/leodido/go-urn v1.4.0 // indirect - github.com/mailgun/raymond/v2 v2.0.48 // indirect - github.com/mailru/easyjson v0.9.0 // indirect + github.com/kulti/thelper v0.6.3 // indirect + github.com/kunwardeep/paralleltest v1.0.10 // indirect + github.com/lasiar/canonicalheader v1.1.2 // indirect + github.com/ldez/exptostd v0.4.2 // indirect + github.com/ldez/gomoddirectives v0.6.1 // indirect + github.com/ldez/grignotin v0.9.0 // indirect + github.com/ldez/tagliatelle v0.7.1 // indirect + github.com/ldez/usetesting v0.4.2 // indirect + github.com/leonklingele/grouper v1.1.2 // indirect + github.com/macabu/inamedparam v0.1.3 // indirect + github.com/maratori/testableexamples v1.0.0 // indirect + github.com/maratori/testpackage v1.1.1 // indirect + github.com/matoous/godox v1.1.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect - github.com/microcosm-cc/bluemonday v1.0.27 // indirect - github.com/mitchellh/go-wordwrap v1.0.1 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/package-url/packageurl-go v0.1.3 // indirect + github.com/mgechev/revive v1.7.0 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/moricho/tparallel v0.3.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/nakabonne/nestif v0.3.1 // indirect + github.com/nishanths/exhaustive v0.12.0 // indirect + github.com/nishanths/predeclared v0.2.2 // indirect + github.com/nunnatsa/ginkgolinter v0.19.1 // indirect + github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/polyfloyd/go-errorlint v1.7.1 // indirect + github.com/prometheus/client_golang v1.20.5 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.62.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1 // indirect + github.com/quasilyte/go-ruleguard/dsl v0.3.22 // indirect + github.com/quasilyte/gogrep v0.5.0 // indirect + github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect + github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect + github.com/raeperd/recvcheck v0.2.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect - github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/sanity-io/litter v1.5.8 // indirect - github.com/schollz/closestmatch v2.1.0+incompatible // indirect + github.com/rogpeppe/go-internal v1.14.1 // indirect + github.com/ryancurrah/gomodguard v1.3.5 // indirect + github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect + github.com/sagikazarmark/locafero v0.9.0 // indirect + github.com/sanposhiho/wastedassign/v2 v2.1.0 // indirect + github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 // indirect + github.com/sashamelentyev/interfacebloat v1.1.0 // indirect + github.com/sashamelentyev/usestdlibvars v1.28.0 // indirect + github.com/securego/gosec/v2 v2.22.2 // indirect github.com/sirupsen/logrus v1.9.3 // indirect + github.com/sivchari/containedctx v1.0.3 // indirect + github.com/sivchari/tenv v1.12.1 // indirect + github.com/sonatard/noctx v0.1.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/sourcegraph/go-diff v0.7.0 // indirect + github.com/spf13/afero v1.14.0 // indirect + github.com/spf13/cast v1.7.1 // indirect github.com/spf13/pflag v1.0.6 // indirect - github.com/tdewolff/minify/v2 v2.23.1 // indirect - github.com/tdewolff/parse/v2 v2.7.23 // indirect - github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - github.com/ugorji/go/codec v1.2.12 // indirect - github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/valyala/fasttemplate v1.2.2 // indirect - github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect - github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect - github.com/yosssi/ace v0.0.5 // indirect - github.com/zclconf/go-cty v1.16.2 // indirect - github.com/zclconf/go-cty-yaml v1.1.0 // indirect + github.com/spf13/viper v1.20.1 // indirect + github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect + github.com/stbenjam/no-sprintf-host-port v0.2.0 // indirect + github.com/stretchr/objx v0.5.2 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + github.com/tdakkota/asciicheck v0.4.1 // indirect + github.com/tetafro/godot v1.5.0 // indirect + github.com/timakin/bodyclose v0.0.0-20241017074812-ed6a65f985e3 // indirect + github.com/timonwong/loggercheck v0.10.1 // indirect + github.com/tomarrell/wrapcheck/v2 v2.10.0 // indirect + github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect + github.com/ultraware/funlen v0.2.0 // indirect + github.com/ultraware/whitespace v0.2.0 // indirect + github.com/uudashr/gocognit v1.2.0 // indirect + github.com/uudashr/iface v1.3.1 // indirect + github.com/xen0n/gosmopolitan v1.2.2 // indirect + github.com/yagipy/maintidx v1.0.0 // indirect + github.com/yeya24/promlinter v0.3.0 // indirect + github.com/ykadowak/zerologlint v0.1.5 // indirect + gitlab.com/bosi/decorder v0.4.2 // indirect + go-simpler.org/musttag v0.13.0 // indirect + go-simpler.org/sloglint v0.9.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect go.opentelemetry.io/otel v1.35.0 // indirect go.opentelemetry.io/otel/metric v1.35.0 // indirect go.opentelemetry.io/otel/trace v1.35.0 // indirect + go.uber.org/automaxprocs v1.6.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect - golang.org/x/arch v0.16.0 // indirect - golang.org/x/crypto v0.37.0 // indirect golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect + golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac // indirect golang.org/x/mod v0.24.0 // indirect golang.org/x/net v0.39.0 // indirect golang.org/x/sync v0.13.0 // indirect golang.org/x/sys v0.32.0 // indirect golang.org/x/term v0.31.0 // indirect golang.org/x/text v0.24.0 // indirect - golang.org/x/time v0.11.0 // indirect golang.org/x/tools v0.32.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250414145226-207652e42e2e // indirect google.golang.org/protobuf v1.36.6 // indirect - gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + honnef.co/go/tools v0.6.1 // indirect + mvdan.cc/gofumpt v0.7.0 // indirect + mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f // indirect ) diff --git a/go.sum b/go.sum index fdb5a52..0f30795 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ -ariga.io/atlas v0.32.0 h1:y+77nueMrExLiKlz1CcPKh/nU7VSlWfBbwCShsJyvCw= -ariga.io/atlas v0.32.0/go.mod h1:Oe1xWPuu5q9LzyrWfbZmEZxFYeu4BHTyzfjeW2aZp/w= +4d63.com/gocheckcompilerdirectives v1.3.0 h1:Ew5y5CtcAAQeTVKUVFrE7EwHMrTO6BggtEj8BZSjZ3A= +4d63.com/gocheckcompilerdirectives v1.3.0/go.mod h1:ofsJ4zx2QAuIP/NO/NAh1ig6R1Fb18/GI7RVMwz7kAY= +4d63.com/gochecknoglobals v0.2.2 h1:H1vdnwnMaZdQW/N+NrkT1SZMTBmcwHe9Vq8lJcYYTtU= +4d63.com/gochecknoglobals v0.2.2/go.mod h1:lLxwTQjL5eIesRbvnzIP3jZtG140FnTdz+AlMa+ogt0= buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250307204501-0409229c3780.1 h1:zgJPqo17m28+Lf5BW4xv3PvU20BnrmTcGYrog22lLIU= buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250307204501-0409229c3780.1/go.mod h1:avRlCjnFzl98VPaeCtJ24RrV/wwHFzB8sWXhj26+n/U= buf.build/gen/go/safedep/api/grpc/go v1.5.1-20250418165058-162f6b0cc319.2 h1:txDywYkqsXvtA/sDSMcwMjC8XTHnqlyc+3aIFMzRVeQ= @@ -7,85 +9,114 @@ buf.build/gen/go/safedep/api/grpc/go v1.5.1-20250418165058-162f6b0cc319.2/go.mod buf.build/gen/go/safedep/api/protocolbuffers/go v1.36.6-20250418165058-162f6b0cc319.1 h1:vJeI1IQuxGZd9r5RHNiLTJ+aPQfy0g7h3Gqa9/Ql7kA= buf.build/gen/go/safedep/api/protocolbuffers/go v1.36.6-20250418165058-162f6b0cc319.1/go.mod h1:uR95GqsnNCRn6cTyRBte6uMJMm0rEBRxTGpakKCNL9I= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -entgo.io/ent v0.14.4 h1:/DhDraSLXIkBhyiVoJeSshr4ZYi7femzhj6/TckzZuI= -entgo.io/ent v0.14.4/go.mod h1:aDPE/OziPEu8+OWbzy4UlvWmD2/kbRuWfK2A40hcxJM= +github.com/4meepo/tagalign v1.4.2 h1:0hcLHPGMjDyM1gHG58cS73aQF8J4TdVR96TZViorO9E= +github.com/4meepo/tagalign v1.4.2/go.mod h1:+p4aMyFM+ra7nb41CnFG6aSDXqRxU/w1VQqScKqDARI= +github.com/Abirdcfly/dupword v0.1.3 h1:9Pa1NuAsZvpFPi9Pqkd93I7LIYRURj+A//dFd5tgBeE= +github.com/Abirdcfly/dupword v0.1.3/go.mod h1:8VbB2t7e10KRNdwTVoxdBaxla6avbhGzb8sCTygUMhw= +github.com/Antonboom/errname v1.0.0 h1:oJOOWR07vS1kRusl6YRSlat7HFnb3mSfMl6sDMRoTBA= +github.com/Antonboom/errname v1.0.0/go.mod h1:gMOBFzK/vrTiXN9Oh+HFs+e6Ndl0eTFbtsRTSRdXyGI= +github.com/Antonboom/nilnil v1.0.1 h1:C3Tkm0KUxgfO4Duk3PM+ztPncTFlOf0b2qadmS0s4xs= +github.com/Antonboom/nilnil v1.0.1/go.mod h1:CH7pW2JsRNFgEh8B2UaPZTEPhCMuFowP/e8Udp9Nnb0= +github.com/Antonboom/testifylint v1.5.2 h1:4s3Xhuv5AvdIgbd8wOOEeo0uZG7PbDKQyKY5lGoQazk= +github.com/Antonboom/testifylint v1.5.2/go.mod h1:vxy8VJ0bc6NavlYqjZfmp6EfqXMtBgQ4+mhCojwC1P8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= -github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 h1:sR+/8Yb4slttB4vD+b9btVEnWgL3Q00OBTzVT8B9C0c= -github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= -github.com/CloudyKit/jet/v6 v6.3.1 h1:6IAo5Cx21xrHVaR8zzXN5gJatKV/wO7Nf6bfCnCSbUw= -github.com/CloudyKit/jet/v6 v6.3.1/go.mod h1:lf8ksdNsxZt7/yH/3n4vJQWA9RUq4wpaHtArHhGVMOw= -github.com/CycloneDX/cyclonedx-go v0.9.2 h1:688QHn2X/5nRezKe2ueIVCt+NRqf7fl3AVQk+vaFcIo= -github.com/CycloneDX/cyclonedx-go v0.9.2/go.mod h1:vcK6pKgO1WanCdd61qx4bFnSsDJQ6SbM2ZuMIgq86Jg= -github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= -github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= -github.com/Joker/hpp v1.0.0 h1:65+iuJYdRXv/XyN62C1uEmmOx3432rNG/rKlX6V7Kkc= -github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= -github.com/Joker/jade v1.1.3 h1:Qbeh12Vq6BxURXT1qZBRHsDxeURB8ztcL6f3EXSGeHk= -github.com/Joker/jade v1.1.3/go.mod h1:T+2WLyt7VH6Lp0TRxQrUYEs64nRc83wkMQrfeIQKduM= +github.com/Crocmagnon/fatcontext v0.7.1 h1:SC/VIbRRZQeQWj/TcQBS6JmrXcfA+BU4OGSVUt54PjM= +github.com/Crocmagnon/fatcontext v0.7.1/go.mod h1:1wMvv3NXEBJucFGfwOJBxSVWcoIO6emV215SMkW9MFU= +github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 h1:sHglBQTwgx+rWPdisA5ynNEsoARbiCBOyGcJM4/OzsM= +github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= +github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.1 h1:Sz1JIXEcSfhz7fUi7xHnhpIE0thVASYjvosApmHuD2k= +github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.1/go.mod h1:n/LSCXNuIYqVfBlVXyHfMQkZDdp1/mmxfSjADd3z1Zg= github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4= github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= -github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= -github.com/Shopify/goreferrer v0.0.0-20240724165105-aceaa0259138 h1:gjbp60h8IZQbN/TpDaYJedWbbD1h1aDPEwWnYWaDaUY= -github.com/Shopify/goreferrer v0.0.0-20240724165105-aceaa0259138/go.mod h1:NYezi6wtnJtBm5btoprXc5SvAdqH0XTXWnUup0MptAI= -github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= -github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= -github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= -github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= -github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= -github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= -github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= -github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= -github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= -github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= -github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= -github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= +github.com/OpenPeeDeeP/depguard/v2 v2.2.1 h1:vckeWVESWp6Qog7UZSARNqfu/cZqvki8zsuj3piCMx4= +github.com/OpenPeeDeeP/depguard/v2 v2.2.1/go.mod h1:q4DKzC4UcVaAvcfd41CZh0PWpGgzrVxUYBlgKNGquUo= +github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= +github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= +github.com/alecthomas/go-check-sumtype v0.3.1 h1:u9aUvbGINJxLVXiFvHUlPEaD7VDULsrxJb4Aq31NLkU= +github.com/alecthomas/go-check-sumtype v0.3.1/go.mod h1:A8TSiN3UPRw3laIgWEUOHHLPa6/r9MtoigdlP5h3K/E= +github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= +github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= +github.com/alexkohler/nakedret/v2 v2.0.5 h1:fP5qLgtwbx9EJE8dGEERT02YwS8En4r9nnZ71RK+EVU= +github.com/alexkohler/nakedret/v2 v2.0.5/go.mod h1:bF5i0zF2Wo2o4X4USt9ntUWve6JbFv02Ff4vlkmS/VU= +github.com/alexkohler/prealloc v1.0.0 h1:Hbq0/3fJPQhNkN0dR95AVrr6R7tou91y0uHG5pOcUuw= +github.com/alexkohler/prealloc v1.0.0/go.mod h1:VetnK3dIgFBBKmg0YnD9F9x6Icjd+9cvfHR56wJVlKE= +github.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQRnw= +github.com/alingse/asasalint v0.0.11/go.mod h1:nCaoMhw7a9kSJObvQyVzNTPBDbNpdocqrSP7t/cW5+I= +github.com/alingse/nilnesserr v0.1.2 h1:Yf8Iwm3z2hUUrP4muWfW83DF4nE3r1xZ26fGWUKCZlo= +github.com/alingse/nilnesserr v0.1.2/go.mod h1:1xJPrXonEtX7wyTq8Dytns5P2hNzoWymVUIaKm4HNFg= +github.com/ashanbrown/forbidigo v1.6.0 h1:D3aewfM37Yb3pxHujIPSpTf6oQk9sc9WZi8gerOIVIY= +github.com/ashanbrown/forbidigo v1.6.0/go.mod h1:Y8j9jy9ZYAEHXdu723cUlraTqbzjKF1MUyfOKL+AjcU= +github.com/ashanbrown/makezero v1.2.0 h1:/2Lp1bypdmK9wDIq7uWBlDF1iMUpIIS4A+pF6C9IEUU= +github.com/ashanbrown/makezero v1.2.0/go.mod h1:dxlPhHbDMC6N6xICzFBSK+4njQDdK8euNO0qjQMtGY4= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= -github.com/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQrS0= -github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE= -github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ= -github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4= -github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= -github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY= -github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bkielbasa/cyclop v1.2.3 h1:faIVMIGDIANuGPWH031CZJTi2ymOQBULs9H21HSMa5w= +github.com/bkielbasa/cyclop v1.2.3/go.mod h1:kHTwA9Q0uZqOADdupvcFJQtp/ksSnytRMe8ztxG8Fuo= +github.com/blizzy78/varnamelen v0.8.0 h1:oqSblyuQvFsW1hbBHh1zfwrKe3kcSj0rnXkKzsQ089M= +github.com/blizzy78/varnamelen v0.8.0/go.mod h1:V9TzQZ4fLJ1DSrjVDfl89H7aMnTvKkApdHeyESmyR7k= +github.com/bombsimon/wsl/v4 v4.5.0 h1:iZRsEvDdyhd2La0FVi5k6tYehpOR/R7qIUjmKk7N74A= +github.com/bombsimon/wsl/v4 v4.5.0/go.mod h1:NOQ3aLF4nD7N5YPXMruR6ZXDOAqLoM0GEpLwTdvmOSc= +github.com/breml/bidichk v0.3.2 h1:xV4flJ9V5xWTqxL+/PMFF6dtJPvZLPsyixAoPe8BGJs= +github.com/breml/bidichk v0.3.2/go.mod h1:VzFLBxuYtT23z5+iVkamXO386OB+/sVwZOpIj6zXGos= +github.com/breml/errchkjson v0.4.0 h1:gftf6uWZMtIa/Is3XJgibewBm2ksAQSY/kABDNFTAdk= +github.com/breml/errchkjson v0.4.0/go.mod h1:AuBOSTHyLSaaAFlWsRSuRBIroCh3eh7ZHh5YeelDIk8= +github.com/butuzov/ireturn v0.3.1 h1:mFgbEI6m+9W8oP/oDdfA34dLisRFCj2G6o/yiI1yZrY= +github.com/butuzov/ireturn v0.3.1/go.mod h1:ZfRp+E7eJLC0NQmk1Nrm1LOrn/gQlOykv+cVPdiXH5M= +github.com/butuzov/mirror v1.3.0 h1:HdWCXzmwlQHdVhwvsfBb2Au0r3HyINry3bDWLYXiKoc= +github.com/butuzov/mirror v1.3.0/go.mod h1:AEij0Z8YMALaq4yQj9CPPVYOyJQyiexpQEQgihajRfI= +github.com/catenacyber/perfsprint v0.8.2 h1:+o9zVmCSVa7M4MvabsWvESEhpsMkhfE7k0sHNGL95yw= +github.com/catenacyber/perfsprint v0.8.2/go.mod h1:q//VWC2fWbcdSLEY1R3l8n0zQCDPdE4IjZwyY1HMunM= +github.com/ccojocar/zxcvbn-go v1.0.2 h1:na/czXU8RrhXO4EZme6eQJLR4PzcGsahsBOAwU6I3Vg= +github.com/ccojocar/zxcvbn-go v1.0.2/go.mod h1:g1qkXtUSvHP8lhHp5GrSmTz6uWALGRMQdw6Qnz/hi60= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/charithe/durationcheck v0.0.10 h1:wgw73BiocdBDQPik+zcEoBG/ob8uyBHf2iyoHGPf5w4= +github.com/charithe/durationcheck v0.0.10/go.mod h1:bCWXb7gYRysD1CU3C+u4ceO49LoGOY1C1L6uouGNreQ= +github.com/chavacava/garif v0.1.0 h1:2JHa3hbYf5D9dsgseMKAmc/MZ109otzgNFk5s87H9Pc= +github.com/chavacava/garif v0.1.0/go.mod h1:XMyYCkEL58DF0oyW4qDjjnPWONs2HBqYKI+UIPD+Gww= +github.com/ckaznocha/intrange v0.3.0 h1:VqnxtK32pxgkhJgYQEeOArVidIPg+ahLP7WBOXZd5ZY= +github.com/ckaznocha/intrange v0.3.0/go.mod h1:+I/o2d2A1FBHgGELbGxzIcyd3/9l9DuwjM8FsbSS3Lo= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= -github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= -github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/curioswitch/go-reassign v0.3.0 h1:dh3kpQHuADL3cobV/sSGETA8DOv457dwl+fbBAhrQPs= +github.com/curioswitch/go-reassign v0.3.0/go.mod h1:nApPCCTtqLJN/s8HfItCcKV0jIPwluBOvZP+dsJGA88= +github.com/daixiang0/gci v0.13.5 h1:kThgmH1yBmZSBCh1EJVxQ7JsHpm5Oms0AMed/0LaH4c= +github.com/daixiang0/gci v0.13.5/go.mod h1:12etP2OniiIdP4q+kjUGrC/rUagga7ODbqsom5Eo5Yk= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/deepmap/oapi-codegen v1.16.3 h1:GT9G86SbQtT1r8ZB+4Cybi9VGdu1P5ieNvNdEoCSbrA= -github.com/deepmap/oapi-codegen v1.16.3/go.mod h1:JD6ErqeX0nYnhdciLc61Konj3NBASREMlkHOgHn8WAM= +github.com/denis-tingaikin/go-header v0.5.0 h1:SRdnP5ZKvcO9KKRP1KJrhFR3RrlGuD+42t4429eC9k8= +github.com/denis-tingaikin/go-header v0.5.0/go.mod h1:mMenU5bWrok6Wl2UsZjy+1okegmwQ3UgWl4V1D8gjlY= +github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo= +github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/ettle/strcase v0.2.0 h1:fGNiVF21fHXpX1niBgk0aROov1LagYsOwV/xqKDKR/Q= +github.com/ettle/strcase v0.2.0/go.mod h1:DajmHElDSaX76ITe3/VHVyMin4LWSJN5Z909Wp+ED1A= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= -github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= -github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= -github.com/flosch/pongo2/v4 v4.0.2 h1:gv+5Pe3vaSVmiJvh/BZa82b7/00YUGm0PIyVVLop0Hw= -github.com/flosch/pongo2/v4 v4.0.2/go.mod h1:B5ObFANs/36VwxxlgKpdchIJHMvHB562PW+BWPhwZD8= -github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY= -github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok= -github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= -github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= -github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= -github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= -github.com/gkampitakis/ciinfo v0.3.1 h1:lzjbemlGI4Q+XimPg64ss89x8Mf3xihJqy/0Mgagapo= -github.com/gkampitakis/ciinfo v0.3.1/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo= -github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M= -github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk= -github.com/gkampitakis/go-snaps v0.5.11 h1:LFG0ggUKR+KEiiaOvFCmLgJ5NO2zf93AxxddkBn3LdQ= -github.com/gkampitakis/go-snaps v0.5.11/go.mod h1:PcKmy8q5Se7p48ywpogN5Td13reipz1Iivah4wrTIvY= +github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= +github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= +github.com/firefart/nonamedreturns v1.0.5 h1:tM+Me2ZaXs8tfdDw3X6DOX++wMCOqzYUho6tUTYIdRA= +github.com/firefart/nonamedreturns v1.0.5/go.mod h1:gHJjDqhGM4WyPt639SOZs+G89Ko7QKH5R5BhnO6xJhw= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= +github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= +github.com/ghostiam/protogetter v0.3.9 h1:j+zlLLWzqLay22Cz/aYwTHKQ88GE2DQ6GkWSYFOI4lQ= +github.com/ghostiam/protogetter v0.3.9/go.mod h1:WZ0nw9pfzsgxuRsPOFQomgDVSWtDLJRfQJEhsGbmQMA= +github.com/go-critic/go-critic v0.12.0 h1:iLosHZuye812wnkEz1Xu3aBwn5ocCPfc9yqmFG9pa6w= +github.com/go-critic/go-critic v0.12.0/go.mod h1:DpE0P6OVc6JzVYzmM5gq5jMU31zLr4am5mB/VfFK64w= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -93,25 +124,38 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-openapi/inflect v0.21.2 h1:0gClGlGcxifcJR56zwvhaOulnNgnhc4qTAkob5ObnSM= -github.com/go-openapi/inflect v0.21.2/go.mod h1:INezMuUu7SJQc2AyR3WO0DqqYUJSj8Kb4hBd7WtjlAw= -github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= -github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= -github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= -github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= -github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k= -github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= +github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= +github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= -github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/go-toolsmith/astcast v1.1.0 h1:+JN9xZV1A+Re+95pgnMgDboWNVnIMMQXwfBwLRPgSC8= +github.com/go-toolsmith/astcast v1.1.0/go.mod h1:qdcuFWeGGS2xX5bLM/c3U9lewg7+Zu4mr+xPwZIB4ZU= +github.com/go-toolsmith/astcopy v1.1.0 h1:YGwBN0WM+ekI/6SS6+52zLDEf8Yvp3n2seZITCUBt5s= +github.com/go-toolsmith/astcopy v1.1.0/go.mod h1:hXM6gan18VA1T/daUEHCFcYiW8Ai1tIwIzHY6srfEAw= +github.com/go-toolsmith/astequal v1.0.3/go.mod h1:9Ai4UglvtR+4up+bAD4+hCj7iTo4m/OXVTSLnCyTAx4= +github.com/go-toolsmith/astequal v1.1.0/go.mod h1:sedf7VIdCL22LD8qIvv7Nn9MuWJruQA/ysswh64lffQ= +github.com/go-toolsmith/astequal v1.2.0 h1:3Fs3CYZ1k9Vo4FzFhwwewC3CHISHDnVUPC4x0bI2+Cw= +github.com/go-toolsmith/astequal v1.2.0/go.mod h1:c8NZ3+kSFtFY/8lPso4v8LuJjdJiUFVnSuU3s0qrrDY= +github.com/go-toolsmith/astfmt v1.1.0 h1:iJVPDPp6/7AaeLJEruMsBUlOYCmvg0MoCfJprsOmcco= +github.com/go-toolsmith/astfmt v1.1.0/go.mod h1:OrcLlRwu0CuiIBp/8b5PYF9ktGVZUjlNMV634mhwuQ4= +github.com/go-toolsmith/astp v1.1.0 h1:dXPuCl6u2llURjdPLLDxJeZInAeZ0/eZwFJmqZMnpQA= +github.com/go-toolsmith/astp v1.1.0/go.mod h1:0T1xFGz9hicKs8Z5MfAqSUitoUYS30pDMsRVIDHs8CA= +github.com/go-toolsmith/pkgload v1.2.2 h1:0CtmHq/02QhxcF7E9N5LIFcYFsMR5rdovfqTtRKkgIk= +github.com/go-toolsmith/pkgload v1.2.2/go.mod h1:R2hxLNRKuAsiXCo2i5J6ZQPhnPMOVtU+f0arbFPWCus= +github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8= +github.com/go-toolsmith/strparse v1.1.0 h1:GAioeZUK9TGxnLS+qfdqNbA4z0SSm5zVNtCQiyP2Bvw= +github.com/go-toolsmith/strparse v1.1.0/go.mod h1:7ksGy58fsaQkGQlY8WVoBFNyEPMGuJin1rfoPS4lBSQ= +github.com/go-toolsmith/typep v1.1.0 h1:fIRYDyF+JywLfqzyhdiHzRop/GQDxxNhLGQ6gFUNHus= +github.com/go-toolsmith/typep v1.1.0/go.mod h1:fVIw+7zjdsMxDA3ITWnH1yOiw1rnTQKCsF/sk2H/qig= +github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= +github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/go-xmlfmt/xmlfmt v1.1.3 h1:t8Ey3Uy7jDSEisW2K3somuMKIpzktkWptA0iFCnRUWY= +github.com/go-xmlfmt/xmlfmt v1.1.3/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= -github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= -github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= -github.com/goccy/go-yaml v1.15.13 h1:Xd87Yddmr2rC1SLLTm2MNDcTjeO/GYo0JGiww6gSTDg= -github.com/goccy/go-yaml v1.15.13/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= +github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -120,66 +164,88 @@ github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= -github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b h1:EY/KpStFl60qA17CptGXhwfZ+k1sFNJIUNR8DdbcuUk= -github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA= +github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 h1:WUvBfQL6EW/40l6OmeSBYQJNSif4O11+bmWEz+C7FYw= +github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32/go.mod h1:NUw9Zr2Sy7+HxzdjIULge71wI6yEg1lWQr7Evcu8K0E= +github.com/golangci/go-printf-func-name v0.1.0 h1:dVokQP+NMTO7jwO4bwsRwLWeudOVUPPyAKJuzv8pEJU= +github.com/golangci/go-printf-func-name v0.1.0/go.mod h1:wqhWFH5mUdJQhweRnldEywnR5021wTdZSNgwYceV14s= +github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d h1:viFft9sS/dxoYY0aiOTsLKO2aZQAPT4nlQCsimGcSGE= +github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d/go.mod h1:ivJ9QDg0XucIkmwhzCDsqcnxxlDStoTl89jDMIoNxKY= +github.com/golangci/golangci-lint v1.64.7 h1:Xk1EyxoXqZabn5b4vnjNKSjCx1whBK53NP+mzLfX7HA= +github.com/golangci/golangci-lint v1.64.7/go.mod h1:5cEsUQBSr6zi8XI8OjmcY2Xmliqc4iYL7YoPrL+zLJ4= +github.com/golangci/misspell v0.6.0 h1:JCle2HUTNWirNlDIAUO44hUsKhOFqGPoC4LZxlaSXDs= +github.com/golangci/misspell v0.6.0/go.mod h1:keMNyY6R9isGaSAu+4Q8NMBwMPkh15Gtc8UCVoDtAWo= +github.com/golangci/plugin-module-register v0.1.1 h1:TCmesur25LnyJkpsVrupv1Cdzo+2f7zX0H6Jkw1Ol6c= +github.com/golangci/plugin-module-register v0.1.1/go.mod h1:TTpqoB6KkwOJMV8u7+NyXMrkwwESJLOkfl9TxR1DGFc= +github.com/golangci/revgrep v0.8.0 h1:EZBctwbVd0aMeRnNUsFogoyayvKHyxlV3CdUA46FX2s= +github.com/golangci/revgrep v0.8.0/go.mod h1:U4R/s9dlXZsg8uJmaR1GrloUr14D7qDl8gi2iPXJH8k= +github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed h1:IURFTjxeTfNFP0hTEi1YKjB/ub8zkpaOqFFMApi2EAs= +github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed/go.mod h1:XLXN8bNw4CGRPaqgl3bv/lhz7bsGPh4/xSaMTbo2vkQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-github/v70 v70.0.0 h1:/tqCp5KPrcvqCc7vIvYyFYTiCGrYvaWoYMGHSQbo55o= github.com/google/go-github/v70 v70.0.0/go.mod h1:xBUZgo8MI3lUL/hwxl3hlceJW1U8MVnXP3zUyI+rhQY= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/osv-scanner v1.9.2 h1:N5Arl9SA75afbjmX8mKURgOIaKyuK3NUjCaxDlj1KHI= -github.com/google/osv-scanner v1.9.2/go.mod h1:ZTL8Dp9z/7Jr9kkQSOGqo8z6Csqt83qMIr58aZVx+pM= +github.com/google/pprof v0.0.0-20250418163039-24c5476c6587 h1:b/8HpQhvKLSNzH5oTXN2WkNcMl6YB5K3FRbb+i+Ml34= +github.com/google/pprof v0.0.0-20250418163039-24c5476c6587/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= -github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= -github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= -github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= +github.com/gordonklaus/ineffassign v0.1.0 h1:y2Gd/9I7MdY1oEIt+n+rowjBNDcLQq3RsH5hwJd0f9s= +github.com/gordonklaus/ineffassign v0.1.0/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0= +github.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk= +github.com/gostaticanalysis/analysisutil v0.7.1/go.mod h1:v21E3hY37WKMGSnbsw2S/ojApNWb6C1//mXO48CXbVc= +github.com/gostaticanalysis/comment v1.4.1/go.mod h1:ih6ZxzTHLdadaiSnF5WY3dxUoXfXAlTaRzuaNDlSado= +github.com/gostaticanalysis/comment v1.4.2/go.mod h1:KLUTGDv6HOCotCH8h2erHKmpci2ZoR8VPu34YA2uzdM= +github.com/gostaticanalysis/comment v1.5.0 h1:X82FLl+TswsUMpMh17srGRuKaaXprTaytmEpgnKIDu8= +github.com/gostaticanalysis/comment v1.5.0/go.mod h1:V6eb3gpCv9GNVqb6amXzEUX3jXLVK/AdA+IrAMSqvEc= +github.com/gostaticanalysis/forcetypeassert v0.2.0 h1:uSnWrrUEYDr86OCxWa4/Tp2jeYDlogZiZHzGkWFefTk= +github.com/gostaticanalysis/forcetypeassert v0.2.0/go.mod h1:M5iPavzE9pPqWyeiVXSFghQjljW1+l/Uke3PXHS6ILY= +github.com/gostaticanalysis/nilerr v0.1.1 h1:ThE+hJP0fEp4zWLkWHWcRyI2Od0p7DlgYG3Uqrmrcpk= +github.com/gostaticanalysis/nilerr v0.1.1/go.mod h1:wZYb6YI5YAxxq0i1+VJbY0s2YONW0HU0GPE3+5PWN4A= +github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M= +github.com/gostaticanalysis/testutil v0.5.0 h1:Dq4wT1DdTwTGCQQv3rl3IvD5Ld0E6HiY+3Zh0sUGqw8= +github.com/gostaticanalysis/testutil v0.5.0/go.mod h1:OLQSbuM6zw2EvCcXTz1lVq5unyoNft372msDY0nY5Hs= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= -github.com/hashicorp/hcl/v2 v2.23.0 h1:Fphj1/gCylPxHutVSEOf2fBOh1VE4AuLV7+kbJf3qos= -github.com/hashicorp/hcl/v2 v2.23.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA= -github.com/imkira/go-interpol v1.1.0 h1:KIiKr0VSG2CUW1hl1jpiyuzuJeKUUpC8iM1AIE7N1Vk= -github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= +github.com/hashicorp/go-immutable-radix/v2 v2.1.0 h1:CUW5RYIcysz+D3B+l1mDeXrQ7fUvGGCwJfdASSzbrfo= +github.com/hashicorp/go-immutable-radix/v2 v2.1.0/go.mod h1:hgdqLXA4f6NIjRVisM1TJ9aOJVNRqKZj+xDGF6m7PBw= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= +github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/iris-contrib/httpexpect/v2 v2.15.2 h1:T9THsdP1woyAqKHwjkEsbCnMefsAFvk8iJJKokcJ3Go= -github.com/iris-contrib/httpexpect/v2 v2.15.2/go.mod h1:JLDgIqnFy5loDSUv1OA2j0mb6p/rDhiCqigP22Uq9xE= -github.com/iris-contrib/schema v0.0.6 h1:CPSBLyx2e91H2yJzPuhGuifVRnZBBJ3pCOMbOvPZaTw= -github.com/iris-contrib/schema v0.0.6/go.mod h1:iYszG0IOsuIsfzjymw1kMzTL8YQcCWlm65f3wX8J5iA= github.com/jedib0t/go-pretty/v6 v6.6.7 h1:m+LbHpm0aIAPLzLbMfn8dc3Ht8MW7lsSO4MPItz/Uuo= github.com/jedib0t/go-pretty/v6 v6.6.7/go.mod h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU= -github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= -github.com/kataras/blocks v0.0.11 h1:JJdYW0AUaJKLx5kEWs/oRVCvKVXo+6CAAeaVAiJf7wE= -github.com/kataras/blocks v0.0.11/go.mod h1:b4UySrJySEOq6drKH9U3bOpMI+dRH148mayYfS3RFb8= -github.com/kataras/golog v0.1.13 h1:bGbPglTdCutekqwOUf8L1jq3tZ5ADG9gfPBd5p5SzKA= -github.com/kataras/golog v0.1.13/go.mod h1:oQmzBTCv/35TetBosjJl/k+LPdlJEblaTupkNwJlwj8= -github.com/kataras/iris/v12 v12.2.11 h1:sGgo43rMPfzDft8rjVhPs6L3qDJy3TbBrMD/zGL1pzk= -github.com/kataras/iris/v12 v12.2.11/go.mod h1:uMAeX8OqG9vqdhyrIPv8Lajo/wXTtAF43wchP9WHt2w= -github.com/kataras/pio v0.0.14 h1:VGBHOmhwrMMrZeuRqoSfOrFwG+v1JxQge8N50DhmRYQ= -github.com/kataras/pio v0.0.14/go.mod h1:ZIlcw5+5Zyb/kOlU7X4uosZ8dbnXmA4GcGKt1XyyTY0= -github.com/kataras/sitemap v0.0.6 h1:w71CRMMKYMJh6LR2wTgnk5hSgjVNB9KL60n5e2KHvLY= -github.com/kataras/sitemap v0.0.6/go.mod h1:dW4dOCNs896OR1HmG+dMLdT7JjDk7mYBzoIRwuj5jA4= -github.com/kataras/tunnel v0.0.4 h1:sCAqWuJV7nPzGrlb0os3j49lk2JhILT0rID38NHNLpA= -github.com/kataras/tunnel v0.0.4/go.mod h1:9FkU4LaeifdMWqZu7o20ojmW4B7hdhv2CMLwfnHGpYw= +github.com/jgautheron/goconst v1.7.1 h1:VpdAG7Ca7yvvJk5n8dMwQhfEZJh95kl/Hl9S1OI5Jkk= +github.com/jgautheron/goconst v1.7.1/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4= +github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjzq7gFzUs= +github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c= +github.com/jjti/go-spancheck v0.6.4 h1:Tl7gQpYf4/TMU7AT84MN83/6PutY21Nb9fuQjFTpRRc= +github.com/jjti/go-spancheck v0.6.4/go.mod h1:yAEYdKJ2lRkDA8g7X+oKUHXOWVAXSBJRv04OhF+QUjk= +github.com/julz/importas v0.2.0 h1:y+MJN/UdL63QbFJHws9BVC5RpA2iq0kpjrFajTGivjQ= +github.com/julz/importas v0.2.0/go.mod h1:pThlt589EnCYtMnmhmRYY/qn9lCf/frPOK+WMx3xiJY= +github.com/karamaru-alpha/copyloopvar v1.2.1 h1:wmZaZYIjnJ0b5UoKDjUHrikcV0zuPyyxI4SVplLd2CI= +github.com/karamaru-alpha/copyloopvar v1.2.1/go.mod h1:nFmMlFNlClC2BPvNaHMdkirmTJxVCY0lhxBtlfOypMM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/errcheck v1.9.0 h1:9xt1zI9EBfcYBvdU1nVrzMzzUPUtPKs9bVSIM3TAb3M= +github.com/kisielk/errcheck v1.9.0/go.mod h1:kQxWMMVZgIkDq7U8xtG/n2juOjbLgZtedi0D+/VL/i8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kkHAIKE/contextcheck v1.1.6 h1:7HIyRcnyzxL9Lz06NGhiKvenXq7Zw6Q0UQu/ttjfJCE= +github.com/kkHAIKE/contextcheck v1.1.6/go.mod h1:3dDbMRNBFaq8HFXWC1JyvDSPm43CmE6IuHam8Wr0rkg= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= -github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= -github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= -github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -188,142 +254,221 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY= -github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g= -github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= -github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= -github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= -github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= -github.com/mailgun/raymond/v2 v2.0.48 h1:5dmlB680ZkFG2RN/0lvTAghrSxIESeu9/2aeDqACtjw= -github.com/mailgun/raymond/v2 v2.0.48/go.mod h1:lsgvL50kgt1ylcFJYZiULi5fjPBkkhNfj4KA0W54Z18= -github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= -github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= -github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo= -github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg= +github.com/kulti/thelper v0.6.3 h1:ElhKf+AlItIu+xGnI990no4cE2+XaSu1ULymV2Yulxs= +github.com/kulti/thelper v0.6.3/go.mod h1:DsqKShOvP40epevkFrvIwkCMNYxMeTNjdWL4dqWHZ6I= +github.com/kunwardeep/paralleltest v1.0.10 h1:wrodoaKYzS2mdNVnc4/w31YaXFtsc21PCTdvWJ/lDDs= +github.com/kunwardeep/paralleltest v1.0.10/go.mod h1:2C7s65hONVqY7Q5Efj5aLzRCNLjw2h4eMc9EcypGjcY= +github.com/lasiar/canonicalheader v1.1.2 h1:vZ5uqwvDbyJCnMhmFYimgMZnJMjwljN5VGY0VKbMXb4= +github.com/lasiar/canonicalheader v1.1.2/go.mod h1:qJCeLFS0G/QlLQ506T+Fk/fWMa2VmBUiEI2cuMK4djI= +github.com/ldez/exptostd v0.4.2 h1:l5pOzHBz8mFOlbcifTxzfyYbgEmoUqjxLFHZkjlbHXs= +github.com/ldez/exptostd v0.4.2/go.mod h1:iZBRYaUmcW5jwCR3KROEZ1KivQQp6PHXbDPk9hqJKCQ= +github.com/ldez/gomoddirectives v0.6.1 h1:Z+PxGAY+217f/bSGjNZr/b2KTXcyYLgiWI6geMBN2Qc= +github.com/ldez/gomoddirectives v0.6.1/go.mod h1:cVBiu3AHR9V31em9u2kwfMKD43ayN5/XDgr+cdaFaKs= +github.com/ldez/grignotin v0.9.0 h1:MgOEmjZIVNn6p5wPaGp/0OKWyvq42KnzAt/DAb8O4Ow= +github.com/ldez/grignotin v0.9.0/go.mod h1:uaVTr0SoZ1KBii33c47O1M8Jp3OP3YDwhZCmzT9GHEk= +github.com/ldez/tagliatelle v0.7.1 h1:bTgKjjc2sQcsgPiT902+aadvMjCeMHrY7ly2XKFORIk= +github.com/ldez/tagliatelle v0.7.1/go.mod h1:3zjxUpsNB2aEZScWiZTHrAXOl1x25t3cRmzfK1mlo2I= +github.com/ldez/usetesting v0.4.2 h1:J2WwbrFGk3wx4cZwSMiCQQ00kjGR0+tuuyW0Lqm4lwA= +github.com/ldez/usetesting v0.4.2/go.mod h1:eEs46T3PpQ+9RgN9VjpY6qWdiw2/QmfiDeWmdZdrjIQ= +github.com/leonklingele/grouper v1.1.2 h1:o1ARBDLOmmasUaNDesWqWCIFH3u7hoFlM84YrjT3mIY= +github.com/leonklingele/grouper v1.1.2/go.mod h1:6D0M/HVkhs2yRKRFZUoGjeDy7EZTfFBE9gl4kjmIGkA= +github.com/macabu/inamedparam v0.1.3 h1:2tk/phHkMlEL/1GNe/Yf6kkR/hkcUdAEY3L0hjYV1Mk= +github.com/macabu/inamedparam v0.1.3/go.mod h1:93FLICAIk/quk7eaPPQvbzihUdn/QkGDwIZEoLtpH6I= +github.com/maratori/testableexamples v1.0.0 h1:dU5alXRrD8WKSjOUnmJZuzdxWOEQ57+7s93SLMxb2vI= +github.com/maratori/testableexamples v1.0.0/go.mod h1:4rhjL1n20TUTT4vdh3RDqSizKLyXp7K2u6HgraZCGzE= +github.com/maratori/testpackage v1.1.1 h1:S58XVV5AD7HADMmD0fNnziNHqKvSdDuEKdPD1rNTU04= +github.com/maratori/testpackage v1.1.1/go.mod h1:s4gRK/ym6AMrqpOa/kEbQTV4Q4jb7WeLZzVhVVVOQMc= +github.com/matoous/godox v1.1.0 h1:W5mqwbyWrwZv6OQ5Z1a/DHGMOvXYCBP3+Ht7KMoJhq4= +github.com/matoous/godox v1.1.0/go.mod h1:jgE/3fUXiTurkdHOLT5WEkThTSuE7yxHv5iWPa80afs= +github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= +github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A= -github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= -github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= -github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= -github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= -github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/mgechev/revive v1.7.0 h1:JyeQ4yO5K8aZhIKf5rec56u0376h8AlKNQEmjfkjKlY= +github.com/mgechev/revive v1.7.0/go.mod h1:qZnwcNhoguE58dfi96IJeSTPeZQejNeoMQLUZGi4SW4= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/moricho/tparallel v0.3.2 h1:odr8aZVFA3NZrNybggMkYO3rgPRcqjeQUlBBFVxKHTI= +github.com/moricho/tparallel v0.3.2/go.mod h1:OQ+K3b4Ln3l2TZveGCywybl68glfLEwFGqvnjok8b+U= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81U= +github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE= +github.com/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhKRf3Swg= +github.com/nishanths/exhaustive v0.12.0/go.mod h1:mEZ95wPIZW+x8kC4TgC+9YCUgiST7ecevsVDTgc2obs= +github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk= +github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c= +github.com/nunnatsa/ginkgolinter v0.19.1 h1:mjwbOlDQxZi9Cal+KfbEJTCz327OLNfwNvoZ70NJ+c4= +github.com/nunnatsa/ginkgolinter v0.19.1/go.mod h1:jkQ3naZDmxaZMXPWaS9rblH+i+GWXQCaS/JFIWcOH2s= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU= +github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk= +github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= +github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/owenrumney/go-sarif/v2 v2.3.3 h1:ubWDJcF5i3L/EIOER+ZyQ03IfplbSU1BLOE26uKQIIU= -github.com/owenrumney/go-sarif/v2 v2.3.3/go.mod h1:MSqMMx9WqlBSY7pXoOZWgEsVB4FDNfhcaXDA1j6Sr+w= -github.com/package-url/packageurl-go v0.1.3 h1:4juMED3hHiz0set3Vq3KeQ75KD1avthoXLtmE3I0PLs= -github.com/package-url/packageurl-go v0.1.3/go.mod h1:nKAWB8E6uk1MHqiS/lQb9pYBGH2+mdJ2PJc2s50dQY0= -github.com/pandatix/go-cvss v0.6.2 h1:TFiHlzUkT67s6UkelHmK6s1INKVUG7nlKYiWWDTITGI= -github.com/pandatix/go-cvss v0.6.2/go.mod h1:jDXYlQBZrc8nvrMUVVvTG8PhmuShOnKrxP53nOFkt8Q= +github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= +github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= +github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= +github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= +github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= +github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= +github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/polyfloyd/go-errorlint v1.7.1 h1:RyLVXIbosq1gBdk/pChWA8zWYLsq9UEw7a1L5TVMCnA= +github.com/polyfloyd/go-errorlint v1.7.1/go.mod h1:aXjNb1x2TNhoLsk26iv1yl7a+zTnXPhwEMtEXukiLR8= +github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= +github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= +github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= +github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= +github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1 h1:+Wl/0aFp0hpuHM3H//KMft64WQ1yX9LdJY64Qm/gFCo= +github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1/go.mod h1:GJLgqsLeo4qgavUoL8JeGFNS7qcisx3awV/w9eWTmNI= +github.com/quasilyte/go-ruleguard/dsl v0.3.22 h1:wd8zkOhSNr+I+8Qeciml08ivDt1pSXe60+5DqOpCjPE= +github.com/quasilyte/go-ruleguard/dsl v0.3.22/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= +github.com/quasilyte/gogrep v0.5.0 h1:eTKODPXbI8ffJMN+W2aE0+oL0z/nh8/5eNdiO34SOAo= +github.com/quasilyte/gogrep v0.5.0/go.mod h1:Cm9lpz9NZjEoL1tgZ2OgeUKPIxL1meE7eo60Z6Sk+Ng= +github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 h1:TCg2WBOl980XxGFEZSS6KlBGIV0diGdySzxATTWoqaU= +github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0= +github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 h1:M8mH9eK4OUR4lu7Gd+PU1fV2/qnDNfzT635KRSObncs= +github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8A4Y+GyBgPuaQJuWiy0XYftx4Xm/y5Jqk9I6VQ= +github.com/raeperd/recvcheck v0.2.0 h1:GnU+NsbiCqdC2XX5+vMZzP+jAJC5fht7rcVTAhX74UI= +github.com/raeperd/recvcheck v0.2.0/go.mod h1:n04eYkwIR0JbgD73wT8wL4JjPC3wm0nFtzBnWNocnYU= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/safedep/dry v0.0.0-20250410092643-c7079e2f9442 h1:+wee7FBBGm/SD46Y+cHh6GOihYI2PZ4ql2la62in7Pk= -github.com/safedep/dry v0.0.0-20250410092643-c7079e2f9442/go.mod h1:EBAaPIWWi5sPFShU5wsTGynZekSo1Mxf+l/zzs1KTls= -github.com/safedep/vet v1.10.1 h1:L8yG3X/t3I9fDVICc0DZ+zGweoLhDeyFIK6ubcNiXUc= -github.com/safedep/vet v1.10.1/go.mod h1:DN+59m5kB1QSOpdstCk9rfLTMCPtT/ZLMP5rwuix/j8= -github.com/sanity-io/litter v1.5.8 h1:uM/2lKrWdGbRXDrIq08Lh9XtVYoeGtcQxk9rtQ7+rYg= -github.com/sanity-io/litter v1.5.8/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U= -github.com/schollz/closestmatch v2.1.0+incompatible h1:Uel2GXEpJqOWBrlyI+oY9LTiyyjYS17cCYRqP13/SHk= -github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= -github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= -github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/ryancurrah/gomodguard v1.3.5 h1:cShyguSwUEeC0jS7ylOiG/idnd1TpJ1LfHGpV3oJmPU= +github.com/ryancurrah/gomodguard v1.3.5/go.mod h1:MXlEPQRxgfPQa62O8wzK3Ozbkv9Rkqr+wKjSxTdsNJE= +github.com/ryanrolds/sqlclosecheck v0.5.1 h1:dibWW826u0P8jNLsLN+En7+RqWWTYrjCB9fJfSfdyCU= +github.com/ryanrolds/sqlclosecheck v0.5.1/go.mod h1:2g3dUjoS6AL4huFdv6wn55WpLIDjY7ZgUR4J8HOO/XQ= +github.com/safedep/dry v0.0.0-20250514080944-bb77f30c7175 h1:TxAI6m/v01CL+kwIYE3RZsuxu01pbuGy3wOi3WyBT1E= +github.com/safedep/dry v0.0.0-20250514080944-bb77f30c7175/go.mod h1:Mdqx/Q2DhAcN38XiUNTGCC5MktofYDQW9Az7YWGEF0s= +github.com/sagikazarmark/locafero v0.9.0 h1:GbgQGNtTrEmddYDSAH9QLRyfAHY12md+8YFTqyMTC9k= +github.com/sagikazarmark/locafero v0.9.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk= +github.com/sanposhiho/wastedassign/v2 v2.1.0 h1:crurBF7fJKIORrV85u9UUpePDYGWnwvv3+A96WvwXT0= +github.com/sanposhiho/wastedassign/v2 v2.1.0/go.mod h1:+oSmSC+9bQ+VUAxA66nBb0Z7N8CK7mscKTDYC6aIek4= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 h1:PKK9DyHxif4LZo+uQSgXNqs0jj5+xZwwfKHgph2lxBw= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.1/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= +github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tMEOsumirXcOJqAw= +github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ= +github.com/sashamelentyev/usestdlibvars v1.28.0 h1:jZnudE2zKCtYlGzLVreNp5pmCdOxXUzwsMDBkR21cyQ= +github.com/sashamelentyev/usestdlibvars v1.28.0/go.mod h1:9nl0jgOfHKWNFS43Ojw0i7aRoS4j6EBye3YBhmAIRF8= +github.com/securego/gosec/v2 v2.22.2 h1:IXbuI7cJninj0nRpZSLCUlotsj8jGusohfONMrHoF6g= +github.com/securego/gosec/v2 v2.22.2/go.mod h1:UEBGA+dSKb+VqM6TdehR7lnQtIIMorYJ4/9CW1KVQBE= +github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= +github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+Wwfd0XE= +github.com/sivchari/containedctx v1.0.3/go.mod h1:c1RDvCbnJLtH4lLcYD/GqwiBSSf4F5Qk0xld2rBqzJ4= +github.com/sivchari/tenv v1.12.1 h1:+E0QzjktdnExv/wwsnnyk4oqZBUfuh89YMQT1cyuvSY= +github.com/sivchari/tenv v1.12.1/go.mod h1:1LjSOUCc25snIr5n3DtGGrENhX3LuWefcplwVGC24mw= +github.com/sonatard/noctx v0.1.0 h1:JjqOc2WN16ISWAjAk8M5ej0RfExEXtkEyExl2hLW+OM= +github.com/sonatard/noctx v0.1.0/go.mod h1:0RvBxqY8D4j9cTTTWE8ylt2vqj2EPI8fHmrxHdsaZ2c= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/sourcegraph/go-diff v0.7.0 h1:9uLlrd5T46OXs5qpp8L/MTltk0zikUGi0sNNyCpA8G0= +github.com/sourcegraph/go-diff v0.7.0/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs= +github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA= +github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo= +github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= +github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= +github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4= +github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= +github.com/ssgreg/nlreturn/v2 v2.2.1 h1:X4XDI7jstt3ySqGU86YGAURbxw3oTDPK9sPEi6YEwQ0= +github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= +github.com/stbenjam/no-sprintf-host-port v0.2.0 h1:i8pxvGrt1+4G0czLr/WnmyH7zbZ8Bg8etvARQ1rpyl4= +github.com/stbenjam/no-sprintf-host-port v0.2.0/go.mod h1:eL0bQ9PasS0hsyTyfTjjG+E80QIyPnBVQbYZyv20Jfk= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/tdewolff/minify/v2 v2.23.1 h1:r6sKQrumHzskWZRdhiRa+pZhn7CdBMojACNP9fuKpXQ= -github.com/tdewolff/minify/v2 v2.23.1/go.mod h1:RkUGjklq6uIsBoOdzY3ll35HKKQ2aFqLQhnanBHhDyU= -github.com/tdewolff/parse/v2 v2.7.23 h1:sCW2PNTCM1yVldh5YK/8wrpRI9rSbloUZWjAydlN2IA= -github.com/tdewolff/parse/v2 v2.7.23/go.mod h1:I7TXO37t3aSG9SlPUBefAhgIF8nt7yYUwVGgETIoBcA= -github.com/tdewolff/test v1.0.11 h1:FdLbwQVHxqG16SlkGveC0JVyrJN62COWTRyUFzfbtBE= -github.com/tdewolff/test v1.0.11/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8= -github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= -github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= -github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= -github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= -github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= -github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= -github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= -github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= -github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= -github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= -github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= -github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= -github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= -github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= -github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= -github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= -github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= -github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= -github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 h1:6fRhSjgLCkTD3JnJxvaJ4Sj+TYblw757bqYgZaOq5ZY= -github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= -github.com/yosssi/ace v0.0.5 h1:tUkIP/BLdKqrlrPwcmH0shwEEhTRHoGnc1wFIWmaBUA= -github.com/yosssi/ace v0.0.5/go.mod h1:ALfIzm2vT7t5ZE7uoIZqF3TQ7SAOyupFZnkrF5id+K0= -github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= -github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= -github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= -github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/tdakkota/asciicheck v0.4.1 h1:bm0tbcmi0jezRA2b5kg4ozmMuGAFotKI3RZfrhfovg8= +github.com/tdakkota/asciicheck v0.4.1/go.mod h1:0k7M3rCfRXb0Z6bwgvkEIMleKH3kXNz9UqJ9Xuqopr8= +github.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA= +github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0= +github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpRQGxTSkNYKJ51yaw6ChIqO+Je8UqsTKN/cDag= +github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= +github.com/tetafro/godot v1.5.0 h1:aNwfVI4I3+gdxjMgYPus9eHmoBeJIbnajOyqZYStzuw= +github.com/tetafro/godot v1.5.0/go.mod h1:2oVxTBSftRTh4+MVfUaUXR6bn2GDXCaMcOG4Dk3rfio= +github.com/timakin/bodyclose v0.0.0-20241017074812-ed6a65f985e3 h1:y4mJRFlM6fUyPhoXuFg/Yu02fg/nIPFMOY8tOqppoFg= +github.com/timakin/bodyclose v0.0.0-20241017074812-ed6a65f985e3/go.mod h1:mkjARE7Yr8qU23YcGMSALbIxTQ9r9QBVahQOBRfU460= +github.com/timonwong/loggercheck v0.10.1 h1:uVZYClxQFpw55eh+PIoqM7uAOHMrhVcDoWDery9R8Lg= +github.com/timonwong/loggercheck v0.10.1/go.mod h1:HEAWU8djynujaAVX7QI65Myb8qgfcZ1uKbdpg3ZzKl8= +github.com/tomarrell/wrapcheck/v2 v2.10.0 h1:SzRCryzy4IrAH7bVGG4cK40tNUhmVmMDuJujy4XwYDg= +github.com/tomarrell/wrapcheck/v2 v2.10.0/go.mod h1:g9vNIyhb5/9TQgumxQyOEqDHsmGYcGsVMOx/xGkqdMo= +github.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+yU8u1Zw= +github.com/tommy-muehle/go-mnd/v2 v2.5.1/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw= +github.com/ultraware/funlen v0.2.0 h1:gCHmCn+d2/1SemTdYMiKLAHFYxTYz7z9VIDRaTGyLkI= +github.com/ultraware/funlen v0.2.0/go.mod h1:ZE0q4TsJ8T1SQcjmkhN/w+MceuatI6pBFSxxyteHIJA= +github.com/ultraware/whitespace v0.2.0 h1:TYowo2m9Nfj1baEQBjuHzvMRbp19i+RCcRYrSWoFa+g= +github.com/ultraware/whitespace v0.2.0/go.mod h1:XcP1RLD81eV4BW8UhQlpaR+SDc2givTvyI8a586WjW8= +github.com/uudashr/gocognit v1.2.0 h1:3BU9aMr1xbhPlvJLSydKwdLN3tEUUrzPSSM8S4hDYRA= +github.com/uudashr/gocognit v1.2.0/go.mod h1:k/DdKPI6XBZO1q7HgoV2juESI2/Ofj9AcHPZhBBdrTU= +github.com/uudashr/iface v1.3.1 h1:bA51vmVx1UIhiIsQFSNq6GZ6VPTk3WNMZgRiCe9R29U= +github.com/uudashr/iface v1.3.1/go.mod h1:4QvspiRd3JLPAEXBQ9AiZpLbJlrWWgRChOKDJEuQTdg= +github.com/xen0n/gosmopolitan v1.2.2 h1:/p2KTnMzwRexIW8GlKawsTWOxn7UHA+jCMF/V8HHtvU= +github.com/xen0n/gosmopolitan v1.2.2/go.mod h1:7XX7Mj61uLYrj0qmeN0zi7XDon9JRAEhYQqAPLVNTeg= +github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM= +github.com/yagipy/maintidx v1.0.0/go.mod h1:0qNf/I/CCZXSMhsRsrEPDZ+DkekpKLXAJfsTACwgXLk= +github.com/yeya24/promlinter v0.3.0 h1:JVDbMp08lVCP7Y6NP3qHroGAO6z2yGKQtS5JsjqtoFs= +github.com/yeya24/promlinter v0.3.0/go.mod h1:cDfJQQYv9uYciW60QT0eeHlFodotkYZlL+YcPQN+mW4= +github.com/ykadowak/zerologlint v0.1.5 h1:Gy/fMz1dFQN9JZTPjv1hxEk+sRWm05row04Yoolgdiw= +github.com/ykadowak/zerologlint v0.1.5/go.mod h1:KaUskqF3e/v59oPmdq1U1DnKcuHokl2/K1U4pmIELKg= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/zclconf/go-cty v1.16.2 h1:LAJSwc3v81IRBZyUVQDUdZ7hs3SYs9jv0eZJDWHD/70= -github.com/zclconf/go-cty v1.16.2/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= -github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo= -github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM= -github.com/zclconf/go-cty-yaml v1.1.0 h1:nP+jp0qPHv2IhUVqmQSzjvqAWcObN0KBkUl2rWBdig0= -github.com/zclconf/go-cty-yaml v1.1.0/go.mod h1:9YLUH4g7lOhVWqUbctnVlZ5KLpg7JAprQNgxSZ1Gyxs= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +gitlab.com/bosi/decorder v0.4.2 h1:qbQaV3zgwnBZ4zPMhGLW4KZe7A7NwxEhJx39R3shffo= +gitlab.com/bosi/decorder v0.4.2/go.mod h1:muuhHoaJkA9QLcYHq4Mj8FJUwDZ+EirSHRiaTcTf6T8= +go-simpler.org/assert v0.9.0 h1:PfpmcSvL7yAnWyChSjOz6Sp6m9j5lyK8Ok9pEL31YkQ= +go-simpler.org/assert v0.9.0/go.mod h1:74Eqh5eI6vCK6Y5l3PI8ZYFXG4Sa+tkr70OIPJAUr28= +go-simpler.org/musttag v0.13.0 h1:Q/YAW0AHvaoaIbsPj3bvEI5/QFP7w696IMUpnKXQfCE= +go-simpler.org/musttag v0.13.0/go.mod h1:FTzIGeK6OkKlUDVpj0iQUXZLUO1Js9+mvykDQy9C5yM= +go-simpler.org/sloglint v0.9.0 h1:/40NQtjRx9txvsB/RN022KsUJU+zaaSb/9q9BSefSrE= +go-simpler.org/sloglint v0.9.0/go.mod h1:G/OrAF6uxj48sHahCzrbarVMptL2kjWTaUeC8+fOGww= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw= @@ -339,6 +484,8 @@ go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6Yv go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= +go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= @@ -348,35 +495,55 @@ go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN8 go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -golang.org/x/arch v0.16.0 h1:foMtLTdyOmIniqWCHjY6+JxuC54XP1fDwx4N0ASyW+U= -golang.org/x/arch v0.16.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= -golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM= golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8= +golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac h1:TSSpLIG4v+p0rPv1pNOQtl1I8knsO4S9trOxNMOLVP4= +golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -384,35 +551,63 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= -golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= -golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -420,9 +615,24 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200324003944-a576cf524670/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200329025819-fd4102a86c65/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200820010801-b793a1359eac/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20201023174141-c8cfbd0f21e6/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= +golang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= +golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU= golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -447,11 +657,8 @@ google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9x google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= -gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -464,6 +671,9 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -moul.io/http2curl/v2 v2.3.0 h1:9r3JfDzWPcbIklMOs2TnIFzDYvfAZvjeavG6EzP7jYs= -moul.io/http2curl/v2 v2.3.0/go.mod h1:RW4hyBjTWSYDOxapodpNEtX0g5Eb16sxklBqmd2RHcE= -nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= +honnef.co/go/tools v0.6.1 h1:R094WgE8K4JirYjBaOpz/AvTyUu/3wbmAoskKN/pxTI= +honnef.co/go/tools v0.6.1/go.mod h1:3puzxxljPCe8RGJX7BIy1plGbxEOZni5mR2aXe3/uk4= +mvdan.cc/gofumpt v0.7.0 h1:bg91ttqXmi9y2xawvkuMXyvAA/1ZGJqYAEGjXuP0JXU= +mvdan.cc/gofumpt v0.7.0/go.mod h1:txVFJy/Sc/mvaycET54pV8SW8gWxTlUuGHVEcncmNUo= +mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f h1:lMpcwN6GxNbWtbpI1+xzFLSW8XzX0u72NttUGVFjO3U= +mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f/go.mod h1:RSLa7mKKCNeTTMHBw5Hsy2rfJmd6O2ivt9Dw9ZqCQpQ= diff --git a/guard/guard.go b/guard/guard.go new file mode 100644 index 0000000..771f89b --- /dev/null +++ b/guard/guard.go @@ -0,0 +1,258 @@ +package guard + +import ( + "context" + "fmt" + "os" + "os/exec" + "sync" + "time" + + packagev1 "buf.build/gen/go/safedep/api/protocolbuffers/go/safedep/messages/package/v1" + "github.com/safedep/dry/log" + "github.com/safedep/pmg/analyzer" + "github.com/safedep/pmg/packagemanager" +) + +type PackageManagerGuardInteraction struct { + // SetStatus is called to set the status of the guard in the UI + SetStatus func(status string) + + // ClearStatus is called to clear the status of the guard in the UI + ClearStatus func() + + // GetConfirmationOnMalware is called to get the confirmation of the user on the malware packages + GetConfirmationOnMalware func(malwarePackages []*analyzer.PackageVersionAnalysisResult) (bool, error) + + // Block is called to block the installation of the malware packages. One or more malicious + // packages are passed as arguments. These are the packages that were detected as malicious. + // Client code must perform the necessary error handling and termination of the process. + Block func(...*analyzer.PackageVersionAnalysisResult) error +} + +type PackageManagerGuardConfig struct { + ResolveDependencies bool + MaxConcurrentAnalyzes int + AnalysisTimeout time.Duration +} + +func DefaultPackageManagerGuardConfig() PackageManagerGuardConfig { + return PackageManagerGuardConfig{ + ResolveDependencies: true, + MaxConcurrentAnalyzes: 10, + AnalysisTimeout: 5 * time.Minute, + } +} + +type packageManagerGuard struct { + config PackageManagerGuardConfig + interaction PackageManagerGuardInteraction + analyzers []analyzer.PackageVersionAnalyzer + packageManager packagemanager.PackageManager + packageResolver packagemanager.PackageResolver +} + +func NewPackageManagerGuard(config PackageManagerGuardConfig, + packageManager packagemanager.PackageManager, + packageResolver packagemanager.PackageResolver, + analyzers []analyzer.PackageVersionAnalyzer, + interaction PackageManagerGuardInteraction, +) (*packageManagerGuard, error) { + return &packageManagerGuard{ + interaction: interaction, + analyzers: analyzers, + packageManager: packageManager, + packageResolver: packageResolver, + config: config, + }, nil +} + +func (g *packageManagerGuard) Run(ctx context.Context, args []string) error { + log.Debugf("Running package manager guard with args: %v", args) + + parsedCommand, err := g.packageManager.ParseCommand(args) + if err != nil { + return fmt.Errorf("failed to parse command: %w", err) + } + + if !parsedCommand.HasInstallTarget() { + log.Debugf("No install target found, continuing execution") + return g.continueExecution(ctx, parsedCommand) + } + + // TODO: We should track the dependency tree here so that we can trace a + // dependency to one of the parent packages from install targets + + packagesToAnalyze := []*packagev1.PackageVersion{} + for _, installTarget := range parsedCommand.InstallTargets { + packagesToAnalyze = append(packagesToAnalyze, installTarget.PackageVersion) + } + + log.Debugf("Found %d install targets", len(parsedCommand.InstallTargets)) + + g.setStatus(fmt.Sprintf("Resolving dependencies for %d packages", len(parsedCommand.InstallTargets))) + + if g.config.ResolveDependencies { + for _, pkg := range parsedCommand.InstallTargets { + if pkg.PackageVersion.GetVersion() == "" { + log.Debugf("Resolving latest version for package: %s", pkg.PackageVersion.Package.Name) + latestVersion, err := g.packageResolver.ResolveLatestVersion(ctx, pkg.PackageVersion.GetPackage()) + if err != nil { + return fmt.Errorf("failed to resolve latest version: %w", err) + } + + pkg.PackageVersion.Version = latestVersion.GetVersion() + } + + log.Debugf("Resolving dependencies for package: %s@%s", pkg.PackageVersion.Package.Name, pkg.PackageVersion.Version) + + dependencies, err := g.packageResolver.ResolveDependencies(ctx, pkg.PackageVersion) + if err != nil { + return fmt.Errorf("failed to resolve dependencies: %w", err) + } + + log.Debugf("Resolved %d dependencies for package: %s@%s", len(dependencies), + pkg.PackageVersion.Package.Name, pkg.PackageVersion.Version) + + packagesToAnalyze = append(packagesToAnalyze, dependencies...) + } + } + + log.Debugf("Checking %d packages for malware", len(packagesToAnalyze)) + + g.setStatus(fmt.Sprintf("Analyzing %d packages for malware", len(packagesToAnalyze))) + + analysisResults, err := g.concurrentAnalyzePackages(ctx, packagesToAnalyze) + if err != nil { + return fmt.Errorf("failed to analyze packages: %w", err) + } + + confirmableMalwarePackages := []*analyzer.PackageVersionAnalysisResult{} + for _, result := range analysisResults { + if result.Action == analyzer.ActionBlock { + return g.blockInstallation(result) + } + + if result.Action == analyzer.ActionConfirm { + confirmableMalwarePackages = append(confirmableMalwarePackages, result) + } + } + + if len(confirmableMalwarePackages) > 0 { + confirmed, err := g.getConfirmationOnMalware(ctx, confirmableMalwarePackages) + if err != nil { + return fmt.Errorf("failed to get confirmation on malware: %w", err) + } + + if !confirmed { + return g.blockInstallation(confirmableMalwarePackages...) + } + } + + log.Debugf("No malicious packages found, continuing execution") + + g.clearStatus() + return g.continueExecution(ctx, parsedCommand) +} + +func (g *packageManagerGuard) continueExecution(ctx context.Context, pc *packagemanager.ParsedCommand) error { + if len(pc.Command.Exe) == 0 { + return fmt.Errorf("no command to execute") + } + + cmd := exec.CommandContext(ctx, pc.Command.Exe, pc.Command.Args...) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + return cmd.Run() +} + +func (g *packageManagerGuard) concurrentAnalyzePackages(ctx context.Context, + packages []*packagev1.PackageVersion) ([]*analyzer.PackageVersionAnalysisResult, error) { + + ctx, cancel := context.WithTimeout(ctx, g.config.AnalysisTimeout) + defer cancel() + + wg := sync.WaitGroup{} + jobs := make(chan *packagev1.PackageVersion, len(packages)) + results := make(chan *analyzer.PackageVersionAnalysisResult, len(packages)) + + for i := 0; i < g.config.MaxConcurrentAnalyzes; i++ { + wg.Add(1) + go func() { + defer wg.Done() + for pkg := range jobs { + for _, analyzer := range g.analyzers { + analysisResult, err := analyzer.Analyze(ctx, pkg) + if err != nil { + // This is not an error because we may not have results for all packages + log.Debugf("failed to analyze package: %v", err) + continue + } + + results <- analysisResult + } + } + }() + } + + for _, pkg := range packages { + jobs <- pkg + } + close(jobs) + + analysisResults := []*analyzer.PackageVersionAnalysisResult{} + go func() { + for result := range results { + analysisResults = append(analysisResults, result) + } + }() + + waiter := make(chan struct{}) + go func() { + wg.Wait() + close(waiter) + close(results) + }() + + select { + case <-waiter: + case <-ctx.Done(): + return nil, fmt.Errorf("analysis timed out") + } + + return analysisResults, nil +} + +func (g *packageManagerGuard) getConfirmationOnMalware(ctx context.Context, malwarePackages []*analyzer.PackageVersionAnalysisResult) (bool, error) { + if g.interaction.GetConfirmationOnMalware == nil { + return false, nil + } + + return g.interaction.GetConfirmationOnMalware(malwarePackages) +} + +func (g *packageManagerGuard) setStatus(status string) { + if g.interaction.SetStatus == nil { + return + } + + g.interaction.SetStatus(status) +} + +func (g *packageManagerGuard) blockInstallation(malwarePackages ...*analyzer.PackageVersionAnalysisResult) error { + if g.interaction.Block == nil { + return nil + } + + return g.interaction.Block(malwarePackages...) +} + +func (g *packageManagerGuard) clearStatus() { + if g.interaction.ClearStatus == nil { + return + } + + g.interaction.ClearStatus() +} diff --git a/pkg/common/utils/terminal_colors.go b/internal/ui/colors.go similarity index 51% rename from pkg/common/utils/terminal_colors.go rename to internal/ui/colors.go index a7a281d..642231b 100644 --- a/pkg/common/utils/terminal_colors.go +++ b/internal/ui/colors.go @@ -1,15 +1,19 @@ -package utils +package ui import "github.com/fatih/color" +type ColorFn func(format string, a ...interface{}) string + type TerminalColors struct { - Red func(format string, a ...interface{}) string - Yellow func(format string, a ...interface{}) string - Cyan func(format string, a ...interface{}) string - Green func(format string, a ...interface{}) string + Normal ColorFn + Red ColorFn + Yellow ColorFn + Cyan ColorFn + Green ColorFn } -var colors = TerminalColors{ +var Colors = TerminalColors{ + Normal: color.New().SprintfFunc(), Red: color.New(color.FgRed, color.Bold).SprintfFunc(), Yellow: color.New(color.FgYellow).SprintfFunc(), Cyan: color.New(color.FgCyan).SprintfFunc(), diff --git a/internal/ui/spinner.go b/internal/ui/spinner.go new file mode 100644 index 0000000..c5c90af --- /dev/null +++ b/internal/ui/spinner.go @@ -0,0 +1,53 @@ +package ui + +import ( + "fmt" + "time" +) + +var spinnerChan chan bool + +func StartSpinner(msg string) { + StartSpinnerWithColor(msg, Colors.Normal) +} + +func StartSpinnerWithColor(msg string, c ColorFn) { + if c == nil { + c = Colors.Normal + } + + style := `⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏` + frames := []rune(style) + length := len(frames) + + spinnerChan = make(chan bool) + + ticker := time.NewTicker(100 * time.Millisecond) + go func() { + pos := 0 + + for { + select { + case <-spinnerChan: + ticker.Stop() + return + case <-ticker.C: + fmt.Printf("\r%s ... %s", c(msg), string(frames[pos%length])) + pos += 1 + } + } + }() +} + +func StopSpinner() { + // Gracefully handle the case where the spinner is already stopped + // and the channel is closed, yet client code calls StopSpinner() again. + defer func() { + _ = recover() + }() + + close(spinnerChan) + + fmt.Printf("\r") + fmt.Println() +} diff --git a/internal/ui/ui.go b/internal/ui/ui.go new file mode 100644 index 0000000..d908d00 --- /dev/null +++ b/internal/ui/ui.go @@ -0,0 +1,150 @@ +package ui + +import ( + "fmt" + "os" + "strings" + + "github.com/safedep/pmg/analyzer" +) + +// The UI is internal to PMG and opinionated for the CLI. +// It is not intended to be used outside of PMG. + +type VerbosityLevel int + +const ( + // PMG is hidden from the user except for errors + // and when malicious packages are detected + VerbosityLevelSilent VerbosityLevel = iota + + // Show minimal status updates + VerbosityLevelNormal + + // Show verbose status updates and information including + // information about malicious packages + VerbosityLevelVerbose +) + +var verbosityLevel VerbosityLevel = VerbosityLevelNormal + +func SetVerbosityLevel(level VerbosityLevel) { + verbosityLevel = level +} + +func ClearStatus() { + StopSpinner() + fmt.Print("\r") +} + +func Block(malwarePackages ...*analyzer.PackageVersionAnalysisResult) error { + StopSpinner() + + fmt.Println() + fmt.Println(Colors.Red("❌ Malicious package blocked!")) + + printMaliciousPackagesList(malwarePackages) + + fmt.Println() + os.Exit(1) + + return nil +} + +func SetStatus(status string) { + if verbosityLevel == VerbosityLevelSilent { + return + } + + StopSpinner() + StartSpinnerWithColor(fmt.Sprintf("ℹ️ %s", status), Colors.Green) +} + +func GetConfirmationOnMalware(malwarePackages []*analyzer.PackageVersionAnalysisResult) (bool, error) { + StopSpinner() + + fmt.Println() + fmt.Println(Colors.Red(fmt.Sprintf("🚨 Suspicious package(s) detected: %d", len(malwarePackages)))) + + printMaliciousPackagesList(malwarePackages) + + fmt.Println() + fmt.Print(Colors.Yellow("Do you want to continue with the installation? (y/N) ")) + + var response string + + // We don't care about the error here because we will return false + // if the user doesn't provide a valid response + _, _ = fmt.Scanln(&response) + + if len(response) == 0 { + return false, nil + } + + response = strings.ToLower(response) + if response == "y" || response == "yes" || response[0] == 'y' { + return true, nil + } + + return false, nil +} + +func Fatalf(msg string, args ...interface{}) { + ClearStatus() + + fmt.Println(Colors.Red(fmt.Sprintf(msg, args...))) + os.Exit(1) +} + +func printMaliciousPackagesList(malwarePackages []*analyzer.PackageVersionAnalysisResult) { + for _, mp := range malwarePackages { + fmt.Println() + fmt.Println("⚠️ ", Colors.Red(fmt.Sprintf("%s@%s", mp.PackageVersion.GetPackage().GetName(), + mp.PackageVersion.GetVersion()))) + + if verbosityLevel == VerbosityLevelVerbose { + fmt.Println(Colors.Yellow(termWidthFormatText(mp.Summary, 80))) + + if mp.ReferenceURL != "" { + fmt.Println() + fmt.Println(Colors.Yellow(fmt.Sprintf("Reference: %s", mp.ReferenceURL))) + } + } + } +} + +// Format the string to be maximum maxWidth. Use newlines to wrap the text. +func termWidthFormatText(text string, maxWidth int) string { + // Replace all newlines with spaces so that we can split the text into words + // This is to ensure that we don't split the text at the newlines + text = strings.ReplaceAll(text, "\n", " ") + + words := strings.Split(text, " ") + lines := []string{} + currentLine := "" + + for i, word := range words { + // Skip empty words that might result from multiple spaces + if word == "" { + continue + } + + if i == 0 { + // First word doesn't need a leading space + currentLine = word + } else if len(currentLine)+len(word)+1 > maxWidth { + // +1 for the space we would add + lines = append(lines, currentLine) + currentLine = word + } else { + currentLine += " " + word + } + } + + // Don't forget to add the last line + if currentLine != "" { + lines = append(lines, currentLine) + } + + return strings.Join(lines, "\n") +} diff --git a/internal/ui/ui_test.go b/internal/ui/ui_test.go new file mode 100644 index 0000000..00b0bb0 --- /dev/null +++ b/internal/ui/ui_test.go @@ -0,0 +1,79 @@ +package ui + +import ( + "testing" +) + +// TestTermWidthFormatText is exported for testing +func TestTermWidthFormatTextFunc(t *testing.T) { + tests := []struct { + name string + text string + maxWidth int + expected string + }{ + { + name: "empty string", + text: "", + maxWidth: 10, + expected: "", + }, + { + name: "single word less than max width", + text: "hello", + maxWidth: 10, + expected: "hello", + }, + { + name: "single word longer than max width", + text: "supercalifragilisticexpialidocious", + maxWidth: 10, + expected: "supercalifragilisticexpialidocious", + }, + { + name: "multiple words on single line", + text: "hello world", + maxWidth: 20, + expected: "hello world", + }, + { + name: "multiple words wrapped to multiple lines", + text: "The quick brown fox jumps over the lazy dog", + maxWidth: 20, + expected: "The quick brown fox\njumps over the lazy\ndog", + }, + { + name: "text with existing newlines", + text: "hello\nworld", + maxWidth: 20, + expected: "hello world", + }, + { + name: "text with multiple spaces", + text: "hello world test", + maxWidth: 20, + expected: "hello world test", + }, + { + name: "very small max width", + text: "hello world", + maxWidth: 3, + expected: "hello\nworld", + }, + { + name: "large max width", + text: "The quick brown fox jumps over the lazy dog", + maxWidth: 100, + expected: "The quick brown fox jumps over the lazy dog", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := termWidthFormatText(tt.text, tt.maxWidth) + if result != tt.expected { + t.Errorf("termWidthFormatText() = %q, want %q", result, tt.expected) + } + }) + } +} diff --git a/lefthook.yml b/lefthook.yml new file mode 100644 index 0000000..36ec7c3 --- /dev/null +++ b/lefthook.yml @@ -0,0 +1,17 @@ +pre-push: + parallel: true + commands: + test: + run: go test -v ./... + +pre-commit: + parallel: true + commands: + linter: + run: go tool golangci-lint run -n + secrets-scanning: + files: git diff --name-only --diff-filter=d --staged + # https://gitlab.com/gitlab-org/gitlab/-/blob/master/lefthook.yml + run: 'if command -v gitleaks > /dev/null 2>&1; then gitleaks protect --no-banner --staged --redact --verbose; else echo "WARNING: gitleaks is not installed. Please install it. See https://github.com/gitleaks/gitleaks#installing"; fi' + + diff --git a/main.go b/main.go index a85baf7..778ba9c 100644 --- a/main.go +++ b/main.go @@ -6,29 +6,74 @@ import ( "github.com/safedep/dry/log" "github.com/safedep/pmg/cmd/npm" + "github.com/safedep/pmg/config" + "github.com/safedep/pmg/internal/ui" "github.com/spf13/cobra" ) -func main() { - var debug bool +var ( + debug bool + silent bool + verbose bool + logFile string + globalConfig config.Config +) +func main() { cmd := &cobra.Command{ Use: "pmg", TraverseChildren: true, PersistentPreRun: func(cmd *cobra.Command, args []string) { - if debug { - log.Init("pmg-logger", "debug") + // Always set this first because we will override the log + // level if debug or verbose is set + if logFile != "" { + os.Setenv("APP_LOG_FILE", logFile) + os.Setenv("APP_LOG_LEVEL", "info") } + + // Set the log level when debug is enabled + if debug { + os.Setenv("APP_LOG_LEVEL", "debug") + } + + // Skip stdout logging when debugging is not enabled + if !debug { + os.Setenv("APP_LOG_SKIP_STDOUT_LOGGER", "true") + } + + if silent && verbose { + fmt.Println("pmg: --silent and --verbose cannot be used together") + os.Exit(1) + } + + if silent { + ui.SetVerbosityLevel(ui.VerbosityLevelSilent) + } else if verbose { + ui.SetVerbosityLevel(ui.VerbosityLevelVerbose) + } + + log.InitZapLogger("pmg", "cli") + cmd.SetContext(globalConfig.Inject(cmd.Context())) }, RunE: func(cmd *cobra.Command, args []string) error { if len(args) == 0 { + cmd.Help() return nil } + return fmt.Errorf("pmg: %s is not a valid command", args[0]) }, } - cmd.PersistentFlags().BoolVar(&debug, "debug", false, "Enable debug logging") + cmd.PersistentFlags().StringVar(&logFile, "log", "", "Log file to write to") + cmd.PersistentFlags().BoolVar(&silent, "silent", false, "Silent mode for invisible experience") + cmd.PersistentFlags().BoolVar(&verbose, "verbose", false, "Verbose mode for more information") + cmd.PersistentFlags().BoolVar(&debug, "debug", false, "Enable debug logging (defaults to stdout)") + cmd.PersistentFlags().BoolVar(&globalConfig.Transitive, "transitive", true, "Resolve transitive dependencies") + cmd.PersistentFlags().IntVar(&globalConfig.TransitiveDepth, "transitive-depth", 5, + "Maximum depth of transitive dependencies to resolve") + cmd.PersistentFlags().BoolVar(&globalConfig.IncludeDevDependencies, "include-dev-dependencies", false, + "Include dev dependencies in the dependency graph (slows down resolution)") cmd.AddCommand(npm.NewNpmCommand()) cmd.AddCommand(npm.NewPnpmCommand()) diff --git a/packagemanager/dependency_resolver.go b/packagemanager/dependency_resolver.go new file mode 100644 index 0000000..8f03786 --- /dev/null +++ b/packagemanager/dependency_resolver.go @@ -0,0 +1,187 @@ +package packagemanager + +import ( + "context" + "fmt" + "slices" + "sync" + + packagev1 "buf.build/gen/go/safedep/api/protocolbuffers/go/safedep/messages/package/v1" + "github.com/safedep/dry/log" + "github.com/safedep/dry/packageregistry" +) + +type dependencyResolverConfig struct { + IncludeDevDependencies bool + IncludeTransitiveDependencies bool + TransitiveDepth int + FailFast bool + MaxConcurrency int +} + +type dependencyResolver struct { + client packageregistry.Client + config dependencyResolverConfig + mutex sync.Mutex +} + +func newDependencyResolver(client packageregistry.Client, config dependencyResolverConfig) *dependencyResolver { + if config.MaxConcurrency <= 0 { + config.MaxConcurrency = 10 + } + + return &dependencyResolver{ + client: client, + config: config, + } +} + +func (r *dependencyResolver) resolveDependencies(ctx context.Context, + packageVersion *packagev1.PackageVersion) ([]*packagev1.PackageVersion, error) { + pd, err := r.client.PackageDiscovery() + if err != nil { + return nil, fmt.Errorf("failed to get package discovery: %w", err) + } + + // Track visited packages to avoid cycles + visitedPackages := make(map[string]bool) + + // Result collection + dependencies := make([]*packagev1.PackageVersion, 0) + + // Start concurrent resolution + err = r.resolvePackageDependenciesConcurrent(ctx, pd, packageVersion, 0, visitedPackages, &dependencies) + if err != nil { + return nil, fmt.Errorf("failed to resolve dependencies: %w", err) + } + + return dependencies, nil +} + +// resolvePackageDependenciesConcurrent resolves dependencies for a package version concurrently +func (r *dependencyResolver) resolvePackageDependenciesConcurrent( + ctx context.Context, + pd packageregistry.PackageDiscovery, + packageVersion *packagev1.PackageVersion, + depth int, + visitedPackages map[string]bool, + result *[]*packagev1.PackageVersion) error { + + // Check for context cancellation + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + + ff := func(err error) error { + if r.config.FailFast { + return err + } + + log.Warnf("error resolving package dependencies: %s", err) + return nil + } + + // Check depth limit + if depth > r.config.TransitiveDepth { + return ff(fmt.Errorf("exceeded maximum transitive depth of %d", r.config.TransitiveDepth)) + } + + // Skip if already visited + packageKey := r.packageKey(packageVersion) + + alreadyVisited := false + r.synchronize(func() { + alreadyVisited = visitedPackages[packageKey] + }) + + if alreadyVisited { + return nil + } + + // Mark the current package as visited + r.synchronize(func() { + visitedPackages[packageKey] = true + }) + + log.Debugf("resolving dependencies for %s@%s", packageVersion.Package.Name, packageVersion.Version) + + // Get dependencies for the current package + dependencyList, err := pd.GetPackageDependencies(packageVersion.Package.Name, packageVersion.Version) + if err != nil { + return ff(fmt.Errorf("failed to get package dependencies: %w", err)) + } + + // Collect all dependencies (and optionally dev dependencies) + dependencies := dependencyList.Dependencies + if r.config.IncludeDevDependencies { + dependencies = append(dependencies, dependencyList.DevDependencies...) + } + + // Create package version objects for all dependencies and clean versions + resolvedDependencies := make([]*packagev1.PackageVersion, 0, len(dependencies)) + for _, dependency := range dependencies { + resolvedDependencies = append(resolvedDependencies, &packagev1.PackageVersion{ + Package: &packagev1.Package{ + Ecosystem: packagev1.Ecosystem_ECOSYSTEM_NPM, + Name: dependency.Name, + }, + Version: npmCleanVersion(dependency.VersionSpec), + }) + } + + // Add resolved dependencies to the result + r.synchronize(func() { + for _, dependency := range resolvedDependencies { + if !slices.Contains(*result, dependency) { + *result = append(*result, dependency) + } + } + }) + + // Process transitive dependencies if enabled and depth limit not reached + if r.config.IncludeTransitiveDependencies && depth < r.config.TransitiveDepth && len(resolvedDependencies) > 0 { + // Create worker pool using semaphore pattern + semaphore := make(chan struct{}, r.config.MaxConcurrency) + errCh := make(chan error, len(resolvedDependencies)) + var wg sync.WaitGroup + + for _, dependency := range resolvedDependencies { + wg.Add(1) + + go func(dep *packagev1.PackageVersion) { + defer wg.Done() + + semaphore <- struct{}{} + defer func() { <-semaphore }() + + err := r.resolvePackageDependenciesConcurrent(ctx, pd, dep, depth+1, visitedPackages, result) + if err != nil { + errCh <- err + } + }(dependency) + } + + // Wait for all goroutines to finish + wg.Wait() + close(errCh) + + // Check for errors + for err := range errCh { + return ff(fmt.Errorf("failed to resolve transitive dependency: %w", err)) + } + } + + return nil +} + +func (r *dependencyResolver) packageKey(pkg *packagev1.PackageVersion) string { + return fmt.Sprintf("%s@%s", pkg.Package.Name, pkg.Version) +} + +func (r *dependencyResolver) synchronize(fn func()) { + r.mutex.Lock() + defer r.mutex.Unlock() + fn() +} diff --git a/packagemanager/npm.go b/packagemanager/npm.go new file mode 100644 index 0000000..ffed095 --- /dev/null +++ b/packagemanager/npm.go @@ -0,0 +1,156 @@ +package packagemanager + +import ( + "fmt" + "slices" + "strings" + + packagev1 "buf.build/gen/go/safedep/api/protocolbuffers/go/safedep/messages/package/v1" +) + +type NpmPackageManagerConfig struct { + InstallCommands []string + CommandName string +} + +func DefaultNpmPackageManagerConfig() NpmPackageManagerConfig { + return NpmPackageManagerConfig{ + InstallCommands: []string{"install", "i", "add"}, + CommandName: "npm", + } +} + +func DefaultPnpmPackageManagerConfig() NpmPackageManagerConfig { + return NpmPackageManagerConfig{ + InstallCommands: []string{"install", "i", "add"}, + CommandName: "pnpm", + } +} + +type npmPackageManager struct { + Config NpmPackageManagerConfig +} + +func NewNpmPackageManager(config NpmPackageManagerConfig) (*npmPackageManager, error) { + return &npmPackageManager{ + Config: config, + }, nil +} + +func (npm *npmPackageManager) Name() string { + return "npm" +} + +func (npm *npmPackageManager) ParseCommand(args []string) (*ParsedCommand, error) { + if len(args) > 0 && (args[0] == "npm" || args[0] == "pnpm") { + args = args[1:] + } + + command := Command{Exe: npm.Config.CommandName, Args: args} + + // No command specified + if len(args) < 2 { + return &ParsedCommand{ + Command: command, + }, nil + } + + // Extract packages from args + var packages []string + for idx, arg := range args { + if slices.Contains(npm.Config.InstallCommands, arg) { + // All subsequent args are packages except for flags + for i := idx + 1; i < len(args); i++ { + if strings.HasPrefix(args[i], "-") { + continue + } + + packages = append(packages, args[i]) + } + + break + } + } + + // No packages found + if len(packages) == 0 { + return &ParsedCommand{ + Command: command, + }, nil + } + + // Process all package arguments + var installTargets []*PackageInstallTarget + for _, pkg := range packages { + packageName, version, err := npmParsePackageInfo(pkg) + if err != nil { + return nil, fmt.Errorf("failed to parse package info: %w", err) + } + + // Clean version if specified + if version != "" { + version = npmCleanVersion(version) + } + + installTargets = append(installTargets, &PackageInstallTarget{ + PackageVersion: &packagev1.PackageVersion{ + Package: &packagev1.Package{ + Ecosystem: packagev1.Ecosystem_ECOSYSTEM_NPM, + Name: packageName, + }, + Version: version, + }, + }) + } + + return &ParsedCommand{ + Command: command, + InstallTargets: installTargets, + }, nil +} + +func npmParsePackageInfo(input string) (packageName, version string, err error) { + if input == "" { + return "", "", fmt.Errorf("package info cannot be empty") + } + + input = strings.TrimSpace(input) + if strings.HasPrefix(input, "@") { + // Scoped package (e.g. @types/node or @types/node@1.0.0) + lastAtIndex := strings.LastIndex(input, "@") + if lastAtIndex > 0 { + packageName = strings.TrimSpace(input[:lastAtIndex]) + version = strings.TrimSpace(input[lastAtIndex+1:]) + return packageName, version, nil + } + + // If no version specifier, return the whole input as package name + return strings.TrimSpace(input), "", nil + } + + // Normal package (e.g. lodash or lodash@4.17.21) + parts := strings.Split(input, "@") + if len(parts) == 2 { + packageName = strings.TrimSpace(parts[0]) + version = strings.TrimSpace(parts[1]) + return packageName, version, nil + } + + if len(parts) == 1 { + packageName = strings.TrimSpace(parts[0]) + return packageName, "", nil + } + + return "", "", fmt.Errorf("invalid format: expected 'package' OR 'package@version', got '%s'", input) +} + +func npmCleanVersion(version string) string { + version = strings.TrimPrefix(version, "^") + version = strings.TrimPrefix(version, "~") + + if version == "*" || version == "" { + return "latest" + } + + return version +} diff --git a/packagemanager/npm_resolver.go b/packagemanager/npm_resolver.go new file mode 100644 index 0000000..4f4eabc --- /dev/null +++ b/packagemanager/npm_resolver.go @@ -0,0 +1,84 @@ +package packagemanager + +import ( + "context" + "fmt" + + packagev1 "buf.build/gen/go/safedep/api/protocolbuffers/go/safedep/messages/package/v1" + "github.com/safedep/dry/log" + "github.com/safedep/dry/packageregistry" +) + +type NpmDependencyResolverConfig struct { + IncludeDevDependencies bool + IncludeTransitiveDependencies bool + TransitiveDepth int + + // FailFast will stop resolving dependencies after the first error + FailFast bool + + // MaxConcurrency limits the number of concurrent goroutines used for dependency resolution + MaxConcurrency int +} + +func NewDefaultNpmDependencyResolverConfig() NpmDependencyResolverConfig { + return NpmDependencyResolverConfig{ + IncludeDevDependencies: false, + IncludeTransitiveDependencies: true, + TransitiveDepth: 5, + FailFast: false, + MaxConcurrency: 10, + } +} + +type npmDependencyResolver struct { + registry packageregistry.Client + config NpmDependencyResolverConfig +} + +var _ PackageResolver = &npmDependencyResolver{} + +func NewNpmDependencyResolver(config NpmDependencyResolverConfig) (*npmDependencyResolver, error) { + client, err := packageregistry.NewNpmAdapter() + if err != nil { + return nil, fmt.Errorf("failed to create npm adapter: %w", err) + } + + return &npmDependencyResolver{ + registry: client, + config: config, + }, nil +} + +func (r *npmDependencyResolver) ResolveLatestVersion(ctx context.Context, + pkg *packagev1.Package) (*packagev1.PackageVersion, error) { + pd, err := r.registry.PackageDiscovery() + if err != nil { + return nil, fmt.Errorf("failed to get package discovery: %w", err) + } + + pkgInfo, err := pd.GetPackage(pkg.Name) + if err != nil { + return nil, fmt.Errorf("failed to get package: %w", err) + } + + log.Debugf("Resolved npm/%s to latest version %s", pkg.Name, pkgInfo.LatestVersion) + + return &packagev1.PackageVersion{ + Package: pkg, + Version: pkgInfo.LatestVersion, + }, nil +} + +func (r *npmDependencyResolver) ResolveDependencies(ctx context.Context, + packageVersion *packagev1.PackageVersion) ([]*packagev1.PackageVersion, error) { + resolver := newDependencyResolver(r.registry, dependencyResolverConfig{ + IncludeDevDependencies: r.config.IncludeDevDependencies, + IncludeTransitiveDependencies: r.config.IncludeTransitiveDependencies, + TransitiveDepth: r.config.TransitiveDepth, + FailFast: r.config.FailFast, + MaxConcurrency: r.config.MaxConcurrency, + }) + + return resolver.resolveDependencies(ctx, packageVersion) +} diff --git a/packagemanager/npm_resolver_test.go b/packagemanager/npm_resolver_test.go new file mode 100644 index 0000000..8dd98f3 --- /dev/null +++ b/packagemanager/npm_resolver_test.go @@ -0,0 +1,167 @@ +package packagemanager + +import ( + "context" + "testing" + + packagev1 "buf.build/gen/go/safedep/api/protocolbuffers/go/safedep/messages/package/v1" + "github.com/safedep/dry/semver" + "github.com/stretchr/testify/require" +) + +func TestNpmDependencyResolver_ResolveLatestVersion(t *testing.T) { + cases := []struct { + name string + pkg *packagev1.Package + assertFn func(t *testing.T, pv *packagev1.PackageVersion, err error) + }{ + { + name: "should resolve latest version for a package", + pkg: &packagev1.Package{ + Name: "react", + Ecosystem: packagev1.Ecosystem_ECOSYSTEM_NPM, + }, + assertFn: func(t *testing.T, pv *packagev1.PackageVersion, err error) { + require.NoError(t, err) + require.True(t, semver.IsAhead("19.0.0", pv.Version)) + }, + }, + { + name: "should return an error if the package is not found", + pkg: &packagev1.Package{ + Name: "nonexistent", + Ecosystem: packagev1.Ecosystem_ECOSYSTEM_NPM, + }, + assertFn: func(t *testing.T, pv *packagev1.PackageVersion, err error) { + require.Error(t, err) + require.Nil(t, pv) + }, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + resolver, err := NewNpmDependencyResolver(NewDefaultNpmDependencyResolverConfig()) + require.NoError(t, err) + + pv, err := resolver.ResolveLatestVersion(context.Background(), tc.pkg) + tc.assertFn(t, pv, err) + }) + } +} + +func TestNpmDependencyResolver_ResolveDependencies(t *testing.T) { + cases := []struct { + name string + pkg *packagev1.PackageVersion + includeTransitiveDependencies bool + transitiveDepth int + failFast bool + assertFn func(t *testing.T, dependencies []*packagev1.PackageVersion, err error) + }{ + { + name: "should resolve dependencies for a package when transitive dependencies are not included", + pkg: &packagev1.PackageVersion{ + Package: &packagev1.Package{ + Name: "react", + Ecosystem: packagev1.Ecosystem_ECOSYSTEM_NPM, + }, + Version: "18.2.0", + }, + includeTransitiveDependencies: false, + transitiveDepth: 1, + assertFn: func(t *testing.T, dependencies []*packagev1.PackageVersion, err error) { + require.NoError(t, err) + require.Equal(t, 1, len(dependencies)) + require.Equal(t, "loose-envify", dependencies[0].Package.Name) + require.Equal(t, "1.1.0", dependencies[0].Version) + }, + }, + { + name: "should resolve dependencies for a package up to a given depth", + pkg: &packagev1.PackageVersion{ + Package: &packagev1.Package{ + Name: "react", + Ecosystem: packagev1.Ecosystem_ECOSYSTEM_NPM, + }, + Version: "18.2.0", + }, + includeTransitiveDependencies: true, + transitiveDepth: 2, + assertFn: func(t *testing.T, dependencies []*packagev1.PackageVersion, err error) { + require.NoError(t, err) + require.Equal(t, 2, len(dependencies)) + + packageNames := []string{} + for _, dep := range dependencies { + packageNames = append(packageNames, dep.Package.Name) + } + + require.ElementsMatch(t, []string{ + "loose-envify", + "js-tokens", + }, packageNames) + }, + }, + { + name: "should resolve all dependencies for a package when transitive dependencies are included", + pkg: &packagev1.PackageVersion{ + Package: &packagev1.Package{ + Name: "express", + Ecosystem: packagev1.Ecosystem_ECOSYSTEM_NPM, + }, + Version: "4.18.2", + }, + includeTransitiveDependencies: true, + transitiveDepth: 5, + assertFn: func(t *testing.T, dependencies []*packagev1.PackageVersion, err error) { + require.NoError(t, err) + require.Greater(t, len(dependencies), 5, "Express should have more than 5 dependencies") + }, + }, + { + name: "should not fail when package is not found without fail fast", + pkg: &packagev1.PackageVersion{ + Package: &packagev1.Package{ + Name: "nonexistent", + Ecosystem: packagev1.Ecosystem_ECOSYSTEM_NPM, + }, + Version: "1.0.0", + }, + assertFn: func(t *testing.T, dependencies []*packagev1.PackageVersion, err error) { + require.NoError(t, err) + require.Empty(t, dependencies) + }, + }, + { + name: "should fail when package is not found with fail fast", + pkg: &packagev1.PackageVersion{ + Package: &packagev1.Package{ + Name: "nonexistent", + Ecosystem: packagev1.Ecosystem_ECOSYSTEM_NPM, + }, + Version: "1.0.0", + }, + failFast: true, + assertFn: func(t *testing.T, dependencies []*packagev1.PackageVersion, err error) { + require.Error(t, err) + require.Nil(t, dependencies) + }, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + config := NewDefaultNpmDependencyResolverConfig() + config.IncludeTransitiveDependencies = tc.includeTransitiveDependencies + config.TransitiveDepth = tc.transitiveDepth + config.FailFast = tc.failFast + + resolver, err := NewNpmDependencyResolver(config) + require.NoError(t, err) + + dependencies, err := resolver.ResolveDependencies(context.Background(), tc.pkg) + tc.assertFn(t, dependencies, err) + }) + } +} diff --git a/packagemanager/npm_test.go b/packagemanager/npm_test.go new file mode 100644 index 0000000..f807fc8 --- /dev/null +++ b/packagemanager/npm_test.go @@ -0,0 +1,123 @@ +package packagemanager + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNpmParseCommand(t *testing.T) { + cases := []struct { + name string + command string + assert func(t *testing.T, parsedCommand *ParsedCommand, err error) + }{ + { + name: "install a single package", + command: "npm install @types/node", + assert: func(t *testing.T, parsedCommand *ParsedCommand, err error) { + assert.NoError(t, err) + assert.Equal(t, 1, len(parsedCommand.InstallTargets)) + assert.Equal(t, "@types/node", parsedCommand.InstallTargets[0].PackageVersion.Package.Name) + assert.Empty(t, parsedCommand.InstallTargets[0].PackageVersion.Version) + }, + }, + { + name: "install a single package with specific version", + command: "npm install @types/node@1.2.3", + assert: func(t *testing.T, parsedCommand *ParsedCommand, err error) { + assert.NoError(t, err) + assert.Equal(t, 1, len(parsedCommand.InstallTargets)) + assert.Equal(t, "@types/node", parsedCommand.InstallTargets[0].PackageVersion.Package.Name) + assert.Equal(t, "1.2.3", parsedCommand.InstallTargets[0].PackageVersion.Version) + }, + }, + { + name: "install a development package", + command: "npm install --save-dev @types/node", + assert: func(t *testing.T, parsedCommand *ParsedCommand, err error) { + assert.NoError(t, err) + assert.Equal(t, 1, len(parsedCommand.InstallTargets)) + assert.Equal(t, "@types/node", parsedCommand.InstallTargets[0].PackageVersion.Package.Name) + }, + }, + { + name: "install a development package with short flag", + command: "npm i @types/node -D", + assert: func(t *testing.T, parsedCommand *ParsedCommand, err error) { + assert.NoError(t, err) + assert.Equal(t, 1, len(parsedCommand.InstallTargets)) + assert.Equal(t, "@types/node", parsedCommand.InstallTargets[0].PackageVersion.Package.Name) + }, + }, + { + name: "no install target", + command: "npm install", + assert: func(t *testing.T, parsedCommand *ParsedCommand, err error) { + assert.NoError(t, err) + assert.Equal(t, 0, len(parsedCommand.InstallTargets)) + }, + }, + { + name: "multiple package installations", + command: "npm install @types/node @types/react", + assert: func(t *testing.T, parsedCommand *ParsedCommand, err error) { + assert.NoError(t, err) + assert.Equal(t, 2, len(parsedCommand.InstallTargets)) + assert.Equal(t, "@types/node", parsedCommand.InstallTargets[0].PackageVersion.Package.Name) + assert.Equal(t, "@types/react", parsedCommand.InstallTargets[1].PackageVersion.Package.Name) + }, + }, + { + name: "not an installation command", + command: "npm update @types/node", + assert: func(t *testing.T, parsedCommand *ParsedCommand, err error) { + assert.NoError(t, err) + assert.NotNil(t, parsedCommand) + assert.Equal(t, 0, len(parsedCommand.InstallTargets)) + }, + }, + { + name: "skip intermediate flags", + command: "npm --x -y install @types/node", + assert: func(t *testing.T, parsedCommand *ParsedCommand, err error) { + assert.NoError(t, err) + assert.Equal(t, 1, len(parsedCommand.InstallTargets)) + assert.Equal(t, "@types/node", parsedCommand.InstallTargets[0].PackageVersion.Package.Name) + }, + }, + { + name: "multiple development packages", + command: "npm i @types/node @types/react -D", + assert: func(t *testing.T, parsedCommand *ParsedCommand, err error) { + assert.NoError(t, err) + assert.Equal(t, 2, len(parsedCommand.InstallTargets)) + assert.Equal(t, "@types/node", parsedCommand.InstallTargets[0].PackageVersion.Package.Name) + assert.Equal(t, "@types/react", parsedCommand.InstallTargets[1].PackageVersion.Package.Name) + }, + }, + { + name: "second package has a version", + command: "npm i express @types/node@1.2.3", + assert: func(t *testing.T, parsedCommand *ParsedCommand, err error) { + assert.NoError(t, err) + assert.Equal(t, 2, len(parsedCommand.InstallTargets)) + assert.Equal(t, "express", parsedCommand.InstallTargets[0].PackageVersion.Package.Name) + assert.Empty(t, parsedCommand.InstallTargets[0].PackageVersion.Version) + assert.Equal(t, "@types/node", parsedCommand.InstallTargets[1].PackageVersion.Package.Name) + assert.Equal(t, "1.2.3", parsedCommand.InstallTargets[1].PackageVersion.Version) + }, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + npm, err := NewNpmPackageManager(DefaultNpmPackageManagerConfig()) + assert.NoError(t, err) + + parsedCommand, err := npm.ParseCommand(strings.Split(tc.command, " ")) + tc.assert(t, parsedCommand, err) + }) + } +} diff --git a/packagemanager/packagemanager.go b/packagemanager/packagemanager.go new file mode 100644 index 0000000..5ac1d9d --- /dev/null +++ b/packagemanager/packagemanager.go @@ -0,0 +1,54 @@ +package packagemanager + +import ( + "context" + + packagev1 "buf.build/gen/go/safedep/api/protocolbuffers/go/safedep/messages/package/v1" +) + +type Command struct { + Exe string + Args []string +} + +type PackageInstallTarget struct { + PackageVersion *packagev1.PackageVersion +} + +func (pit *PackageInstallTarget) HasVersion() bool { + return pit.PackageVersion != nil && pit.PackageVersion.GetVersion() != "" +} + +type ParsedCommand struct { + // Original command + Command Command + + // Parsed install target if this is an install command + InstallTargets []*PackageInstallTarget +} + +func (pc *ParsedCommand) HasInstallTarget() bool { + return len(pc.InstallTargets) > 0 +} + +// PackageManager is the contract for implementing a package manager +type PackageManager interface { + // Name of the package manager implementation + Name() string + + // ParseCommand parses the command and returns a parsed command + // specific to the package manager implementation + ParseCommand(args []string) (*ParsedCommand, error) +} + +// PackageResolver is the contract for resolving package info +type PackageResolver interface { + // ResolveLatestVersion resolves the latest version for a given package + ResolveLatestVersion(context.Context, *packagev1.Package) (*packagev1.PackageVersion, error) + + // ResolveDependencies resolves the dependencies for a given package version + // It returns a flattened list of all the dependencies based on implementation + // specific config. The version resolution is based on minimum version selection + // for a given version range. + ResolveDependencies(context.Context, *packagev1.PackageVersion) ([]*packagev1.PackageVersion, error) +} diff --git a/pkg/analyser/analysis.go b/pkg/analyser/analysis.go deleted file mode 100644 index 2a71b54..0000000 --- a/pkg/analyser/analysis.go +++ /dev/null @@ -1,90 +0,0 @@ -package analyser - -import ( - "context" - "fmt" - "sync" - - "buf.build/gen/go/safedep/api/grpc/go/safedep/services/malysis/v1/malysisv1grpc" - malysisv1pb "buf.build/gen/go/safedep/api/protocolbuffers/go/safedep/messages/malysis/v1" - packagev1 "buf.build/gen/go/safedep/api/protocolbuffers/go/safedep/messages/package/v1" - malysisv1 "buf.build/gen/go/safedep/api/protocolbuffers/go/safedep/services/malysis/v1" - "github.com/safedep/dry/log" - "github.com/safedep/pmg/internal/ui" - "github.com/safedep/pmg/pkg/models" - vetUtils "github.com/safedep/vet/pkg/common/utils" -) - -type PackageAnalyser struct { - MaliciousPkgs map[string]string - Client malysisv1grpc.MalwareAnalysisServiceClient - Ctx context.Context - MaliciousPkgsMutex sync.Mutex - ProgressTracker ui.ProgressTracker - Ecosystem packagev1.Ecosystem -} - -func New(client malysisv1grpc.MalwareAnalysisServiceClient, ctx context.Context, ecosystem packagev1.Ecosystem) *PackageAnalyser { - return &PackageAnalyser{ - MaliciousPkgs: make(map[string]string), - Client: client, - Ctx: ctx, - MaliciousPkgsMutex: sync.Mutex{}, - Ecosystem: ecosystem, - } -} - -func (ap *PackageAnalyser) Handler() vetUtils.WorkQueueFn[models.Package] { - return func(q *vetUtils.WorkQueue[models.Package], item models.Package) error { - reportResp, err := QueryPackageAnalysis(ap.Ctx, ap.Client, - ap.Ecosystem, item.Name, item.Version) - if err != nil { - log.Debugf("Failed to analyze %s@%s: %v", item.Name, item.Version, err) - return nil - } - - report := reportResp.GetReport() - if report == nil { - log.Debugf("Empty report received for %s", item.Name) - return nil - } - - inference := report.GetInference() - if inference == nil { - log.Debugf("No inference data for %s", item.Name) - return nil - } - - log.Infof("Inference for %s: isMalware=%v", item.Name, inference.GetIsMalware()) - - if inference.GetIsMalware() { - ap.MaliciousPkgsMutex.Lock() - ap.MaliciousPkgs[fmt.Sprintf("%s@%s", item.Name, item.Version)] = inference.GetSummary() - ap.MaliciousPkgsMutex.Unlock() - } - - ui.IncrementProgress(ap.ProgressTracker, 1) - return nil - } -} - -func QueryPackageAnalysis(ctx context.Context, client malysisv1grpc.MalwareAnalysisServiceClient, ecosystem packagev1.Ecosystem, name string, - version string) (*malysisv1.QueryPackageAnalysisResponse, error) { - resp, err := client.QueryPackageAnalysis(ctx, &malysisv1.QueryPackageAnalysisRequest{ - Target: &malysisv1pb.PackageAnalysisTarget{ - PackageVersion: &packagev1.PackageVersion{ - Package: &packagev1.Package{ - Ecosystem: ecosystem, - Name: name, - }, - Version: version, - }, - }, - }) - - if err != nil { - return nil, fmt.Errorf("failed to analyze %s@%s: %w", name, version, err) - } - - return resp, nil -} diff --git a/pkg/analyser/client.go b/pkg/analyser/client.go deleted file mode 100644 index dab087d..0000000 --- a/pkg/analyser/client.go +++ /dev/null @@ -1,16 +0,0 @@ -package analyser - -import ( - "fmt" - - "buf.build/gen/go/safedep/api/grpc/go/safedep/services/malysis/v1/malysisv1grpc" - "github.com/safedep/pmg/pkg/common" -) - -func GetMalwareAnalysisClient() (malysisv1grpc.MalwareAnalysisServiceClient, error) { - cc, err := common.NewCloudClientConnection() - if err != nil { - return nil, fmt.Errorf("failed to create gRPC client: %v", err) - } - return malysisv1grpc.NewMalwareAnalysisServiceClient(cc), nil -} diff --git a/pkg/common/extractor.go b/pkg/common/extractor.go deleted file mode 100644 index f24e298..0000000 --- a/pkg/common/extractor.go +++ /dev/null @@ -1,87 +0,0 @@ -package common - -import ( - "fmt" - "os" - "path/filepath" - - "github.com/safedep/dry/crypto" - "github.com/safedep/pmg/pkg/common/utils" - "github.com/safedep/pmg/pkg/models" -) - -// ExtractorOptions holds configuration for running an extractor script -type ExtractorOptions struct { - ScriptContent string // The script content - ScriptType string // File extension like "js", "py", etc. - Interpreter string // What interpreter to use (e.g., "node", "python") - PackageName string // Name of the package to analyze - Args []string // Additional arguments to pass to the script - Env map[string]string // Environment variables to pass to the script -} - -func FlattenDependencyTree(node *models.DependencyNode) []string { - result := make([]string, 0) - seen := make(map[string]bool) - - var flatten func(*models.DependencyNode) - flatten = func(n *models.DependencyNode) { - key := fmt.Sprintf("%s@%s", n.Name, n.Version) - if seen[key] { - return - } - seen[key] = true - - result = append(result, fmt.Sprintf("%s@%s", n.Name, n.Version)) - - for _, dep := range n.Dependencies { - flatten(dep) - } - } - - flatten(node) - return result -} - -// RunExtractor extracts an embedded script to a temp file and executes it -func RunPkgExtractor(opts ExtractorOptions) (string, error) { - interpreterPath, err := utils.GetExecutablePath(opts.Interpreter) - if err != nil { - return "", err - } - - // Create a temporary file for the embedded script - scriptFile, err := os.CreateTemp("", fmt.Sprintf("registry-extractor-*.%s", opts.ScriptType)) - if err != nil { - return "", fmt.Errorf("failed to create temporary script file: %s", err.Error()) - } - defer os.Remove(scriptFile.Name()) - - // Write the embedded script to the temporary file - if _, err = scriptFile.WriteString(opts.ScriptContent); err != nil { - return "", fmt.Errorf("failed to write script to temporary file: %s", err.Error()) - } - - if err = scriptFile.Close(); err != nil { - return "", fmt.Errorf("failed to close temporary script file: %s", err.Error()) - } - - // Create a file with random name which will contain the output - randomFileName, err := crypto.RandomString(12, "abcdefghijklmnopqrstuvwxyz0123456789") - if err != nil { - return "", fmt.Errorf("failed to generate random string: %s", err.Error()) - } - outputFile := filepath.Join(os.TempDir(), randomFileName+".txt") - - // Build the command with all arguments - cmdArgs := append([]string{scriptFile.Name(), opts.PackageName, outputFile}, opts.Args...) - var env []string - for key, value := range opts.Env { - env = append(env, fmt.Sprintf("%s=%s", key, value)) - } - if err = utils.ExecCmd(interpreterPath, cmdArgs, env); err != nil { - return "", err - } - - return outputFile, nil -} diff --git a/pkg/common/grpc.go b/pkg/common/grpc.go deleted file mode 100644 index 18760e8..0000000 --- a/pkg/common/grpc.go +++ /dev/null @@ -1,25 +0,0 @@ -package common - -import ( - "fmt" - "net/http" - - drygrpc "github.com/safedep/dry/adapters/grpc" - "google.golang.org/grpc" -) - -func NewCloudClientConnection() (*grpc.ClientConn, error) { - cc, err := newGrpcClient(http.Header{}, "", "pmg-pkg-scan", "community-api.safedep.io", "443") - if err != nil { - return nil, fmt.Errorf("failed to create gRPC client: %v", err) - } - return cc, nil -} - -func newGrpcClient(headers http.Header, token, clientName, host, port string) (*grpc.ClientConn, error) { - cc, err := drygrpc.GrpcClient(clientName, host, port, token, headers, []grpc.DialOption{}) - if err != nil { - return nil, err - } - return cc, nil -} diff --git a/pkg/common/utils/command.go b/pkg/common/utils/command.go deleted file mode 100644 index 8d59404..0000000 --- a/pkg/common/utils/command.go +++ /dev/null @@ -1,18 +0,0 @@ -package utils - -import ( - "os" - "os/exec" -) - -func ExecCmd(name string, args, env []string) error { - cmd := exec.Command(name, args...) - cmd.Env = append(os.Environ(), env...) - - // Connect to standard streams - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - cmd.Stdin = os.Stdin - - return cmd.Run() -} diff --git a/pkg/common/utils/config.go b/pkg/common/utils/config.go deleted file mode 100644 index f09e6dc..0000000 --- a/pkg/common/utils/config.go +++ /dev/null @@ -1,51 +0,0 @@ -package utils - -import ( - "fmt" - "os" - "strings" -) - -func ApiKey() string { - return os.Getenv("SAFEDEP_API_KEY") -} - -func TenantDomain() string { - return os.Getenv("SAFEDEP_TENANT_ID") -} - -func NpmAuthToken() string { - return os.Getenv("NPM_AUTH_TOKEN") -} - -func ValidateEnvVars() error { - apiKey := ApiKey() - tenantId := TenantDomain() - var missingVars []string - - if apiKey == "" { - missingVars = append(missingVars, "SAFEDEP_API_KEY") - } - if tenantId == "" { - missingVars = append(missingVars, "SAFEDEP_TENANT_ID") - } - - if len(missingVars) > 0 { - return fmt.Errorf(` -SafeDep configuration incomplete - -Missing environment variables: - %s - -To enable package scanning: - 1. Export these variables in your terminal: - export %s=your_api_key - export %s=your_tenant_id - 2. Or add them to your shell profile file - -For more information, visit: https://docs.safedep.io/cloud/quickstart -`, strings.Join(missingVars, "\n "), missingVars[0], missingVars[len(missingVars)-1]) - } - - return nil -} diff --git a/pkg/common/utils/interpreter.go b/pkg/common/utils/interpreter.go deleted file mode 100644 index b8ffcc5..0000000 --- a/pkg/common/utils/interpreter.go +++ /dev/null @@ -1,14 +0,0 @@ -package utils - -import ( - "fmt" - "os/exec" -) - -func GetExecutablePath(name string) (string, error) { - path, err := exec.LookPath(name) - if err != nil { - return "", fmt.Errorf("interpreter '%s' not found in PATH: %s", name, err.Error()) - } - return path, nil -} diff --git a/pkg/common/utils/markdown.go b/pkg/common/utils/markdown.go deleted file mode 100644 index fd2bd2f..0000000 --- a/pkg/common/utils/markdown.go +++ /dev/null @@ -1,54 +0,0 @@ -package utils - -import ( - "regexp" - "strings" -) - -var ( - headerBulletRegex = regexp.MustCompile(`(?m)^(#{1,6}\s+|[-*]\s{1,}|\d+\.\s+|>\s+)`) - inlineCodeRegex = regexp.MustCompile("`{1,3}([^`]*)`{1,3}") - horizontalRuleRegex = regexp.MustCompile(`(?m)^\s*(-{3,}|\*{3,}|\_{3,})\s*$`) - boldItalicRegex = regexp.MustCompile(`(?:\*\*\*|___)(.*?)(?:\*\*\*|___)`) - boldRegex = regexp.MustCompile(`(?:\*\*|__)(.*?)(?:\*\*|__)`) - italicRegex = regexp.MustCompile(`(?:\*|_)(.*?)(?:\*|_)`) - strikethroughRegex = regexp.MustCompile(`~~([^~]+)~~`) - inlineLinkRegex = regexp.MustCompile(`\[([^\]]+)\]\((\S+?)\)`) - imageRegex = regexp.MustCompile(`!\[([^\]]*)\]\((\S+?)\)`) - extraSpacesRegex = regexp.MustCompile(`\s+`) -) - -func removeMarkdown(text string) string { - // Remove bold italic (***bolditalic*** or ___bolditalic___) - text = boldItalicRegex.ReplaceAllString(text, "$1") - - // Remove bold (**bold** or __bold__) - text = boldRegex.ReplaceAllString(text, "$1") - - // Remove italic (*italic* or _italic_) - text = italicRegex.ReplaceAllString(text, "$1") - - // Remove strikethrough (~~text~~) - text = strikethroughRegex.ReplaceAllString(text, "$1") - - // Remove inline code (`code`) - text = inlineCodeRegex.ReplaceAllString(text, "$1") - - // Remove links [text](url) - text = inlineLinkRegex.ReplaceAllString(text, "$1") - - // Remove images ![alt](url) - text = imageRegex.ReplaceAllString(text, "$1") - - // Remove horizontal rules - text = horizontalRuleRegex.ReplaceAllString(text, "") - - // Remove headers, blockquotes, bullets (e.g., ### Heading, > Quote, - Item) - text = headerBulletRegex.ReplaceAllString(text, "") - - // Normalize extra spaces - text = extraSpacesRegex.ReplaceAllString(text, " ") - - // Trim leading/trailing whitespace - return strings.TrimSpace(text) -} diff --git a/pkg/common/utils/npm.go b/pkg/common/utils/npm.go deleted file mode 100644 index a843a85..0000000 --- a/pkg/common/utils/npm.go +++ /dev/null @@ -1,64 +0,0 @@ -package utils - -import ( - "fmt" - "strings" -) - -// ParseNpmInstallArgs parses npm install command arguments and returns -// separated flags and packages. It expects args to include the full command -// including "npm" and "install" at the start -func ParseNpmInstallArgs(args []string) ([]string, []string) { - var flags []string - var packages []string - - for _, arg := range args { - if strings.HasPrefix(arg, "-") { - flags = append(flags, arg) - } else { - packages = append(packages, arg) - } - } - return flags, packages -} - -func CleanVersion(version string) string { - version = strings.TrimPrefix(version, "^") - version = strings.TrimPrefix(version, "~") - if version == "*" { - return "latest" - } - return version -} - -func ParsePackageInfo(input string) (packageName, version string, err error) { - if input == "" { - return "", "", fmt.Errorf("package info cannot be empty") - } - input = strings.TrimSpace(input) - - if strings.HasPrefix(input, "@") { - lastAtIndex := strings.LastIndex(input, "@") - if lastAtIndex > 0 { - packageName = strings.TrimSpace(input[:lastAtIndex]) - version = strings.TrimSpace(input[lastAtIndex+1:]) - return packageName, version, nil - } - // If no version specifier, return the whole input as package name - return strings.TrimSpace(input), "", nil - } - - pkg := strings.Split(input, "@") - if len(pkg) == 2 { - packageName = strings.TrimSpace(pkg[0]) - version = strings.TrimSpace(pkg[1]) - return packageName, version, nil - } - - if len(pkg) == 1 { - packageName = strings.TrimSpace(pkg[0]) - return packageName, "", nil - } - - return "", "", fmt.Errorf("invalid format: expected 'package' OR 'package@version', got '%s'", input) -} diff --git a/pkg/common/utils/security_confirmation.go b/pkg/common/utils/security_confirmation.go deleted file mode 100644 index 2cb1bd7..0000000 --- a/pkg/common/utils/security_confirmation.go +++ /dev/null @@ -1,35 +0,0 @@ -package utils - -import ( - "bufio" - "fmt" - "os" - "strings" - - "github.com/safedep/dry/log" -) - -func ConfirmInstallation(maliciousPkgs map[string]string) bool { - - fmt.Printf("\n%s\n", colors.Red("⚠️ WARNING: %d potentially malicious packages detected!", len(maliciousPkgs))) - fmt.Println(colors.Yellow("The following packages have been flagged:")) - - for name, reason := range maliciousPkgs { - fmt.Printf("%s %s: %s\n", - colors.Cyan("•"), // bullet point - colors.Yellow(name), - removeMarkdown(reason), - ) - } - - fmt.Print("\n", colors.Green("Do you want to continue with installation? (y/N): ")) - reader := bufio.NewReader(os.Stdin) - response, err := reader.ReadString('\n') - if err != nil { - log.Errorf("Failed to read user input: %v", err) - return false - } - - response = strings.ToLower(strings.TrimSpace(response)) - return response == "y" || response == "yes" -} diff --git a/pkg/common/utils/utils.go b/pkg/common/utils/utils.go deleted file mode 100644 index 5a7807c..0000000 --- a/pkg/common/utils/utils.go +++ /dev/null @@ -1,21 +0,0 @@ -package utils - -func IsInstallCommand(pkgManager, cmd string) bool { - validActions := map[string]map[string]bool{ - "npm": { - "install": true, - "i": true, - "add": true, - }, - "pnpm": { - "add": true, - "install": true, - "i": true, - }, - } - - if actions, exists := validActions[pkgManager]; exists { - return actions[cmd] - } - return false -} diff --git a/pkg/common/utils/utils_test.go b/pkg/common/utils/utils_test.go deleted file mode 100644 index a9fd538..0000000 --- a/pkg/common/utils/utils_test.go +++ /dev/null @@ -1,212 +0,0 @@ -package utils - -import ( - "strings" - "testing" -) - -func TestCleanVersion(t *testing.T) { - tests := []struct { - name string - version string - expected string - }{ - { - name: "caret version", - version: "^1.2.3", - expected: "1.2.3", - }, - { - name: "tilde version", - version: "~1.2.3", - expected: "1.2.3", - }, - { - name: "exact version", - version: "1.2.3", - expected: "1.2.3", - }, - { - name: "asterisk version", - version: "*", - expected: "latest", - }, - { - name: "empty version", - version: "", - expected: "", - }, - { - name: "both caret and tilde", - version: "^~1.2.3", - expected: "1.2.3", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result := CleanVersion(tt.version) - if result != tt.expected { - t.Errorf("CleanVersion(%q) = %q, want %q", tt.version, result, tt.expected) - } - }) - } -} - -func TestParsePackageInfo(t *testing.T) { - tests := []struct { - name string - input string - wantPackage string - wantVersion string - wantErr bool - errorContains string - }{ - { - name: "simple package", - input: "express", - wantPackage: "express", - wantVersion: "", - wantErr: false, - }, - { - name: "package with version", - input: "express@4.17.1", - wantPackage: "express", - wantVersion: "4.17.1", - wantErr: false, - }, - { - name: "scoped package", - input: "@angular/core", - wantPackage: "@angular/core", - wantVersion: "", - wantErr: false, - }, - { - name: "scoped package with version", - input: "@angular/core@12.0.0", - wantPackage: "@angular/core", - wantVersion: "12.0.0", - wantErr: false, - }, - { - name: "package with caret version", - input: "react@^17.0.2", - wantPackage: "react", - wantVersion: "^17.0.2", - wantErr: false, - }, - { - name: "package with tilde version", - input: "lodash@~4.17.21", - wantPackage: "lodash", - wantVersion: "~4.17.21", - wantErr: false, - }, - { - name: "empty input", - input: "", - wantPackage: "", - wantVersion: "", - wantErr: true, - errorContains: "package info cannot be empty", - }, - { - name: "invalid format with multiple @", - input: "pkg@1.0.0@2.0.0", - wantPackage: "", - wantVersion: "", - wantErr: true, - errorContains: "invalid format", - }, - { - name: "package with spaces", - input: " express@4.17.1 ", - wantPackage: "express", - wantVersion: "4.17.1", - wantErr: false, - }, - { - name: "scoped package with spaces", - input: " @types/node@14.14.31 ", - wantPackage: "@types/node", - wantVersion: "14.14.31", - wantErr: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - packageName, version, err := ParsePackageInfo(tt.input) - - // Check error - if tt.wantErr { - if err == nil { - t.Errorf("ParsePackageInfo(%q) expected error, got nil", tt.input) - return - } - if tt.errorContains != "" && !strings.Contains(err.Error(), tt.errorContains) { - t.Errorf("ParsePackageInfo(%q) error = %v, want error containing %q", tt.input, err, tt.errorContains) - } - return - } - if err != nil { - t.Errorf("ParsePackageInfo(%q) unexpected error: %v", tt.input, err) - return - } - - // Check package name - if packageName != tt.wantPackage { - t.Errorf("ParsePackageInfo(%q) package = %q, want %q", tt.input, packageName, tt.wantPackage) - } - - // Check version - if version != tt.wantVersion { - t.Errorf("ParsePackageInfo(%q) version = %q, want %q", tt.input, version, tt.wantVersion) - } - }) - } -} - -func TestRemoveMarkdown(t *testing.T) { - tests := []struct { - input string - expected string - }{ - // Bold - {"This is **bold** text", "This is bold text"}, - {"This is __bold__ text", "This is bold text"}, - - // Italic - {"This is *italic* text", "This is italic text"}, - {"This is _italic_ text", "This is italic text"}, - - // Code - {"This is `code` inline", "This is code inline"}, - - // Link - {"Click [here](https://example.com)", "Click here"}, - - // Headings - {"# Heading 1", "Heading 1"}, - {"### Subheading", "Subheading"}, - - // Combined formatting - {"__*bold and italic*__", "bold and italic"}, - {"This is **bold** and `code`", "This is bold and code"}, - - // No markdown - {"Just plain text", "Just plain text"}, - - // Complex mixed - {"### Title\nSome **bold** text and a [link](http://url.com).", "Title Some bold text and a link."}, - } - - for _, tt := range tests { - result := removeMarkdown(tt.input) - if result != tt.expected { - t.Errorf("removeMarkdown(%q) = %q; want %q", tt.input, result, tt.expected) - } - } -} diff --git a/pkg/models/models.go b/pkg/models/models.go deleted file mode 100644 index ed51225..0000000 --- a/pkg/models/models.go +++ /dev/null @@ -1,26 +0,0 @@ -package models - -import ( - "fmt" -) - -type Package struct { - Name string - Version string -} - -func (p Package) Id() string { - return fmt.Sprintf("%s@%s", p.Name, p.Version) -} - -type PackageInfo struct { - Name string `json:"name"` - Version string `json:"version"` - Dependencies map[string]string `json:"dependencies"` -} - -type DependencyNode struct { - Name string - Version string - Dependencies map[string]*DependencyNode -} diff --git a/pkg/registry/client.go b/pkg/registry/client.go deleted file mode 100644 index 458b75d..0000000 --- a/pkg/registry/client.go +++ /dev/null @@ -1,110 +0,0 @@ -package registry - -import ( - "context" - "encoding/json" - "fmt" - "io" - "net/http" - "time" - - "github.com/safedep/pmg/pkg/models" -) - -// RegistryClient defines the interface for making requests to a registry -type RegistryClient interface { - // FetchPackageInfo fetches metadata for a specific package version - FetchPackageInfo(ctx context.Context, pkg models.Package) (*models.PackageInfo, error) - // GetLatestVersion fetches the latest version for a package - GetLatestVersion(ctx context.Context, packageName string) (string, error) -} - -// HttpRegistryClient is a basic HTTP client for registry APIs -type HttpRegistryClient struct { - httpClient *http.Client - urlFormat string - parser func([]byte) (*models.PackageInfo, error) -} - -// NewHttpRegistryClient creates a new HTTP registry client -func NewHttpRegistryClient( - timeout time.Duration, - urlFormat string, - parser func([]byte) (*models.PackageInfo, error), -) *HttpRegistryClient { - return &HttpRegistryClient{ - httpClient: &http.Client{Timeout: timeout}, - urlFormat: urlFormat, - parser: parser, - } -} - -// FetchPackageInfo fetches package metadata from the registry -func (c *HttpRegistryClient) FetchPackageInfo(ctx context.Context, pkg models.Package) (*models.PackageInfo, error) { - url := fmt.Sprintf(c.urlFormat, pkg.Name, pkg.Version) - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) - if err != nil { - return nil, fmt.Errorf("creating request: %w", err) - } - - resp, err := c.httpClient.Do(req) - if err != nil { - return nil, fmt.Errorf("making request: %w", err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("registry returned status: %s", resp.Status) - } - - body, err := io.ReadAll(resp.Body) - if err != nil { - return nil, fmt.Errorf("reading response body: %w", err) - } - - return c.parser(body) -} - -// GetLatestVersion fetches the latest version for an NPM package -func (c *HttpRegistryClient) GetLatestVersion(ctx context.Context, packageName string) (string, error) { - // For NPM, we can get latest version by querying the base package URL - url := fmt.Sprintf("https://registry.npmjs.org/%s", packageName) - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) - if err != nil { - return "", fmt.Errorf("creating request: %w", err) - } - - resp, err := c.httpClient.Do(req) - if err != nil { - return "", fmt.Errorf("making request: %w", err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return "", fmt.Errorf("registry returned status: %s", resp.Status) - } - - body, err := io.ReadAll(resp.Body) - if err != nil { - return "", fmt.Errorf("reading response body: %w", err) - } - - // Parse the response to get the latest version - var pkgData struct { - DistTags struct { - Latest string `json:"latest"` - } `json:"dist-tags"` - } - - if err := json.Unmarshal(body, &pkgData); err != nil { - return "", fmt.Errorf("parsing package info: %w", err) - } - - if pkgData.DistTags.Latest == "" { - return "", fmt.Errorf("no latest version found for package %s", packageName) - } - - return pkgData.DistTags.Latest, nil -} diff --git a/pkg/registry/factory.go b/pkg/registry/factory.go deleted file mode 100644 index 2125dab..0000000 --- a/pkg/registry/factory.go +++ /dev/null @@ -1,38 +0,0 @@ -package registry - -import ( - "fmt" - "time" -) - -// RegistryType represents different package registries -type RegistryType string - -const ( - RegistryNPM RegistryType = "npm" - RegistryPNPM RegistryType = "pnpm" - RegistryPyPI RegistryType = "pypi" - RegistryGo RegistryType = "go" -) - -// FetcherFactory creates appropriate fetchers based on registry type -type FetcherFactory struct { - timeout time.Duration -} - -// NewFetcherFactory creates a new factory for registry fetchers -func NewFetcherFactory(timeout time.Duration) *FetcherFactory { - return &FetcherFactory{ - timeout: timeout, - } -} - -// CreateFetcher returns a fetcher for the specified registry type -func (ff *FetcherFactory) CreateFetcher(registryType RegistryType) (Fetcher, error) { - switch registryType { - case RegistryNPM, RegistryPNPM: - return NewNpmFetcher(ff.timeout), nil - default: - return nil, fmt.Errorf("unsupported registry type: %s", registryType) - } -} diff --git a/pkg/registry/fetcher.go b/pkg/registry/fetcher.go deleted file mode 100644 index a0e679b..0000000 --- a/pkg/registry/fetcher.go +++ /dev/null @@ -1,84 +0,0 @@ -// Package registry provides interfaces and implementations for fetching dependencies -// from various package registries (npm, pypi, go, etc.) -package registry - -import ( - "context" - "fmt" - "sync" - "sync/atomic" - - "github.com/safedep/pmg/internal/ui" - "github.com/safedep/pmg/pkg/models" -) - -// Fetcher defines the interface for registry dependency fetchers -type Fetcher interface { - // GetDependencyTree returns the complete dependency tree for a package - GetDependencyTree(ctx context.Context, pkg models.Package) (*models.DependencyNode, error) - - // GetFlattenedDependencies returns a list of all dependencies as package@version strings - GetFlattenedDependencies(ctx context.Context, packageName, version string) ([]string, error) -} - -// BaseFetcher implements common functionality for all registry fetchers -type BaseFetcher struct { - visitedMu sync.RWMutex - visited map[string]bool - client RegistryClient - progressTracker ui.ProgressTracker - fetchedDeps int32 -} - -// NewBaseFetcher creates a new BaseFetcher with the specified registry client -func NewBaseFetcher(client RegistryClient) *BaseFetcher { - return &BaseFetcher{ - visited: make(map[string]bool), - client: client, - } -} - -func (bf *BaseFetcher) SetProgressTracker(tracker ui.ProgressTracker) { - bf.progressTracker = tracker - atomic.StoreInt32(&bf.fetchedDeps, 0) -} - -// isVisited checks if a package has already been visited -func (bf *BaseFetcher) isVisited(key string) bool { - bf.visitedMu.RLock() - defer bf.visitedMu.RUnlock() - return bf.visited[key] -} - -// markVisited marks a package as visited -func (bf *BaseFetcher) markVisited(key string) { - bf.visitedMu.Lock() - defer bf.visitedMu.Unlock() - bf.visited[key] = true -} - -// cacheKey generates a unique key for a package -func cacheKey(pkg models.Package) string { - return fmt.Sprintf("%s@%s", pkg.Name, pkg.Version) -} - -// flattenDependencyTree recursively converts a dependency tree to a flat list of strings -func flattenDependencyTree(node *models.DependencyNode, result *[]string) { - if node == nil { - return - } - - depString := fmt.Sprintf("%s@%s", node.Name, node.Version) - *result = append(*result, depString) - - for _, dep := range node.Dependencies { - flattenDependencyTree(dep, result) - } -} - -// resetVisited resets the visited packages map -func (bf *BaseFetcher) resetVisited() { - bf.visitedMu.Lock() - defer bf.visitedMu.Unlock() - bf.visited = make(map[string]bool) -} diff --git a/pkg/registry/npm_fetcher.go b/pkg/registry/npm_fetcher.go deleted file mode 100644 index 03f1d41..0000000 --- a/pkg/registry/npm_fetcher.go +++ /dev/null @@ -1,175 +0,0 @@ -package registry - -import ( - "context" - "encoding/json" - "fmt" - "sync" - "sync/atomic" - "time" - - "github.com/safedep/dry/log" - "github.com/safedep/pmg/internal/ui" - "github.com/safedep/pmg/pkg/common/utils" - "github.com/safedep/pmg/pkg/models" -) - -// NpmFetcher fetches dependencies from NPM registry -type NpmFetcher struct { - *BaseFetcher -} - -func (nf *NpmFetcher) incrementProgress() { - if nf.progressTracker != nil { - atomic.AddInt32(&nf.fetchedDeps, 1) - // Update progress message to show number of packages fetched - ui.SetPinnedMessageOnProgressWriter(fmt.Sprintf("Fetched %d packages", atomic.LoadInt32(&nf.fetchedDeps))) - } -} - -// NewNpmFetcher creates a new NPM registry fetcher -func NewNpmFetcher(timeout time.Duration) *NpmFetcher { - client := NewHttpRegistryClient( - timeout, - "https://registry.npmjs.org/%s/%s", - parseNpmPackageInfo, - ) - return &NpmFetcher{ - BaseFetcher: NewBaseFetcher(client), - } -} - -// parseNpmPackageInfo parses NPM package information from JSON -func parseNpmPackageInfo(data []byte) (*models.PackageInfo, error) { - var packageInfo models.PackageInfo - if err := json.Unmarshal(data, &packageInfo); err != nil { - return nil, fmt.Errorf("parsing package info: %w", err) - } - return &packageInfo, nil -} - -// GetDependencyTree fetches the complete dependency tree for an NPM package -func (nf *NpmFetcher) GetDependencyTree(ctx context.Context, pkg models.Package) (*models.DependencyNode, error) { - return nf.fetchDependenciesConcurrent(ctx, pkg) -} - -// GetFlattenedDependencies returns a flat list of all dependencies as strings -func (nf *NpmFetcher) GetFlattenedDependencies(ctx context.Context, packageName, version string) ([]string, error) { - // Reset the visited map to ensure we get a complete tree - nf.resetVisited() - - // Get the complete dependency tree - tree, err := nf.GetDependencyTree(ctx, models.Package{ - Name: packageName, - Version: version, - }) - - if err != nil { - return nil, fmt.Errorf("failed to fetch dependency tree: %w", err) - } - - // Convert tree to flat list - var dependencies []string - flattenDependencyTree(tree, &dependencies) - - // Remove duplicates if needed - uniqueDeps := make(map[string]bool) - var result []string - - for _, dep := range dependencies { - if !uniqueDeps[dep] { - uniqueDeps[dep] = true - result = append(result, dep) - } - } - - return result, nil -} - -// fetchDependenciesConcurrent recursively fetches package dependencies concurrently -func (nf *NpmFetcher) fetchDependenciesConcurrent(ctx context.Context, pkg models.Package) (*models.DependencyNode, error) { - key := cacheKey(pkg) - if nf.isVisited(key) { - return &models.DependencyNode{ - Name: pkg.Name, - Version: pkg.Version, - }, nil - } - nf.markVisited(key) - - packageInfo, err := nf.client.FetchPackageInfo(ctx, pkg) - if err != nil { - return nil, fmt.Errorf("failed to fetch package info for %s: %w", pkg.Name, err) - } - - nf.incrementProgress() - - dependencies := packageInfo.Dependencies - node := &models.DependencyNode{ - Name: pkg.Name, - Version: pkg.Version, - Dependencies: make(map[string]*models.DependencyNode), - } - - if len(dependencies) == 0 { - return node, nil - } - - // Process dependencies concurrently - type result struct { - name string - node *models.DependencyNode - err error - } - - var wg sync.WaitGroup - resultChan := make(chan result, len(dependencies)) - - for depName, depVersion := range dependencies { - // Check if context is canceled - select { - case <-ctx.Done(): - return nil, ctx.Err() - default: - // Continue processing - } - - wg.Add(1) - go func(name, version string) { - defer wg.Done() - version = utils.CleanVersion(version) - depNode, err := nf.fetchDependenciesConcurrent(ctx, models.Package{Name: name, Version: version}) - resultChan <- result{name, depNode, err} - }(depName, depVersion) - } - - // Wait for all goroutines to complete in a separate goroutine - go func() { - wg.Wait() - close(resultChan) - }() - - // Collect results - for res := range resultChan { - if res.err != nil { - log.Warnf("Failed to fetch dependency %s: %v", res.name, res.err) - continue - } - node.Dependencies[res.name] = res.node - } - - return node, nil -} - -func (nf *NpmFetcher) ResolveVersion(ctx context.Context, packageName, version string) (string, error) { - if version != "" { - return version, nil - } - - latestVersion, err := nf.client.GetLatestVersion(ctx, packageName) - if err != nil { - return "", fmt.Errorf("failed to get latest version for %s: %w", packageName, err) - } - - return latestVersion, nil -} diff --git a/pkg/wrapper/errors.go b/pkg/wrapper/errors.go deleted file mode 100644 index ef414aa..0000000 --- a/pkg/wrapper/errors.go +++ /dev/null @@ -1,11 +0,0 @@ -package wrapper - -import "errors" - -const ( - ErrPackageInstallationDeny = "PACKAGE_INSTALLATION_DENIED" -) - -var ( - ErrPackageInstall = errors.New(ErrPackageInstallationDeny) -) diff --git a/pkg/wrapper/npm_base.go b/pkg/wrapper/npm_base.go deleted file mode 100644 index 1334029..0000000 --- a/pkg/wrapper/npm_base.go +++ /dev/null @@ -1,182 +0,0 @@ -package wrapper - -import ( - "context" - "errors" - "fmt" - "time" - - packagev1 "buf.build/gen/go/safedep/api/protocolbuffers/go/safedep/messages/package/v1" - "github.com/fatih/color" - "github.com/safedep/dry/log" - "github.com/safedep/pmg/internal/ui" - "github.com/safedep/pmg/pkg/analyser" - "github.com/safedep/pmg/pkg/common/utils" - "github.com/safedep/pmg/pkg/models" - "github.com/safedep/pmg/pkg/registry" - vetUtils "github.com/safedep/vet/pkg/common/utils" -) - -type PackageManagerWrapper struct { - RegistryType registry.RegistryType - Flags []string - Action string - PackageNames []string - currentPackage string - PackagesToInstall []string -} - -func NewPackageManagerWrapper(registryType registry.RegistryType, flags []string, packageNames []string, action string) *PackageManagerWrapper { - return &PackageManagerWrapper{ - RegistryType: registryType, - PackageNames: packageNames, - Flags: flags, - Action: action, - } -} - -func (pmw *PackageManagerWrapper) Wrap() error { - if len(pmw.PackageNames) == 0 { - return fmt.Errorf("no packages specified") - } - - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute) - defer cancel() - - // Scan all packages first - for _, pkg := range pmw.PackageNames { - ui.StartProgressWriter() - var DefaultProgressTotal = 1 - pmw.currentPackage = pkg - progressTracker := ui.TrackProgress(fmt.Sprintf("Scanning %s", pkg), DefaultProgressTotal) - - if err := pmw.scanAndInstall(ctx, progressTracker); err != nil { - if errors.Is(err, ErrPackageInstall) { - log.Warnf("Skipping package %s due to ErrPackageInstall: %v", pkg, err) - continue - } - return err - } - pmw.PackagesToInstall = append(pmw.PackagesToInstall, pkg) - - ui.StopProgressWriter() - } - - if len(pmw.PackagesToInstall) == 0 { - log.Infof("No packages were installed due to security concerns") - return nil - } - // Execute installation after all scans complete - if err := pmw.executeInstallation(); err != nil { - return err - } - - log.Infof("Successfully installed all packages") - return nil -} - -func (pmw *PackageManagerWrapper) scanAndInstall(ctx context.Context, progressTracker ui.ProgressTracker) error { - factory := registry.NewFetcherFactory(10 * time.Second) - fetcher, err := factory.CreateFetcher(pmw.RegistryType) - if err != nil { - return err - } - - name, version, err := utils.ParsePackageInfo(pmw.currentPackage) - if err != nil { - return err - } - - if version == "" { - version, err = pmw.resolveLatestVersion(ctx, fetcher, name) - if err != nil { - return err - } - pmw.currentPackage = fmt.Sprintf("%s@%s", name, version) - } - - // Get dependencies with progress tracking - npmFetcher := fetcher.(*registry.NpmFetcher) - npmFetcher.SetProgressTracker(progressTracker) - - deps, err := npmFetcher.GetFlattenedDependencies(ctx, name, version) - if err != nil { - return err - } - - // Set progress for analysis phase - ui.IncrementTrackerTotal(progressTracker, int64(len(deps))) - if err := pmw.analyzeDependencies(ctx, deps, progressTracker); err != nil { - return err - } - - return nil -} - -func (pmw *PackageManagerWrapper) resolveLatestVersion(ctx context.Context, fetcher registry.Fetcher, name string) (string, error) { - log.Infof("No version specified for %s, fetching latest version...", name) - version, err := fetcher.(*registry.NpmFetcher).ResolveVersion(ctx, name, "") - if err != nil { - return "", err - } - log.Infof("Latest version of %s is %s", name, version) - return version, nil -} - -func (pmw *PackageManagerWrapper) analyzeDependencies(ctx context.Context, deps []string, progressTracker ui.ProgressTracker) error { - client, err := analyser.GetMalwareAnalysisClient() - if err != nil { - return fmt.Errorf("error while creating a malware analysis client: %w", err) - } - - pkgAnalyser := analyser.New(client, ctx, packagev1.Ecosystem_ECOSYSTEM_NPM) - pkgAnalyser.ProgressTracker = progressTracker - handler := pkgAnalyser.Handler() - - queue := vetUtils.NewWorkQueue[models.Package](100, 10, handler) - queue.Start() - defer queue.Stop() - - for _, dep := range deps { - name, version, err := utils.ParsePackageInfo(dep) - if err != nil { - log.Errorf("Error while parsing info of package %s", name) - continue - } - queue.Add(models.Package{ - Name: name, - Version: version, - }) - } - - queue.Wait() - ui.MarkTrackerAsDone(progressTracker) - ui.StopProgressWriter() - - if len(pkgAnalyser.MaliciousPkgs) > 0 { - if !utils.ConfirmInstallation(pkgAnalyser.MaliciousPkgs) { - log.Infof("Installation canceled due to security concerns") - return ErrPackageInstall - } - yellow := color.New(color.FgYellow, color.Bold).SprintfFunc() - log.Warnf(yellow("Continuing installation despite security warnings...")) - } - - return nil -} - -func (pmw *PackageManagerWrapper) executeInstallation() error { - execPath, err := utils.GetExecutablePath(string(pmw.RegistryType)) - if err != nil { - return fmt.Errorf("%s not found: %w", pmw.RegistryType, err) - } - - cmdArgs := []string{pmw.Action} - cmdArgs = append(cmdArgs, pmw.Flags...) - cmdArgs = append(cmdArgs, pmw.PackagesToInstall...) - if err = utils.ExecCmd(execPath, cmdArgs, []string{}); err != nil { - return fmt.Errorf("failed to execute %s command: %w", pmw.RegistryType, err) - } - - return nil -}