mirror of
https://github.com/safedep/pmg.git
synced 2026-08-03 07:24:09 +02:00
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:
@@ -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 .
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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).
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
golang 1.24.1
|
||||
gitleaks 8.21.2
|
||||
+29
@@ -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"]
|
||||
@@ -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
|
||||

|
||||
@@ -66,30 +143,20 @@ Get your API credentials by visiting [SafeDep Quickstart Guide](https://docs.saf
|
||||
#### Bulk Package Analysis
|
||||

|
||||
|
||||
### 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>
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
@@ -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(),
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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 
|
||||
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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package wrapper
|
||||
|
||||
import "errors"
|
||||
|
||||
const (
|
||||
ErrPackageInstallationDeny = "PACKAGE_INSTALLATION_DENIED"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrPackageInstall = errors.New(ErrPackageInstallationDeny)
|
||||
)
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user