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
This commit is contained in:
Abhisek Datta
2025-05-15 16:50:59 +05:30
committed by GitHub
parent 8b46964c7a
commit e86b6ef056
47 changed files with 2562 additions and 1788 deletions
+77
View File
@@ -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 .
+70
View File
@@ -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
+8 -33
View File
@@ -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
+16 -4
View File
@@ -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).
+2
View File
@@ -0,0 +1,2 @@
golang 1.24.1
gitleaks 8.21.2
+29
View File
@@ -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"]
+131 -64
View File
@@ -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 <package-name>
pnpm add <package-name>
```
## 📑 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 <package>` |
| PNPM | ✅ Active | `pmg pnpm add <package>` |
| PyPI | 🚧 Planned | Coming soon |
| Go | 🚧 Planned | Coming soon |
PMG supports the following package managers:
| Package Manager | Status | Command |
| --------------- | --------- | --------------------------- |
| `npm` | ✅ Active | `pmg npm install <package>` |
| `pnpm` | ✅ Active | `pmg pnpm add <package>` |
| `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 <package-name>
pmg pnpm add <package-name>
```
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 <package-name>
```
```bash
pnpm add <package-name>
```
### Silent Mode
Use the `--silent` flag to run PMG in silent mode:
```bash
pmg --silent npm install <package-name>
```
### Verbose Mode
Use the `--verbose` flag to run PMG in verbose mode:
```bash
pmg --verbose npm install <package-name>
```
### Debugging
Use the `--debug` flag to enable debug mode:
```bash
pmg --debug npm install <package-name>
```
Store the debug logs in a file:
```bash
pmg --debug --log /tmp/debug.json npm install <package-name>
```
### 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 <package-name>
```
Alternative commands:
```bash
pmg npm i <package-name> # Short form
pmg npm add <package-name> # Alternative syntax
```
### PNPM Packages
Install a package:
```bash
pmg pnpm add <package-name>
```
### Common Flags
All standard package manager flags are supported:
```bash
pmg npm install --save-dev <package-name> # Install as dev dependency
pmg pnpm add -D <package-name> # Install as dev dependency
```
## Contributing
Refer to [CONTRIBUTING.md](CONTRIBUTING.md)
## Limitations
<details>
<summary>Approximate dependency version resolution</summary>
`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.
</details>
+47
View File
@@ -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)
}
+80
View File
@@ -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)
}
+62
View File
@@ -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)
}
+10 -31
View File
@@ -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
}
+10 -31
View File
@@ -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
}
+33
View File
@@ -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
}
+162 -74
View File
@@ -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
)
+437 -227
View File
@@ -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=
+258
View File
@@ -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()
}
@@ -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(),
+53
View File
@@ -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()
}
+150
View File
@@ -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")
}
+79
View File
@@ -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)
}
})
}
}
+17
View File
@@ -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'
+50 -5
View File
@@ -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())
+187
View File
@@ -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()
}
+156
View File
@@ -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
}
+84
View File
@@ -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)
}
+167
View File
@@ -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)
})
}
}
+123
View File
@@ -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)
})
}
}
+54
View File
@@ -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)
}
-90
View File
@@ -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
}
-16
View File
@@ -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
}
-87
View File
@@ -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
}
-25
View File
@@ -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
}
-18
View File
@@ -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()
}
-51
View File
@@ -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
}
-14
View File
@@ -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
}
-54
View File
@@ -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)
}
-64
View File
@@ -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)
}
-35
View File
@@ -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"
}
-21
View File
@@ -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
}
-212
View File
@@ -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)
}
}
}
-26
View File
@@ -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
}
-110
View File
@@ -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
}
-38
View File
@@ -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)
}
}
-84
View File
@@ -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)
}
-175
View File
@@ -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
}
-11
View File
@@ -1,11 +0,0 @@
package wrapper
import "errors"
const (
ErrPackageInstallationDeny = "PACKAGE_INSTALLATION_DENIED"
)
var (
ErrPackageInstall = errors.New(ErrPackageInstallationDeny)
)
-182
View File
@@ -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
}