Merge branch 'main' into s3-source-unit-impl

This commit is contained in:
Mustansir
2025-11-17 20:41:39 +05:00
committed by GitHub
418 changed files with 26391 additions and 10389 deletions
+48
View File
@@ -0,0 +1,48 @@
# GitHub Workflows
This directory contains GitHub Actions workflows for the TruffleHog repository.
## PR Approval Check (`pr-approval-check.yml`)
This workflow enforces that at least one PR approver must be an **active** member of the `@trufflesecurity/product-eng` team or any of its child teams.
### How it works:
1. **Triggers**: The workflow runs on:
- `pull_request_review` events when a review is submitted (`submitted` type)
- `pull_request` events when a PR is opened, reopened, or synchronized (`opened`, `reopened`, `synchronize` types)
2. **Approval Check Process**: The workflow:
- Fetches all reviews for the PR using the GitHub API
- Filters for reviews with state `APPROVED`
- Gets all child teams of `@trufflesecurity/product-eng` using `listChildInOrg` API
- Checks if any approver is an **active** member (not pending) of either:
- The parent `@trufflesecurity/product-eng` team, OR
- Any of its child teams
- Sets a commit status accordingly
3. **Status Check**: Creates a commit status named `product-eng-approval` with:
- ✅ **Success**: When at least one approver is an active member of `@trufflesecurity/product-eng` or any child team
- ❌ **Failure**: When there are no approvals or there are approvals but none from active `@trufflesecurity/product-eng` members
### Error Handling
If there are errors listing reviews or checking team membership, the workflow reports a failure status and also fails itself.
### Branch Protection
To make this check required:
1. Go to Settings → Branches
2. Add or edit a branch protection rule for your main branch
3. Enable "Require status checks to pass before merging"
4. Add `pr-approval-check` to the required status checks
### Permissions
The workflow uses the default `GITHUB_TOKEN` which has sufficient permissions to:
- Read PR reviews
- List child teams and check team membership (for public teams)
- Create commit statuses
**Note**: If the `product-eng` team or its child teams are private, you may need to use a personal access token with appropriate permissions. The Github API returns 404 for non-members and for lack of permissions.
+1 -1
View File
@@ -38,7 +38,7 @@ jobs:
with:
go-version: "1.24"
- name: Cosign install
uses: sigstore/cosign-installer@3454372f43399081ed03b604cb2d021dabca52bb # v3.8.2
uses: sigstore/cosign-installer@d58896d6a1865668819e1d91763c7751a165e159 # v3.9.2
- name: Install UPX
run: |
sudo apt-get update
+1 -1
View File
@@ -8,7 +8,7 @@ RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build \
GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o trufflehog .
FROM alpine:3.21
FROM alpine:3.22
RUN apk add --no-cache bash git openssh-client ca-certificates rpm2cpio binutils cpio \
&& rm -rf /var/cache/apk/* && update-ca-certificates
COPY --from=builder /build/trufflehog /usr/bin/trufflehog
+1 -1
View File
@@ -1,4 +1,4 @@
FROM alpine:3.21
FROM alpine:3.22
RUN apk add --no-cache bash git openssh-client ca-certificates \
&& rm -rf /var/cache/apk/* && update-ca-certificates
+18
View File
@@ -160,6 +160,24 @@ In rare cases, you may need to bypass pre-commit hooks:
git commit --no-verify -m "Your commit message"
```
### Running in Audit Mode
You can run the TruffleHog pre-commit hook in an "audit" or "non-enforcement" mode to test the git hook with the following commands:
Local Binary Version:
```bash
trufflehog git file://. --since-commit HEAD --results=verified,unknown 2>/dev/null
```
Docker Container Version:
```bash
docker run --rm -v "$(pwd):/workdir" -i --rm trufflesecurity/trufflehog:latest git file:///workdir --since-commit HEAD --results=verified,unknown 2>/dev/null
```
This change does two things: (1) removes the `--fail` flag, which means the pre-commit hook will *always* pass, (2) suppresses `stderr` output, so only verified secrets are printed to the terminal output.
**For users of the Pre-Commit Framework: add the `verbose: true` flag during audit mode; otherwise, the hook will pass, and you won't see any secrets.**
## Troubleshooting
### Hook Not Running
+38 -23
View File
@@ -38,7 +38,7 @@ We take the revenue from the enterprise product to fund more awesome open source
# What is TruffleHog 🐽
TruffleHog is the most powerful secrets **Discovery, Classification, Validation,** and **Analysis** tool. In this context secret refers to a credential a machine uses to authenticate itself to another machine. This includes API keys, database passwords, private encryption keys, and more...
TruffleHog is the most powerful secrets **Discovery, Classification, Validation,** and **Analysis** tool. In this context, secret refers to a credential a machine uses to authenticate itself to another machine. This includes API keys, database passwords, private encryption keys, and more...
## Discovery 🔍
@@ -46,7 +46,7 @@ TruffleHog can look for secrets in many places including Git, chats, wikis, logs
## Classification 📁
TruffleHog classifies over 800 secret types, mapping them back to the specific identity they belong to. Is it an AWS secret? Stripe secret? Cloudflare secret? Postgres password? SSL Private key? Sometimes its hard to tell looking at it, so TruffleHog classifies everything it finds.
TruffleHog classifies over 800 secret types, mapping them back to the specific identity they belong to. Is it an AWS secret? Stripe secret? Cloudflare secret? Postgres password? SSL Private key? Sometimes it's hard to tell looking at it, so TruffleHog classifies everything it finds.
## Validation ✅
@@ -58,7 +58,7 @@ For the 20 some of the most commonly leaked out credential types, instead of sen
# :loudspeaker: Join Our Community
Have questions? Feedback? Jump in slack or discord and hang out with us
Have questions? Feedback? Jump into Slack or Discord and hang out with us.
Join our [Slack Community](https://join.slack.com/t/trufflehog-community/shared_invite/zt-pw2qbi43-Aa86hkiimstfdKH9UCpPzQ)
@@ -74,7 +74,7 @@ docker run --rm -it -v "$PWD:/pwd" trufflesecurity/trufflehog:latest github --or
# :floppy_disk: Installation
Several options available for you:
Several options are available for you:
### MacOS users
@@ -149,7 +149,7 @@ You need the following tool to verify signature:
- [Cosign](https://docs.sigstore.dev/cosign/system_config/installation/)
Verification steps are as follow:
Verification steps are as follows:
1. Download the artifact files you want, and the following files from the [releases](https://github.com/trufflesecurity/trufflehog/releases) page.
@@ -175,8 +175,8 @@ Verification steps are as follow:
Replace `{version}` with the downloaded files version
Alternatively, if you are using installation script, pass `-v` option to perform signature verification.
This required Cosign binary to be installed prior to running installation script.
Alternatively, if you are using the installation script, pass `-v` option to perform signature verification.
This requires Cosign binary to be installed prior to running the installation script.
# :rocket: Quick Start
@@ -185,7 +185,7 @@ This required Cosign binary to be installed prior to running installation script
Command:
```bash
trufflehog git https://github.com/trufflesecurity/test_keys --results=verified,unknown
trufflehog git https://github.com/trufflesecurity/test_keys --results=verified
```
Expected output:
@@ -209,15 +209,15 @@ Timestamp: 2022-06-16 10:17:40 -0700 PDT
## 2: Scan a GitHub Org for only verified secrets
```bash
trufflehog github --org=trufflesecurity --results=verified,unknown
trufflehog github --org=trufflesecurity --results=verified
```
## 3: Scan a GitHub Repo for only verified keys and get JSON output
## 3: Scan a GitHub Repo for only verified secrets and get JSON output
Command:
```bash
trufflehog git https://github.com/trufflesecurity/test_keys --results=verified,unknown --json
trufflehog git https://github.com/trufflesecurity/test_keys --results=verified --json
```
Expected output:
@@ -233,7 +233,7 @@ Expected output:
trufflehog github --repo=https://github.com/trufflesecurity/test_keys --issue-comments --pr-comments
```
## 5: Scan an S3 bucket for verified keys
## 5: Scan an S3 bucket for high-confidence results (verified + unknown)
```bash
trufflehog s3 --bucket=<bucket name> --results=verified,unknown
@@ -245,7 +245,7 @@ trufflehog s3 --bucket=<bucket name> --results=verified,unknown
trufflehog s3 --role-arn=<iam role arn>
```
## 7: Scan a Github Repo using SSH authentication in docker
## 7: Scan a Github Repo using SSH authentication in Docker
```bash
docker run --rm -v "$HOME/.ssh:/root/.ssh:ro" trufflesecurity/trufflehog:latest git ssh://github.com/trufflesecurity/test_keys
@@ -269,18 +269,27 @@ Run trufflehog from the parent directory (outside the git repo).
$ trufflehog git file://test_keys --results=verified,unknown
```
## 10: Scan GCS buckets for verified secrets
To guard against malicious git configs in local scanning (see CVE-2025-41390), TruffleHog clones local git repositories to a temporary directory prior to scanning. This follows [Git's security best practices](https://git-scm.com/docs/git#_security). If you want to specify a custom path to clone the repository to (instead of tmp), you can use the `--clone-path` flag. If you'd like to skip the local cloning process and scan the repository directly (only do this for trusted repos), you can use the `--trust-local-git-config` flag.
## 10: Scan GCS buckets for only verified secrets
```bash
trufflehog gcs --project-id=<project-ID> --cloud-environment --results=verified,unknown
trufflehog gcs --project-id=<project-ID> --cloud-environment --results=verified
```
## 11: Scan a Docker image for verified secrets
## 11: Scan a Docker image for only verified secrets
Use the `--image` flag multiple times to scan multiple images.
```bash
trufflehog docker --image trufflesecurity/secrets --results=verified,unknown
# to scan from a remote registry
trufflehog docker --image trufflesecurity/secrets --results=verified
# to scan from the local docker daemon
trufflehog docker --image docker://new_image:tag --results=verified
# to scan from an image saved as a tarball
trufflehog docker --image file://path_to_image.tar --results=verified
```
## 12: Scan in CI
@@ -382,7 +391,7 @@ aws s3 cp s3://example/gzipped/data.gz - | gunzip -c | trufflehog stdin
- Why is the scan taking a long time when I scan a GitHub org
- Unauthenticated GitHub scans have rate limits. To improve your rate limits, include the `--token` flag with a personal access token
- It says a private key was verified, what does that mean?
- Check out our Driftwood blog post to learn how to do this, in short we've confirmed the key can be used live for SSH or SSL [Blog post](https://trufflesecurity.com/blog/driftwood-know-if-private-keys-are-sensitive/)
- A verified result means TruffleHog confirmed the credential is valid by testing it against the service's API. For private keys, we've confirmed the key can be used live for SSH or SSL authentication. Check out our Driftwood blog post to learn more [Blog post](https://trufflesecurity.com/blog/driftwood-know-if-private-keys-are-sensitive/)
- Is there an easy way to ignore specific secrets?
- If the scanned source [supports line numbers](https://github.com/trufflesecurity/trufflehog/blob/d6375ba92172fd830abb4247cca15e3176448c5d/pkg/engine/engine.go#L358-L365), then you can add a `trufflehog:ignore` comment on the line containing the secret to ignore that secrets.
@@ -398,7 +407,13 @@ TruffleHog v3 is a complete rewrite in Go with many new powerful features.
## What is credential verification?
For every potential credential that is detected, we've painstakingly implemented programmatic verification against the API that we think it belongs to. Verification eliminates false positives. For example, the [AWS credential detector](pkg/detectors/aws/aws.go) performs a `GetCallerIdentity` API call against the AWS API to verify if an AWS credential is active.
For every potential credential that is detected, we've painstakingly implemented programmatic verification against the API that we think it belongs to. Verification eliminates false positives and provides three result statuses:
- **verified**: Credential confirmed as valid and active by API testing
- **unverified**: Credential detected but not confirmed valid (may be invalid, expired, or verification disabled)
- **unknown**: Verification attempted but failed due to errors, such as a network or API failure
For example, the [AWS credential detector](pkg/detectors/aws/aws.go) performs a `GetCallerIdentity` API call against the AWS API to verify if an AWS credential is active.
# :memo: Usage
@@ -437,7 +452,7 @@ Flags:
--github-actions Output in GitHub Actions format.
--concurrency=20 Number of concurrent workers.
--no-verification Don't verify the results.
--results=RESULTS Specifies which type(s) of results to output: verified, unknown, unverified, filtered_unverified. Defaults to all types.
--results=RESULTS Specifies which type(s) of results to output: verified (confirmed valid by API), unknown (verification failed due to error), unverified (detected but not verified), filtered_unverified (unverified but would have been filtered out). Defaults to all types.
--allow-verification-overlap
Allow verification of similar credentials across detectors
--filter-unverified Only output first unverified result per chunk per detector if there are more than one results.
@@ -568,7 +583,7 @@ In the example config above, we're scanning for live secrets in all PRs and Push
### Shallow Cloning
If you're incorporating TruffleHog into a standalone workflow and aren't running any other CI/CD tooling alongside TruffleHog, then we recommend using [Shallow Cloning](https://git-scm.com/docs/git-clone#Documentation/git-clone.txt---depthltdepthgt) to speed up your workflow. Here's an example for how to do it:
If you're incorporating TruffleHog into a standalone workflow and aren't running any other CI/CD tooling alongside TruffleHog, then we recommend using [Shallow Cloning](https://git-scm.com/docs/git-clone#Documentation/git-clone.txt---depthltdepthgt) to speed up your workflow. Here's an example of how to do it:
```
...
@@ -670,9 +685,9 @@ webhook is used containing the regular expression matches.
TruffleHog will send a JSON POST request containing the regex matches to a
configured webhook endpoint. If the endpoint responds with a `200 OK` response
status code, the secret is considered verified.
status code, the secret is considered verified. If verification fails due to network/API errors, the result is marked as unknown.
Custom Detectors support a few different filtering mechanisms: entropy, regex targeting the entire match, regex targeting the captured secret,
Custom Detectors support a few different filtering mechanisms: entropy, regex targeting the entire match, regex targeting the captured secret,
and excluded word lists checked against the secret (captured group if present, entire match if capture group is not present). Note that if
your custom detector has multiple `regex` set (in this example `hogID`, and `hogToken`), then the filters get applied to each regex. [Here](examples/generic_with_filters.yml) is an example of a custom detector using these filters.
+4
View File
@@ -11,6 +11,10 @@ detectors:
regex:
secret: |-
(?i)[\w.-]{0,50}?(?:access|auth|(?-i:[Aa]pi|API)|credential|creds|key|passw(?:or)?d|secret|token)(?:[ \t\w.-]{0,20})[\s'"]{0,3}(?:=|>|:{1,3}=|\|\||:|=>|\?=|,)[\x60'"\s=]{0,5}([\w.=-]{10,150}|[a-z0-9][a-z0-9+/]{11,}={0,3})(?:[\x60'"\s;]|\\[nr]|$)
validations:
secret: # name of the regex to apply these validations to
contains_digit: true
contains_special_char: true
entropy: 3.5
# exclude_regexes_capture:
# - |-
+100 -101
View File
@@ -1,49 +1,51 @@
module github.com/trufflesecurity/trufflehog/v3
go 1.23.1
go 1.24
toolchain go1.24.4
toolchain go1.24.5
replace github.com/jpillora/overseer => github.com/trufflesecurity/overseer v1.2.8
// Coinbase archived this library and it has some vulnerable dependencies so we've forked.
replace github.com/coinbase/waas-client-library-go => github.com/trufflesecurity/waas-client-library-go v1.0.9
// TODO: v1.134.0 is available but deprecates existing Auth methods, should be updated separately
replace gitlab.com/gitlab-org/api/client-go => gitlab.com/gitlab-org/api/client-go v0.129.0
require (
cloud.google.com/go/secretmanager v1.14.7
cloud.google.com/go/storage v1.50.0
cloud.google.com/go/secretmanager v1.15.0
cloud.google.com/go/storage v1.56.1
github.com/BobuSumisu/aho-corasick v1.0.3
github.com/TheZeroSlave/zapsentry v1.23.0
github.com/adrg/strutil v0.3.1
github.com/alecthomas/kingpin/v2 v2.4.0
github.com/avast/apkparser v0.0.0-20250530053210-166ba1767cb9
github.com/aws/aws-sdk-go-v2 v1.36.4
github.com/aws/aws-sdk-go-v2/config v1.29.16
github.com/aws/aws-sdk-go-v2/credentials v1.17.69
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.79
github.com/aws/aws-sdk-go-v2/service/s3 v1.80.2
github.com/aws/aws-sdk-go-v2/service/sns v1.34.6
github.com/aws/aws-sdk-go-v2/service/sts v1.33.21
github.com/aws/smithy-go v1.22.3
github.com/avast/apkparser v0.0.0-20250626104540-d53391f4d69d
github.com/aws/aws-sdk-go-v2 v1.39.0
github.com/aws/aws-sdk-go-v2/config v1.31.7
github.com/aws/aws-sdk-go-v2/credentials v1.18.11
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.19.5
github.com/aws/aws-sdk-go-v2/service/s3 v1.88.0
github.com/aws/aws-sdk-go-v2/service/sns v1.38.2
github.com/aws/aws-sdk-go-v2/service/sts v1.38.3
github.com/aws/smithy-go v1.23.0
github.com/aymanbagabas/go-osc52 v1.2.1
github.com/bill-rich/go-syslog v0.0.0-20220413021637-49edb52a574c
github.com/bitfinexcom/bitfinex-api-go v0.0.0-20210608095005-9e0b26f200fb
github.com/bradleyfalzon/ghinstallation/v2 v2.14.0
github.com/brianvoe/gofakeit/v7 v7.2.1
github.com/bradleyfalzon/ghinstallation/v2 v2.16.0
github.com/brianvoe/gofakeit/v7 v7.6.0
github.com/charmbracelet/bubbles v0.18.0
github.com/charmbracelet/bubbletea v1.3.4
github.com/charmbracelet/glamour v0.7.0
github.com/charmbracelet/lipgloss v1.1.0
github.com/coinbase/waas-client-library-go v1.0.8
github.com/couchbase/gocb/v2 v2.10.0
github.com/charmbracelet/bubbletea v1.3.6
github.com/charmbracelet/glamour v0.10.0
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834
github.com/couchbase/gocb/v2 v2.11.0
github.com/crewjam/rfc5424 v0.1.0
github.com/csnewman/dextk v0.3.0
github.com/docker/docker v28.3.3+incompatible
github.com/dustin/go-humanize v1.0.1
github.com/elastic/go-elasticsearch/v8 v8.17.1
github.com/envoyproxy/protoc-gen-validate v1.2.1
github.com/fatih/color v1.18.0
github.com/felixge/fgprof v0.9.5
github.com/gabriel-vasile/mimetype v1.4.9
github.com/gabriel-vasile/mimetype v1.4.10
github.com/getsentry/sentry-go v0.32.0
github.com/go-errors/errors v1.5.1
github.com/go-git/go-git/v5 v5.13.2
@@ -53,15 +55,15 @@ require (
github.com/go-redis/redis v6.15.9+incompatible
github.com/go-sql-driver/mysql v1.8.1
github.com/gobwas/glob v0.2.3
github.com/golang-jwt/jwt/v5 v5.2.2
github.com/golang-jwt/jwt/v5 v5.2.3
github.com/google/go-cmp v0.7.0
github.com/google/go-containerregistry v0.20.3
github.com/google/go-containerregistry v0.20.6
github.com/google/go-github/v67 v67.0.0
github.com/google/uuid v1.6.0
github.com/googleapis/gax-go/v2 v2.14.2
github.com/hashicorp/go-retryablehttp v0.7.7
github.com/googleapis/gax-go/v2 v2.15.0
github.com/hashicorp/go-retryablehttp v0.7.8
github.com/hashicorp/golang-lru/v2 v2.0.7
github.com/jedib0t/go-pretty/v6 v6.6.7
github.com/jedib0t/go-pretty/v6 v6.6.8
github.com/jlaffaye/ftp v0.2.0
github.com/joho/godotenv v1.5.1
github.com/jpillora/overseer v1.1.6
@@ -84,11 +86,11 @@ require (
github.com/repeale/fp-go v0.11.1
github.com/sassoftware/go-rpmutils v0.4.0
github.com/schollz/progressbar/v3 v3.17.1
github.com/sendgrid/sendgrid-go v3.16.0+incompatible
github.com/sendgrid/sendgrid-go v3.16.1+incompatible
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3
github.com/shuheiktgw/go-travis v0.3.1
github.com/shurcooL/githubv4 v0.0.0-20240727222349-48295856cce7
github.com/stretchr/testify v1.10.0
github.com/tailscale/depaware v0.0.0-20250112153213-b748de04d81b
github.com/testcontainers/testcontainers-go v0.34.0
github.com/testcontainers/testcontainers-go/modules/elasticsearch v0.34.0
github.com/testcontainers/testcontainers-go/modules/mongodb v0.34.0
@@ -97,20 +99,20 @@ require (
github.com/testcontainers/testcontainers-go/modules/postgres v0.34.0
github.com/trufflesecurity/disk-buffer-reader v0.2.1
github.com/wasilibs/go-re2 v1.9.0
github.com/xo/dburl v0.23.7
gitlab.com/gitlab-org/api/client-go v0.127.0
go.mongodb.org/mongo-driver v1.17.3
github.com/xo/dburl v0.23.8
gitlab.com/gitlab-org/api/client-go v0.129.0
go.mongodb.org/mongo-driver v1.17.4
go.uber.org/automaxprocs v1.6.0
go.uber.org/mock v0.5.1
go.uber.org/mock v0.5.2
go.uber.org/zap v1.27.0
golang.org/x/crypto v0.37.0
golang.org/x/net v0.39.0
golang.org/x/crypto v0.41.0
golang.org/x/net v0.43.0
golang.org/x/oauth2 v0.30.0
golang.org/x/sync v0.14.0
golang.org/x/text v0.24.0
golang.org/x/time v0.11.0
google.golang.org/api v0.232.0
google.golang.org/protobuf v1.36.6
golang.org/x/sync v0.16.0
golang.org/x/text v0.28.0
golang.org/x/time v0.12.0
google.golang.org/api v0.247.0
google.golang.org/protobuf v1.36.9
gopkg.in/h2non/gock.v1 v1.1.2
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.1
@@ -120,43 +122,41 @@ require (
)
require (
cel.dev/expr v0.20.0 // indirect
cloud.google.com/go v0.120.0 // indirect
cloud.google.com/go/auth v0.16.1 // indirect
cel.dev/expr v0.24.0 // indirect
cloud.google.com/go v0.121.6 // indirect
cloud.google.com/go/auth v0.16.5 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/compute/metadata v0.6.0 // indirect
cloud.google.com/go/compute/metadata v0.8.0 // indirect
cloud.google.com/go/iam v1.5.2 // indirect
cloud.google.com/go/longrunning v0.6.7 // indirect
cloud.google.com/go/monitoring v1.24.2 // indirect
dario.cat/mergo v1.0.0 // indirect
filippo.io/edwards25519 v1.1.0 // indirect
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 // indirect
github.com/DataDog/zstd v1.5.5 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.26.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.50.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.50.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/ProtonMail/go-crypto v1.1.5 // indirect
github.com/STARRY-S/zip v0.2.1 // indirect
github.com/alecthomas/chroma/v2 v2.8.0 // indirect
github.com/alecthomas/chroma/v2 v2.14.0 // indirect
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect
github.com/andybalholm/brotli v1.1.1 // indirect
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.31 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.35 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.35 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.7 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.7 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.7 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.35 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.3 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.16 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.16 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.25.4 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.2 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.7 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.7 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.7 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.7 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.29.2 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.34.3 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
@@ -166,15 +166,18 @@ require (
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
github.com/charmbracelet/x/ansi v0.8.0 // indirect
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
github.com/charmbracelet/x/ansi v0.9.3 // indirect
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf // indirect
github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/cloudflare/circl v1.3.8 // indirect
github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 // indirect
github.com/cloudflare/circl v1.6.1 // indirect
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect
github.com/containerd/errdefs v1.0.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/containerd/platforms v0.2.1 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.16.3 // indirect
github.com/couchbase/gocbcore/v10 v10.7.0 // indirect
github.com/couchbase/gocbcore/v10 v10.8.0 // indirect
github.com/couchbase/gocbcoreps v0.1.3 // indirect
github.com/couchbase/goprotostellar v1.0.2 // indirect
github.com/couchbaselabs/gocbconnstr/v2 v2.0.0-20240607131231-fb385523de28 // indirect
@@ -182,11 +185,10 @@ require (
github.com/cyphar/filepath-securejoin v0.3.6 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/distribution/reference v0.6.0 // indirect
github.com/dlclark/regexp2 v1.4.0 // indirect
github.com/docker/cli v27.5.0+incompatible // indirect
github.com/dlclark/regexp2 v1.11.0 // indirect
github.com/docker/cli v28.2.2+incompatible // indirect
github.com/docker/distribution v2.8.3+incompatible // indirect
github.com/docker/docker v27.5.0+incompatible // indirect
github.com/docker/docker-credential-helpers v0.8.2 // indirect
github.com/docker/docker-credential-helpers v0.9.3 // indirect
github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect
@@ -199,18 +201,17 @@ require (
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.6.2 // indirect
github.com/go-jose/go-jose/v4 v4.0.4 // indirect
github.com/go-jose/go-jose/v4 v4.0.5 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/gofrs/flock v0.12.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.1 // indirect
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
github.com/golang-sql/sqlexp v0.1.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/go-github/v66 v66.0.0 // indirect
github.com/google/go-github/v69 v69.0.0 // indirect
github.com/golang/snappy v1.0.0 // indirect
github.com/google/go-github/v72 v72.0.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 // indirect
github.com/google/s2a-go v0.1.9 // indirect
@@ -238,9 +239,10 @@ require (
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/go-archive v0.1.0 // indirect
github.com/moby/patternmatcher v0.6.0 // indirect
github.com/moby/sys/sequential v0.5.0 // indirect
github.com/moby/sys/user v0.1.0 // indirect
github.com/moby/sys/sequential v0.6.0 // indirect
github.com/moby/sys/user v0.4.0 // indirect
github.com/moby/sys/userns v0.1.0 // indirect
github.com/moby/term v0.5.0 // indirect
github.com/montanaflynn/stats v0.7.1 // indirect
@@ -249,14 +251,12 @@ require (
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/termenv v0.16.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/nwaples/rardecode/v2 v2.0.0-beta.4.0.20241112120701-034e449c6e78 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/nwaples/rardecode/v2 v2.2.1 // indirect
github.com/onsi/ginkgo v1.16.5 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/pierrec/lz4/v4 v4.1.21 // indirect
github.com/pjbgf/sha1cd v0.3.2 // indirect
github.com/pkg/diff v0.0.0-20200914180035-5b29258ca4f7 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
@@ -268,6 +268,7 @@ require (
github.com/sendgrid/rest v2.6.9+incompatible // indirect
github.com/shirou/gopsutil/v3 v3.23.12 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/skeema/knownhosts v1.3.0 // indirect
github.com/sorairolake/lzip-go v0.3.5 // indirect
@@ -279,7 +280,7 @@ require (
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/trufflesecurity/touchfile v0.1.1 // indirect
github.com/ulikunitz/xz v0.5.12 // indirect
github.com/vbatts/tar-split v0.11.6 // indirect
github.com/vbatts/tar-split v0.12.1 // indirect
github.com/wasilibs/wazero-helpers v0.0.0-20240620070341-3dff1577cd52 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
@@ -289,32 +290,30 @@ require (
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
github.com/yuin/goldmark v1.5.4 // indirect
github.com/yuin/goldmark-emoji v1.0.2 // indirect
github.com/yuin/goldmark v1.7.8 // indirect
github.com/yuin/goldmark-emoji v1.0.5 // indirect
github.com/yusufpapurcu/wmi v1.2.3 // indirect
github.com/zeebo/errs v1.4.0 // indirect
go.einride.tech/aip v0.60.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.34.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
go.opentelemetry.io/otel v1.35.0 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.62.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
go.opentelemetry.io/otel v1.37.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 // indirect
go.opentelemetry.io/otel/metric v1.35.0 // indirect
go.opentelemetry.io/otel/sdk v1.35.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.35.0 // indirect
go.opentelemetry.io/otel/trace v1.35.0 // indirect
go.opentelemetry.io/otel/metric v1.37.0 // indirect
go.opentelemetry.io/otel/sdk v1.37.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect
go.opentelemetry.io/otel/trace v1.37.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 // indirect
golang.org/x/mod v0.22.0 // indirect
golang.org/x/sys v0.32.0 // indirect
golang.org/x/term v0.31.0 // indirect
golang.org/x/tools v0.29.0 // indirect
google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250505200425-f936aa4a68b2 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250505200425-f936aa4a68b2 // indirect
google.golang.org/grpc v1.72.0 // indirect
golang.org/x/mod v0.26.0 // indirect
golang.org/x/sys v0.35.0 // indirect
golang.org/x/term v0.34.0 // indirect
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c // indirect
google.golang.org/grpc v1.74.2 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
pault.ag/go/topsort v0.1.1 // indirect
)
+211 -293
View File
@@ -1,7 +1,5 @@
cel.dev/expr v0.19.2 h1:V354PbqIXr9IQdwy4SYA4xa0HXaWq1BUPAGzugBY5V4=
cel.dev/expr v0.19.2/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw=
cel.dev/expr v0.20.0 h1:OunBvVCfvpWlt4dN7zg3FM6TDkzOePe1+foGJ9AXeeI=
cel.dev/expr v0.20.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw=
cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=
cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
@@ -11,50 +9,42 @@ cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTj
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.120.0 h1:wc6bgG9DHyKqF5/vQvX1CiZrtHnxJjBlKUyF9nP6meA=
cloud.google.com/go v0.120.0/go.mod h1:/beW32s8/pGRuj4IILWQNd4uuebeT4dkOhKmkfit64Q=
cloud.google.com/go/auth v0.16.0 h1:Pd8P1s9WkcrBE2n/PhAwKsdrR35V3Sg2II9B+ndM3CU=
cloud.google.com/go/auth v0.16.0/go.mod h1:1howDHJ5IETh/LwYs3ZxvlkXF48aSqqJUM+5o02dNOI=
cloud.google.com/go/auth v0.16.1 h1:XrXauHMd30LhQYVRHLGvJiYeczweKQXZxsTbV9TiguU=
cloud.google.com/go/auth v0.16.1/go.mod h1:1howDHJ5IETh/LwYs3ZxvlkXF48aSqqJUM+5o02dNOI=
cloud.google.com/go v0.121.6 h1:waZiuajrI28iAf40cWgycWNgaXPO06dupuS+sgibK6c=
cloud.google.com/go v0.121.6/go.mod h1:coChdst4Ea5vUpiALcYKXEpR1S9ZgXbhEzzMcMR66vI=
cloud.google.com/go/auth v0.16.5 h1:mFWNQ2FEVWAliEQWpAdH80omXFokmrnbDhUS9cBywsI=
cloud.google.com/go/auth v0.16.5/go.mod h1:utzRfHMP+Vv0mpOkTRQoWD2q3BatTOoWbA7gCc2dUhQ=
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
cloud.google.com/go/compute/metadata v0.8.0 h1:HxMRIbao8w17ZX6wBnjhcDkW6lTFpgcaobyVfZWqRLA=
cloud.google.com/go/compute/metadata v0.8.0/go.mod h1:sYOGTp851OV9bOFJ9CH7elVvyzopvWQFNNghtDQ/Biw=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/iam v1.5.0 h1:QlLcVMhbLGOjRcGe6VTGGTyQib8dRLK2B/kYNV0+2xs=
cloud.google.com/go/iam v1.5.0/go.mod h1:U+DOtKQltF/LxPEtcDLoobcsZMilSRwR7mgNL7knOpo=
cloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8=
cloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE=
cloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc=
cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA=
cloud.google.com/go/longrunning v0.6.6 h1:XJNDo5MUfMM05xK3ewpbSdmt7R2Zw+aQEMbdQR65Rbw=
cloud.google.com/go/longrunning v0.6.6/go.mod h1:hyeGJUrPHcx0u2Uu1UFSoYZLn4lkMrccJig0t4FI7yw=
cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE=
cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY=
cloud.google.com/go/monitoring v1.24.0 h1:csSKiCJ+WVRgNkRzzz3BPoGjFhjPY23ZTcaenToJxMM=
cloud.google.com/go/monitoring v1.24.0/go.mod h1:Bd1PRK5bmQBQNnuGwHBfUamAV1ys9049oEPHnn4pcsc=
cloud.google.com/go/monitoring v1.24.2 h1:5OTsoJ1dXYIiMiuL+sYscLc9BumrL3CarVLL7dd7lHM=
cloud.google.com/go/monitoring v1.24.2/go.mod h1:x7yzPWcgDRnPEv3sI+jJGBkwl5qINf+6qY4eq0I9B4U=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/secretmanager v1.14.7 h1:VkscIRzj7GcmZyO4z9y1EH7Xf81PcoiAo7MtlD+0O80=
cloud.google.com/go/secretmanager v1.14.7/go.mod h1:uRuB4F6NTFbg0vLQ6HsT7PSsfbY7FqHbtJP1J94qxGc=
cloud.google.com/go/secretmanager v1.15.0 h1:RtkCMgTpaBMbzozcRUGfZe46jb9a3qh5EdEtVRUATF8=
cloud.google.com/go/secretmanager v1.15.0/go.mod h1:1hQSAhKK7FldiYw//wbR/XPfPc08eQ81oBsnRUHEvUc=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.50.0 h1:3TbVkzTooBvnZsk7WaAQfOsNrdoM8QHusXA1cpk6QJs=
cloud.google.com/go/storage v1.50.0/go.mod h1:l7XeiD//vx5lfqE3RavfmU9yvk5Pp0Zhcv482poyafY=
cloud.google.com/go/trace v1.11.3 h1:c+I4YFjxRQjvAhRmSsmjpASUKq88chOX854ied0K/pE=
cloud.google.com/go/trace v1.11.3/go.mod h1:pt7zCYiDSQjC9Y2oqCsh9jF4GStB/hmjrYLsxRR27q8=
cloud.google.com/go/storage v1.56.1 h1:n6gy+yLnHn0hTwBFzNn8zJ1kqWfR91wzdM8hjRF4wP0=
cloud.google.com/go/storage v1.56.1/go.mod h1:C9xuCZgFl3buo2HZU/1FncgvvOgTAs/rnh4gF4lMg0s=
cloud.google.com/go/trace v1.11.6 h1:2O2zjPzqPYAHrn3OKl029qlqG6W8ZdYaOWRyr8NgMT4=
cloud.google.com/go/trace v1.11.6/go.mod h1:GA855OeDEBiBMzcckLPE2kDunIpC72N+Pq8WFieFjnI=
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 h1:E+OJmp2tPvt1W+amx48v1eqbjDYsgN+RzP4q16yV5eM=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1/go.mod h1:a6xsAQUZg+VsS3TJ05SRp524Hs4pZ/AeFSr5ENf0Yjo=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0 h1:U2rTu3Ef+7w9FHKIAXM6ZyqF3UOWJZ12zIm8zECAFfg=
@@ -77,16 +67,14 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ=
github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 h1:3c8yed4lgqTt+oTQ+JNMDo+F4xprBf+O/il4ZC0nRLw=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0/go.mod h1:obipzmGjfSjam60XLwGfqUkJsfiheAl+TUjG+4yzyPM=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.26.0 h1:f2Qw/Ehhimh5uO1fayV0QIW7DShEQqhtUfhYc+cBPlw=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.26.0/go.mod h1:2bIszWvQRlJVmJLiuLhukLImRjKPcYdzzsx6darK02A=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.50.0 h1:5IT7xOdq17MtcdtL/vtl6mGfzhaq4m4vpollPRmlsBQ=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.50.0/go.mod h1:ZV4VOm0/eHR06JLrXWe09068dHpr3TRpY9Uo7T+anuA=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.50.0 h1:nNMpRpnkWDAaqcpxMJvxa/Ud98gjbYwayJY4/9bdjiU=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.50.0/go.mod h1:SZiPHWGOOk3bl8tkevxkoiwPgsIl6CwrWcbwjfHZpdM=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.50.0 h1:ig/FpDD2JofP/NExKQUbn7uOSZzJAQqogfqluZK4ed4=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.50.0/go.mod h1:otE2jQekW/PqXk1Awf5lmfokJx4uwuqcj1ab5SpGeW0=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 h1:ErKg/3iS1AKcTkf3yixlZ54f9U1rljCkQyEXWUnIUxc=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0/go.mod h1:yAZHSGnqScoU556rBOVkwLze6WP5N+U11RHuWaGVxwY=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 h1:owcC2UnmsZycprQ5RfRgjydWhuoxg71LUfyiQdijZuM=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0/go.mod h1:ZPpqegjbE99EPKsu3iUWV22A04wzGPcAY/ziSIQEEgs=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.53.0 h1:4LP6hvB4I5ouTbGgWtixJhgED6xdf67twf9PoY96Tbg=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.53.0/go.mod h1:jUZ5LYlw40WMd07qxcQJD5M40aUxrfwqQX1g7zxYnrQ=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 h1:Ron4zCA/yk6U7WOBXhTJcDpsUBG9npumK6xw2auFltQ=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0/go.mod h1:cSgYe11MCNYunTnRXrKiR/tHc0eoKjICUuWpNZoVCOo=
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
@@ -98,14 +86,14 @@ github.com/TheZeroSlave/zapsentry v1.23.0 h1:TKyzfEL7LRlRr+7AvkukVLZ+jZPC++ebCUv
github.com/TheZeroSlave/zapsentry v1.23.0/go.mod h1:3DRFLu4gIpnCTD4V9HMCBSaqYP8gYU7mZickrs2/rIY=
github.com/adrg/strutil v0.3.1 h1:OLvSS7CSJO8lBii4YmBt8jiK9QOtB9CzCzwl4Ic/Fz4=
github.com/adrg/strutil v0.3.1/go.mod h1:8h90y18QLrs11IBffcGX3NW/GFBXCMcNg4M7H6MspPA=
github.com/alecthomas/assert/v2 v2.2.1 h1:XivOgYcduV98QCahG8T5XTezV5bylXe+lBxLG2K2ink=
github.com/alecthomas/assert/v2 v2.2.1/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ=
github.com/alecthomas/chroma/v2 v2.8.0 h1:w9WJUjFFmHHB2e8mRpL9jjy3alYDlU0QLDezj1xE264=
github.com/alecthomas/chroma/v2 v2.8.0/go.mod h1:yrkMI9807G1ROx13fhe1v6PN2DDeaR73L3d+1nmYQtw=
github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE=
github.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E=
github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I=
github.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjHpqDjYY=
github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE=
github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk=
github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
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/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc=
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI=
@@ -118,86 +106,54 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/avast/apkparser v0.0.0-20250307094510-e2100ee9c0f5 h1:zRaiVswCgpvZ6lyeErxC94EJaZdSoj0mzUPYRBOnqCI=
github.com/avast/apkparser v0.0.0-20250307094510-e2100ee9c0f5/go.mod h1:GNvprXNmXaDjpHmN3RFxz5QdK5VXTUvmQludCbjoBy4=
github.com/avast/apkparser v0.0.0-20250530053210-166ba1767cb9 h1:/c8YtwIZvwI43QdQh/nh5s4GYWnMPqnzZNJ5JFxMAkU=
github.com/avast/apkparser v0.0.0-20250530053210-166ba1767cb9/go.mod h1:3F9A8btIerUcuy7Fmno+g/nIk4ELKJ6NCs2/KK1bvLs=
github.com/aws/aws-sdk-go-v2 v1.36.3 h1:mJoei2CxPutQVxaATCzDUjcZEjVRdpsiiXi2o38yqWM=
github.com/aws/aws-sdk-go-v2 v1.36.3/go.mod h1:LLXuLpgzEbD766Z5ECcRmi8AzSwfZItDtmABVkRLGzg=
github.com/aws/aws-sdk-go-v2 v1.36.4 h1:GySzjhVvx0ERP6eyfAbAuAXLtAda5TEy19E5q5W8I9E=
github.com/aws/aws-sdk-go-v2 v1.36.4/go.mod h1:LLXuLpgzEbD766Z5ECcRmi8AzSwfZItDtmABVkRLGzg=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 h1:zAybnyUQXIZ5mok5Jqwlf58/TFE7uvd3IAsa1aF9cXs=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10/go.mod h1:qqvMj6gHLR/EXWZw4ZbqlPbQUyenf4h82UQUlKc+l14=
github.com/aws/aws-sdk-go-v2/config v1.29.14 h1:f+eEi/2cKCg9pqKBoAIwRGzVb70MRKqWX4dg1BDcSJM=
github.com/aws/aws-sdk-go-v2/config v1.29.14/go.mod h1:wVPHWcIFv3WO89w0rE10gzf17ZYy+UVS1Geq8Iei34g=
github.com/aws/aws-sdk-go-v2/config v1.29.16 h1:XkruGnXX1nEZ+Nyo9v84TzsX+nj86icbFAeust6uo8A=
github.com/aws/aws-sdk-go-v2/config v1.29.16/go.mod h1:uCW7PNjGwZ5cOGZ5jr8vCWrYkGIhPoTNV23Q/tpHKzg=
github.com/aws/aws-sdk-go-v2/credentials v1.17.67 h1:9KxtdcIA/5xPNQyZRgUSpYOE6j9Bc4+D7nZua0KGYOM=
github.com/aws/aws-sdk-go-v2/credentials v1.17.67/go.mod h1:p3C44m+cfnbv763s52gCqrjaqyPikj9Sg47kUVaNZQQ=
github.com/aws/aws-sdk-go-v2/credentials v1.17.69 h1:8B8ZQboRc3uaIKjshve/XlvJ570R7BKNy3gftSbS178=
github.com/aws/aws-sdk-go-v2/credentials v1.17.69/go.mod h1:gPME6I8grR1jCqBFEGthULiolzf/Sexq/Wy42ibKK9c=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 h1:x793wxmUWVDhshP8WW2mlnXuFrO4cOd3HLBroh1paFw=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30/go.mod h1:Jpne2tDnYiFascUEs2AWHJL9Yp7A5ZVy3TNyxaAjD6M=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.31 h1:oQWSGexYasNpYp4epLGZxxjsDo8BMBh6iNWkTXQvkwk=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.31/go.mod h1:nc332eGUU+djP3vrMI6blS0woaCfHTe3KiSQUVTMRq0=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.75 h1:S61/E3N01oral6B3y9hZ2E1iFDqCZPPOBoBQretCnBI=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.75/go.mod h1:bDMQbkI1vJbNjnvJYpPTSNYBkI/VIv18ngWb/K84tkk=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.79 h1:mGo6WGWry+s5GEf2GLfw3zkHad109FQmtvBV3VYQ8mA=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.79/go.mod h1:siwnpWxHYFSSge7Euw9lGMgQBgvRyym352mCuGNHsMQ=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 h1:ZK5jHhnrioRkUNOc+hOgQKlUL5JeC3S6JgLxtQ+Rm0Q=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34/go.mod h1:p4VfIceZokChbA9FzMbRGz5OV+lekcVtHlPKEO0gSZY=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.35 h1:o1v1VFfPcDVlK3ll1L5xHsaQAFdNtZ5GXnNR7SwueC4=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.35/go.mod h1:rZUQNYMNG+8uZxz9FOerQJ+FceCiodXvixpeRtdESrU=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 h1:SZwFm17ZUNNg5Np0ioo/gq8Mn6u9w19Mri8DnJ15Jf0=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34/go.mod h1:dFZsC0BLo346mvKQLWmoJxT+Sjp+qcVR1tRVHQGOH9Q=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.35 h1:R5b82ubO2NntENm3SAm0ADME+H630HomNJdgv+yZ3xw=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.35/go.mod h1:FuA+nmgMRfkzVKYDNEqQadvEMxtxl9+RLT9ribCwEMs=
github.com/avast/apkparser v0.0.0-20250626104540-d53391f4d69d h1:PGSn2pnK/u5ZBompy83R6Wo4BqLYp3dX43QWDoPv7TA=
github.com/avast/apkparser v0.0.0-20250626104540-d53391f4d69d/go.mod h1:3F9A8btIerUcuy7Fmno+g/nIk4ELKJ6NCs2/KK1bvLs=
github.com/aws/aws-sdk-go-v2 v1.39.0 h1:xm5WV/2L4emMRmMjHFykqiA4M/ra0DJVSWUkDyBjbg4=
github.com/aws/aws-sdk-go-v2 v1.39.0/go.mod h1:sDioUELIUO9Znk23YVmIk86/9DOpkbyyVb1i/gUNFXY=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1 h1:i8p8P4diljCr60PpJp6qZXNlgX4m2yQFpYk+9ZT+J4E=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1/go.mod h1:ddqbooRZYNoJ2dsTwOty16rM+/Aqmk/GOXrK8cg7V00=
github.com/aws/aws-sdk-go-v2/config v1.31.7 h1:zS1O6hr6t0nZdBCMFc/c9OyZFyLhXhf/B2IZ9Y0lRQE=
github.com/aws/aws-sdk-go-v2/config v1.31.7/go.mod h1:GpHmi1PQDdL5pP4JaB00pU0ek4EXVcYH7IkjkUadQmM=
github.com/aws/aws-sdk-go-v2/credentials v1.18.11 h1:1Fnb+7Dk96/VYx/uYfzk5sU2V0b0y2RWZROiMZCN/Io=
github.com/aws/aws-sdk-go-v2/credentials v1.18.11/go.mod h1:iuvn9v10dkxU4sDgtTXGWY0MrtkEcmkUmjv4clxhuTc=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.7 h1:Is2tPmieqGS2edBnmOJIbdvOA6Op+rRpaYR60iBAwXM=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.7/go.mod h1:F1i5V5421EGci570yABvpIXgRIBPb5JM+lSkHF6Dq5w=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.19.5 h1:fSuJX/VBJKufwJG/szWgUdRJVyRiEQDDXNh/6NPrTBg=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.19.5/go.mod h1:LvN0noQuST+3Su55Wl++BkITpptnfN9g6Ohkv4zs9To=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.7 h1:UCxq0X9O3xrlENdKf1r9eRJoKz/b0AfGkpp3a7FPlhg=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.7/go.mod h1:rHRoJUNUASj5Z/0eqI4w32vKvC7atoWR0jC+IkmVH8k=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.7 h1:Y6DTZUn7ZUC4th9FMBbo8LVE+1fyq3ofw+tRwkUd3PY=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.7/go.mod h1:x3XE6vMnU9QvHN/Wrx2s44kwzV2o2g5x/siw4ZUJ9g8=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34 h1:ZNTqv4nIdE/DiBfUUfXcLZ/Spcuz+RjeziUtNJackkM=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34/go.mod h1:zf7Vcd1ViW7cPqYWEHLHJkS50X0JS2IKz9Cgaj6ugrs=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.35 h1:th/m+Q18CkajTw1iqx2cKkLCij/uz8NMwJFPK91p2ug=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.35/go.mod h1:dkJuf0a1Bc8HAA0Zm2MoTGm/WDC18Td9vSbrQ1+VqE8=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 h1:eAh2A4b5IzM/lum78bZ590jy36+d/aFLgKF/4Vd1xPE=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3/go.mod h1:0yKJC/kb8sAnmlYa6Zs3QVYqaC8ug2AbnNChv5Ox3uA=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.1 h1:4nm2G6A4pV9rdlWzGMPv4BNtQp22v1hg3yrtkYpeLl8=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.1/go.mod h1:iu6FSzgt+M2/x3Dk8zhycdIcHjEFb36IS8HVUVFoMg0=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.3 h1:VHPZakq2L7w+RLzV54LmQavbvheFaR2u1NomJRSEfcU=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.3/go.mod h1:DX1e/lkbsAt0MkY3NgLYuH4jQvRfw8MYxTe9feR7aXM=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 h1:dM9/92u2F1JbDaGooxTq18wmmFzbJRfXfVfy96/1CXM=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15/go.mod h1:SwFBy2vjtA0vZbjjaFtfN045boopadnoVPhu4Fv66vY=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.16 h1:/ldKrPPXTC421bTNWrUIpq3CxwHwRI/kpc+jPUTJocM=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.16/go.mod h1:5vkf/Ws0/wgIMJDQbjI4p2op86hNW6Hie5QtebrDgT8=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15 h1:moLQUoVq91LiqT1nbvzDukyqAlCv89ZmwaHw/ZFlFZg=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15/go.mod h1:ZH34PJUc8ApjBIfgQCFvkWcUDBtl/WTD+uiYHjd8igA=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.16 h1:2HuI7vWKhFWsBhIr2Zq8KfFZT6xqaId2XXnXZjkbEuc=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.16/go.mod h1:BrwWnsfbFtFeRjdx0iM1ymvlqDX1Oz68JsQaibX/wG8=
github.com/aws/aws-sdk-go-v2/service/s3 v1.79.3 h1:BRXS0U76Z8wfF+bnkilA2QwpIch6URlm++yPUt9QPmQ=
github.com/aws/aws-sdk-go-v2/service/s3 v1.79.3/go.mod h1:bNXKFFyaiVvWuR6O16h/I1724+aXe/tAkA9/QS01t5k=
github.com/aws/aws-sdk-go-v2/service/s3 v1.80.2 h1:T6Wu+8E2LeTUqzqQ/Bh1EoFNj1u4jUyveMgmTlu9fDU=
github.com/aws/aws-sdk-go-v2/service/s3 v1.80.2/go.mod h1:chSY8zfqmS0OnhZoO/hpPx/BHfAIL80m77HwhRLYScY=
github.com/aws/aws-sdk-go-v2/service/sns v1.34.4 h1:ihddI5wufQQCJiujUgAvWRqZcfDmSKIfXlAuX7T95cg=
github.com/aws/aws-sdk-go-v2/service/sns v1.34.4/go.mod h1:PJtxxMdj747j8DeZENRTTYAz/lx/pADn/U0k7YNNiUY=
github.com/aws/aws-sdk-go-v2/service/sns v1.34.6 h1:+UdAoQcO1KupOdam6vJC06kzmbeES64L0W9FM+LEvow=
github.com/aws/aws-sdk-go-v2/service/sns v1.34.6/go.mod h1:0PvYt3tRBPMJ/vky7631/4C6OCvWecnWwR6oq1jF4Uk=
github.com/aws/aws-sdk-go-v2/service/sso v1.25.3 h1:1Gw+9ajCV1jogloEv1RRnvfRFia2cL6c9cuKV2Ps+G8=
github.com/aws/aws-sdk-go-v2/service/sso v1.25.3/go.mod h1:qs4a9T5EMLl/Cajiw2TcbNt2UNo/Hqlyp+GiuG4CFDI=
github.com/aws/aws-sdk-go-v2/service/sso v1.25.4 h1:EU58LP8ozQDVroOEyAfcq0cGc5R/FTZjVoYJ6tvby3w=
github.com/aws/aws-sdk-go-v2/service/sso v1.25.4/go.mod h1:CrtOgCcysxMvrCoHnvNAD7PHWclmoFG78Q2xLK0KKcs=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1 h1:hXmVKytPfTy5axZ+fYbR5d0cFmC3JvwLm5kM83luako=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1/go.mod h1:MlYRNmYu/fGPoxBQVvBYr9nyr948aY/WLUvwBMBJubs=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.2 h1:XB4z0hbQtpmBnb1FQYvKaCM7UsS6Y/u8jVBwIUGeCTk=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.2/go.mod h1:hwRpqkRxnQ58J9blRDrB4IanlXCpcKmsC83EhG77upg=
github.com/aws/aws-sdk-go-v2/service/sts v1.33.19 h1:1XuUZ8mYJw9B6lzAkXhqHlJd/XvaX32evhproijJEZY=
github.com/aws/aws-sdk-go-v2/service/sts v1.33.19/go.mod h1:cQnB8CUnxbMU82JvlqjKR2HBOm3fe9pWorWBza6MBJ4=
github.com/aws/aws-sdk-go-v2/service/sts v1.33.21 h1:nyLjs8sYJShFYj6aiyjCBI3EcLn1udWrQTjEF+SOXB0=
github.com/aws/aws-sdk-go-v2/service/sts v1.33.21/go.mod h1:EhdxtZ+g84MSGrSrHzZiUm9PYiZkrADNja15wtRJSJo=
github.com/aws/smithy-go v1.22.3 h1:Z//5NuZCSW6R4PhQ93hShNbyBbn8BWCmCVCt+Q8Io5k=
github.com/aws/smithy-go v1.22.3/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.7 h1:BszAktdUo2xlzmYHjWMq70DqJ7cROM8iBd3f6hrpuMQ=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.7/go.mod h1:XJ1yHki/P7ZPuG4fd3f0Pg/dSGA2cTQBCLw82MH2H48=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 h1:oegbebPEMA/1Jny7kvwejowCaHz1FWZAQ94WXFNCyTM=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1/go.mod h1:kemo5Myr9ac0U9JfSjMo9yHLtw+pECEHsFtJ9tqCEI8=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.7 h1:zmZ8qvtE9chfhBPuKB2aQFxW5F/rpwXUgmcVCgQzqRw=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.7/go.mod h1:vVYfbpd2l+pKqlSIDIOgouxNsGu5il9uDp0ooWb0jys=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.7 h1:mLgc5QIgOy26qyh5bvW+nDoAppxgn3J2WV3m9ewq7+8=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.7/go.mod h1:wXb/eQnqt8mDQIQTTmcw58B5mYGxzLGZGK8PWNFZ0BA=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.7 h1:u3VbDKUCWarWiU+aIUK4gjTr/wQFXV17y3hgNno9fcA=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.7/go.mod h1:/OuMQwhSyRapYxq6ZNpPer8juGNrB4P5Oz8bZ2cgjQE=
github.com/aws/aws-sdk-go-v2/service/s3 v1.88.0 h1:k5JXPr+2SrPDwM3PdygZUenn0lVPLa3KOs7cCYqinFs=
github.com/aws/aws-sdk-go-v2/service/s3 v1.88.0/go.mod h1:xajPTguLoeQMAOE44AAP2RQoUhF8ey1g5IFHARv71po=
github.com/aws/aws-sdk-go-v2/service/sns v1.38.2 h1:Djc2m7mTPuizL1iMxJfMc209PDy2AqiN1AXrtq/rBdY=
github.com/aws/aws-sdk-go-v2/service/sns v1.38.2/go.mod h1:kHMCS+JDWKuKSDP9J/v3dlV2S9zNBKbXzaLy/kHSdEE=
github.com/aws/aws-sdk-go-v2/service/sso v1.29.2 h1:rcoTaYOhGE/zfxE1uR6X5fvj+uKkqeCNRE0rBbiQM34=
github.com/aws/aws-sdk-go-v2/service/sso v1.29.2/go.mod h1:Ql6jE9kyyWI5JHn+61UT/Y5Z0oyVJGmgmJbZD5g4unY=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.34.3 h1:BSIfeFtU9tlSt8vEYS7KzurMoAuYzYPWhcZiMtxVf2M=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.34.3/go.mod h1:XclEty74bsGBCr1s0VSaA11hQ4ZidK4viWK7rRfO88I=
github.com/aws/aws-sdk-go-v2/service/sts v1.38.3 h1:yEiZ0ztgji2GsCb/6uQSITXcGdtmWMfLRys0jJFiUkc=
github.com/aws/aws-sdk-go-v2/service/sts v1.38.3/go.mod h1:Z+Gd23v97pX9zK97+tX4ppAgqCt3Z2dIXB02CtBncK8=
github.com/aws/smithy-go v1.23.0 h1:8n6I3gXzWJB2DxBDnfxgBaSX6oe0d/t10qGz7OKqMCE=
github.com/aws/smithy-go v1.23.0/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
github.com/aymanbagabas/go-osc52 v1.2.1 h1:q2sWUyDcozPLcLabEMd+a+7Ea2DitxZVN9hTxab9L4E=
github.com/aymanbagabas/go-osc52 v1.2.1/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8=
github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
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/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
@@ -205,20 +161,16 @@ 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/bill-rich/go-syslog v0.0.0-20220413021637-49edb52a574c h1:tSME5FDS02qQll3JYodI6RZR/g4EKOHApGv1wMZT+Z0=
github.com/bill-rich/go-syslog v0.0.0-20220413021637-49edb52a574c/go.mod h1:+sCc6hztur+oZCLOsNk6wCCy+GLrnSNHSRmTnnL+8iQ=
github.com/bitfinexcom/bitfinex-api-go v0.0.0-20210608095005-9e0b26f200fb h1:9v7Bzlg+1EBYi2IYcUmOwHReBEfqBbYIj3ZCi9cIe1Q=
github.com/bitfinexcom/bitfinex-api-go v0.0.0-20210608095005-9e0b26f200fb/go.mod h1:EkOqCuelvo7DY8vCOoZ09p7pHvAK9B1PHI9GeM4Rdxc=
github.com/bodgit/plumbing v1.3.0 h1:pf9Itz1JOQgn7vEOE7v7nlEfBykYqvUYioC61TwWCFU=
github.com/bodgit/plumbing v1.3.0/go.mod h1:JOTb4XiRu5xfnmdnDJo6GmSbSbtSyufrsyZFByMtKEs=
github.com/bodgit/sevenzip v1.6.0 h1:a4R0Wu6/P1o1pP/3VV++aEOcyeBxeO/xE2Y9NSTrr6A=
github.com/bodgit/sevenzip v1.6.0/go.mod h1:zOBh9nJUof7tcrlqJFv1koWRrhz3LbDbUNngkuZxLMc=
github.com/bodgit/windows v1.0.1 h1:tF7K6KOluPYygXa3Z2594zxlkbKPAOvqr97etrGNIz4=
github.com/bodgit/windows v1.0.1/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM=
github.com/bradleyfalzon/ghinstallation/v2 v2.12.0 h1:k8oVjGhZel2qmCUsYwSE34jPNT9DL2wCBOtugsHv26g=
github.com/bradleyfalzon/ghinstallation/v2 v2.12.0/go.mod h1:V4gJcNyAftH0rXpRp1SUVUuh+ACxOH1xOk/ZzkRHltg=
github.com/bradleyfalzon/ghinstallation/v2 v2.14.0 h1:0D4vKCHOvYrDU8u61TnE2JfNT4VRrBLphmxtqazTO+M=
github.com/bradleyfalzon/ghinstallation/v2 v2.14.0/go.mod h1:LOVmdZYVZ8jqdr4n9wWm1ocDiMz9IfMGfRkaYC1a52A=
github.com/brianvoe/gofakeit/v7 v7.2.1 h1:AGojgaaCdgq4Adzrd2uWdbGNDyX6MWNhHdQBraNfOHI=
github.com/brianvoe/gofakeit/v7 v7.2.1/go.mod h1:QXuPeBw164PJCzCUZVmgpgHJ3Llj49jSLVkKPMtxtxA=
github.com/bradleyfalzon/ghinstallation/v2 v2.16.0 h1:B91r9bHtXp/+XRgS5aZm6ZzTdz3ahgJYmkt4xZkgDz8=
github.com/bradleyfalzon/ghinstallation/v2 v2.16.0/go.mod h1:OeVe5ggFzoBnmgitZe/A+BqGOnv1DvU/0uiLQi1wutM=
github.com/brianvoe/gofakeit/v7 v7.6.0 h1:M3RUb5CuS2IZmF/cP+O+NdLxJEuDAZxNQBwPbbqR6h4=
github.com/brianvoe/gofakeit/v7 v7.6.0/go.mod h1:QXuPeBw164PJCzCUZVmgpgHJ3Llj49jSLVkKPMtxtxA=
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@@ -226,18 +178,22 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/39KLfy0=
github.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw=
github.com/charmbracelet/bubbletea v1.3.4 h1:kCg7B+jSCFPLYRA52SDZjr51kG/fMUEoPoZrkaDHyoI=
github.com/charmbracelet/bubbletea v1.3.4/go.mod h1:dtcUCyCGEX3g9tosuYiut3MXgY/Jsv9nKVdibKKRRXo=
github.com/charmbracelet/bubbletea v1.3.6 h1:VkHIxPJQeDt0aFJIsVxw8BQdh/F/L2KKZGsK6et5taU=
github.com/charmbracelet/bubbletea v1.3.6/go.mod h1:oQD9VCRQFF8KplacJLo28/jofOI2ToOfGYeFgBBxHOc=
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
github.com/charmbracelet/glamour v0.7.0 h1:2BtKGZ4iVJCDfMF229EzbeR1QRKLWztO9dMtjmqZSng=
github.com/charmbracelet/glamour v0.7.0/go.mod h1:jUMh5MeihljJPQbJ/wf4ldw2+yBP59+ctV36jASy7ps=
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE=
github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q=
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8=
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
github.com/charmbracelet/glamour v0.10.0 h1:MtZvfwsYCx8jEPFJm3rIBFIMZUfUJ765oX8V6kXldcY=
github.com/charmbracelet/glamour v0.10.0/go.mod h1:f+uf+I/ChNmqo087elLnVdCiVgjSKWuXa/l6NU2ndYk=
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 h1:ZR7e0ro+SZZiIZD7msJyA+NjkCNNavuiPBLgerbOziE=
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834/go.mod h1:aKC/t2arECF6rNOnaKaVU6y4t4ZeHQzqfxedE/VkVhA=
github.com/charmbracelet/x/ansi v0.9.3 h1:BXt5DHS/MKF+LjuK4huWrC6NCvHtexww7dMayh6GXd0=
github.com/charmbracelet/x/ansi v0.9.3/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k=
github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a h1:G99klV19u0QnhiizODirwVksQB91TJKV/UaTnACcG30=
github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf h1:rLG0Yb6MQSDKdB52aGX55JT1oi0P0Kuaj7wi1bLUpnI=
github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf/go.mod h1:B3UgsnsBZS/eX42BlaNiJkD1pPOUa+oF1IYC6Yd2CEU=
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
github.com/chengxilo/virtualterm v1.0.4 h1:Z6IpERbRVlfB8WkOmtbHiDbBANU7cimRIof7mk9/PwM=
@@ -252,21 +208,25 @@ github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObk
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/circl v1.3.8 h1:j+V8jJt09PoeMFIu2uh5JUyEaIHTXVOHslFoLNAKqwI=
github.com/cloudflare/circl v1.3.8/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU=
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 h1:Om6kYQYDUk5wWbT0t0q6pvyM49i9XZAv9dDrkDA7gjk=
github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 h1:aQ3y1lwWyqYPiWZThqv1aFbZMiM9vblcSArJRf2Irls=
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=
github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=
github.com/containerd/stargz-snapshotter/estargz v0.16.3 h1:7evrXtoh1mSbGj/pfRccTampEyKpjpOnS3CyiV1Ebr8=
github.com/containerd/stargz-snapshotter/estargz v0.16.3/go.mod h1:uyr4BfYfOj3G9WBVE8cOlQmXAbPN9VEQpBBeJIuOipU=
github.com/couchbase/gocb/v2 v2.10.0 h1:NNxZ4okToU1Ylqp6F8tE41CEJQPhb2WjufryAkeubOk=
github.com/couchbase/gocb/v2 v2.10.0/go.mod h1:OSbMfQkP7ltbKiDZhsT2mGDhkQNmvGXxptKcxAUJQ2Y=
github.com/couchbase/gocbcore/v10 v10.7.0 h1:lAEi0PNeEGKOu8pWrPUdtLOT2oGr1J/UTdGHVPC3r/0=
github.com/couchbase/gocbcore/v10 v10.7.0/go.mod h1:Q8JWVenMCEOuRgrDQKApHbzzPif38HzefGgRVe9apAI=
github.com/couchbase/gocb/v2 v2.11.0 h1:OVB+KlVeXlKVtziKx/LWZT7DClLsoQHQFrI4wan5Ijc=
github.com/couchbase/gocb/v2 v2.11.0/go.mod h1:Y+lODSgyVzDSaf0Sy8sIzIa0RTAw3vlZUsjY6+FUq9Y=
github.com/couchbase/gocbcore/v10 v10.8.0 h1:zDcJyYqOirFyC8T/aVvNL4N9oj6GI4qtaBuTGGWCDb4=
github.com/couchbase/gocbcore/v10 v10.8.0/go.mod h1:OWKfU9R5Nm5V3QZBtfdZl5qCfgxtxTqOgXiNr4pn9/c=
github.com/couchbase/gocbcoreps v0.1.3 h1:fILaKGCjxFIeCgAUG8FGmRDSpdrRggohOMKEgO9CUpg=
github.com/couchbase/gocbcoreps v0.1.3/go.mod h1:hBFpDNPnRno6HH5cRXExhqXYRmTsFJlFHQx7vztcXPk=
github.com/couchbase/goprotostellar v1.0.2 h1:yoPbAL9sCtcyZ5e/DcU5PRMOEFaJrF9awXYu3VPfGls=
@@ -291,16 +251,16 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/docker/cli v27.5.0+incompatible h1:aMphQkcGtpHixwwhAXJT1rrK/detk2JIvDaFkLctbGM=
github.com/docker/cli v27.5.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/docker/cli v28.2.2+incompatible h1:qzx5BNUDFqlvyq4AHzdNB7gSyVTmU4cgsyN9SdInc1A=
github.com/docker/cli v28.2.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v27.5.0+incompatible h1:um++2NcQtGRTz5eEgO6aJimo6/JxrTXC941hd05JO6U=
github.com/docker/docker v27.5.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo=
github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=
github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI=
github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8=
github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
@@ -342,8 +302,8 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
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/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0=
github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/getsentry/sentry-go v0.32.0 h1:YKs+//QmwE3DcYtfKRH8/KyOOF/I6Qnx7qYGNHCGmCY=
github.com/getsentry/sentry-go v0.32.0/go.mod h1:CYNcMMz73YigoHljQRG+qPF+eMq8gG72XcGN/p71BAY=
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
@@ -362,17 +322,13 @@ github.com/go-git/go-git/v5 v5.13.2 h1:7O7xvsK7K+rZPKW6AQR1YyNhfywkv7B8/FsP3ki6Z
github.com/go-git/go-git/v5 v5.13.2/go.mod h1:hWdW5P4YZRjmpGHwRH2v3zkWcNl6HeXaXQEMGb3NJ9A=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-jose/go-jose/v4 v4.0.2 h1:R3l3kkBds16bO7ZFAEEcofK0MkrAJt3jlJznWZG0nvk=
github.com/go-jose/go-jose/v4 v4.0.2/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
github.com/go-jose/go-jose/v4 v4.0.4 h1:VsjPI33J0SB9vQM6PLmNjoHqMQNGPiZ0rHL7Ni7Q6/E=
github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc=
github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-ldap/ldap/v3 v3.4.11 h1:4k0Yxweg+a3OyBLjdYn5OKglv18JNvfDykSoI8bW0gU=
github.com/go-ldap/ldap/v3 v3.4.11/go.mod h1:bY7t0FLK8OAVpp/vV6sSlpz3EQDGcQwc8pF0ujLgKvM=
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=
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/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
@@ -389,26 +345,22 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
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/gobwas/httphead v0.0.0-20200921212729-da3d93bc3c58/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.0.4/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
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 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo=
github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v5 v5.2.3 h1:kkGXqQOBSDDWRhWNXTFpqGSCMyh/PLnqUvMGJPDJDs0=
github.com/golang-jwt/jwt/v5 v5.2.3/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.2.4 h1:CNNw5U8lSiiBk7druxtSHHTsRWcxKoac6kZKm2peBBc=
github.com/golang/glog v1.2.4/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -430,8 +382,8 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
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 v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@@ -445,14 +397,12 @@ github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
github.com/google/go-cmp v0.6.0/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-containerregistry v0.20.3 h1:oNx7IdTI936V8CQRveCjaxOiegWwvM7kqkbXTpyiovI=
github.com/google/go-containerregistry v0.20.3/go.mod h1:w00pIgBRDVUDFM6bq+Qx8lwNWK+cxgCuX1vd3PIBDNI=
github.com/google/go-github/v66 v66.0.0 h1:ADJsaXj9UotwdgK8/iFZtv7MLc8E8WBl62WLd/D/9+M=
github.com/google/go-github/v66 v66.0.0/go.mod h1:+4SO9Zkuyf8ytMj0csN1NR/5OTR+MfqPp8P8dVlcvY4=
github.com/google/go-containerregistry v0.20.6 h1:cvWX87UxxLgaH76b4hIvya6Dzz9qHB31qAwjAohdSTU=
github.com/google/go-containerregistry v0.20.6/go.mod h1:T0x8MuoAoKX/873bkeSfLD2FAkwCDf9/HZgsFJ02E2Y=
github.com/google/go-github/v67 v67.0.0 h1:g11NDAmfaBaCO8qYdI9fsmbaRipHNWRIU/2YGvlh4rg=
github.com/google/go-github/v67 v67.0.0/go.mod h1:zH3K7BxjFndr9QSeFibx4lTKkYS3K9nDanoI1NjaOtY=
github.com/google/go-github/v69 v69.0.0 h1:YnFvZ3pEIZF8KHmI8xyQQe3mYACdkhnaTV2hr7CP2/w=
github.com/google/go-github/v69 v69.0.0/go.mod h1:xne4jymxLR6Uj9b7J7PyTpkMYstEMMwGZa0Aehh1azM=
github.com/google/go-github/v72 v72.0.0 h1:FcIO37BLoVPBO9igQQ6tStsv2asG4IPcYFi655PPvBM=
github.com/google/go-github/v72 v72.0.0/go.mod h1:WWtw8GMRiL62mvIquf1kO3onRHeWWKmK01qdCY8c5fg=
github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
@@ -474,13 +424,10 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU
github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q=
github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA=
github.com/googleapis/gax-go/v2 v2.14.2 h1:eBLnkZ9635krYIPD+ag1USrOAI0Nr0QYF3+/3GqO0k0=
github.com/googleapis/gax-go/v2 v2.14.2/go.mod h1:ON64QhlJkhVtSqp4v1uaK92VyZ2gmvDQsweuyLV+8+w=
github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo=
github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc=
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.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI=
@@ -498,8 +445,8 @@ github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB1
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48=
github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw=
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/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
@@ -533,8 +480,8 @@ github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh6
github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
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/jedib0t/go-pretty/v6 v6.6.8 h1:JnnzQeRz2bACBobIaa/r+nqjvws4yEhcmaZ4n1QzsEc=
github.com/jedib0t/go-pretty/v6 v6.6.8/go.mod h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU=
github.com/jlaffaye/ftp v0.2.0 h1:lXNvW7cBu7R/68bknOX3MrRIIqZ61zELs1P2RAiA3lg=
github.com/jlaffaye/ftp v0.2.0/go.mod h1:is2Ds5qkhceAPy2xD6RLI6hmp/qysSoymZ+Z2uTnspI=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
@@ -553,8 +500,6 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
github.com/kjk/lzma v0.0.0-20161016003348-3fd93898850d h1:RnWZeH8N8KXfbwMTex/KKMYMj0FJRCF6tQubUuQ02GM=
github.com/kjk/lzma v0.0.0-20161016003348-3fd93898850d/go.mod h1:phT/jsRPBAEqjAibu1BurrabCBNTYiVI+zbmyCZJY6Q=
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
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 v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
@@ -591,7 +536,6 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
@@ -599,8 +543,6 @@ github.com/mholt/archives v0.0.0-20241216060121-23e0af8fe73d h1:Vw3f39TqFSQLA+Oy
github.com/mholt/archives v0.0.0-20241216060121-23e0af8fe73d/go.mod h1:j/Ire/jm42GN7h90F5kzj6hf6ZFzEH66de+hmjEKu+I=
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/microsoft/go-mssqldb v1.8.0 h1:7cyZ/AT7ycDsEoWPIXibd+aVKFtteUNhDGf3aobP+tw=
github.com/microsoft/go-mssqldb v1.8.0/go.mod h1:6znkekS3T2vp0waiMhen4GPU1BiAsrP+iXHcE7a7rFo=
github.com/microsoft/go-mssqldb v1.8.2 h1:236sewazvC8FvG6Dr3bszrVhMkAl4KYImryLkRMCd0I=
github.com/microsoft/go-mssqldb v1.8.2/go.mod h1:vp38dT33FGfVotRiTmDo3bFyaHq+p3LektQrjTULowo=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
@@ -611,12 +553,16 @@ github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ=
github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo=
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc=
github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo=
github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg=
github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU=
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=
github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
@@ -638,13 +584,11 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4=
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nwaples/rardecode/v2 v2.0.0-beta.4.0.20241112120701-034e449c6e78 h1:MYzLheyVx1tJVDqfu3YnN4jtnyALNzLvwl+f58TcvQY=
github.com/nwaples/rardecode/v2 v2.0.0-beta.4.0.20241112120701-034e449c6e78/go.mod h1:yntwv/HfMc/Hbvtq9I19D1n58te3h6KsqCf3GxyfBGY=
github.com/nwaples/rardecode/v2 v2.2.1 h1:DgHK/O/fkTQEKBJxBMC5d9IU8IgauifbpG78+rZJMnI=
github.com/nwaples/rardecode/v2 v2.2.1/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
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 v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
@@ -653,11 +597,10 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
@@ -672,8 +615,6 @@ github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/diff v0.0.0-20200914180035-5b29258ca4f7 h1:+/+DxvQaYifJ+grD4klzrS5y+KJXldn/2YTl5JG+vZ8=
github.com/pkg/diff v0.0.0-20200914180035-5b29258ca4f7/go.mod h1:zO8QMzTeZd5cpnIkz/Gn6iK0jDfGicM1nynOkkPIl28=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -715,9 +656,8 @@ github.com/schollz/progressbar/v3 v3.17.1 h1:bI1MTaoQO+v5kzklBjYNRQLoVpe0zbyRZNK
github.com/schollz/progressbar/v3 v3.17.1/go.mod h1:RzqpnsPQNjUyIgdglUjRLgD7sVnxN1wpmBMV+UiEbL4=
github.com/sendgrid/rest v2.6.9+incompatible h1:1EyIcsNdn9KIisLW50MKwmSRSK+ekueiEMJ7NEoxJo0=
github.com/sendgrid/rest v2.6.9+incompatible/go.mod h1:kXX7q3jZtJXK5c5qK83bSGMdV6tsOE70KbHoqJls4lE=
github.com/sendgrid/sendgrid-go v3.16.0+incompatible h1:i8eE6IMkiCy7vusSdacHHSBUpXyTcTXy/Rl9N9aZ/Qw=
github.com/sendgrid/sendgrid-go v3.16.0+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sendgrid/sendgrid-go v3.16.1+incompatible h1:zWhTmB0Y8XCDzeWIm2/BIt1GjJohAA0p6hVEaDtHWWs=
github.com/sendgrid/sendgrid-go v3.16.1+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8=
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/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4=
@@ -728,6 +668,10 @@ github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
github.com/shuheiktgw/go-travis v0.3.1 h1:SAT16mi77ccqogOslnXxBXzXbpeyChaIYUwi2aJpVZY=
github.com/shuheiktgw/go-travis v0.3.1/go.mod h1:avnFFDqJDdRHwlF9tgqvYi3asQCm/HGL8aLxYiKa4Yg=
github.com/shurcooL/githubv4 v0.0.0-20240727222349-48295856cce7 h1:cYCy18SHPKRkvclm+pWm1Lk4YrREb4IOIb/YdFO0p2M=
github.com/shurcooL/githubv4 v0.0.0-20240727222349-48295856cce7/go.mod h1:zqMwyHmnN/eDOZOdiTohqIUKUrTFX62PNlu7IJdu0q8=
github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 h1:17JxqqJY66GmZVHkmAsGEkcIu0oCe3AM420QDgGwZx0=
github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466/go.mod h1:9dIRpgIY7hVhoqfe0/FcYp0bpInZaT7dc3BYOprrIUE=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
@@ -752,7 +696,6 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
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.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
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=
@@ -760,8 +703,6 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
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/tailscale/depaware v0.0.0-20250112153213-b748de04d81b h1:ewWb4cA+YO9/3X+v5UhdV+eKFsNBOPcGRh39Glshx/4=
github.com/tailscale/depaware v0.0.0-20250112153213-b748de04d81b/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8=
github.com/testcontainers/testcontainers-go v0.34.0 h1:5fbgF0vIN5u+nD3IWabQwRybuB4GY8G2HHgCkbMzMHo=
github.com/testcontainers/testcontainers-go v0.34.0/go.mod h1:6P/kMkQe8yqPHfPWNulFGdFHTD8HB2vLq/231xY2iPQ=
github.com/testcontainers/testcontainers-go/modules/elasticsearch v0.34.0 h1:BBwJUs9xBpt1uOfO+yAr2pYW75MsyzuO/o70HTPnhe4=
@@ -788,13 +729,11 @@ github.com/trufflesecurity/overseer v1.2.8 h1:VXlWPiwYaQmwNxY2W1rVulEAG9O6iF1S0L
github.com/trufflesecurity/overseer v1.2.8/go.mod h1:Dt6Y9LFpM+C/3rRWpy4//4iS5qrbb0pL3XvZqMd4zhg=
github.com/trufflesecurity/touchfile v0.1.1 h1:Snhd5VEa8Cxd+D60nvLEj2kVeb1omY2tWwnhDhjTqdo=
github.com/trufflesecurity/touchfile v0.1.1/go.mod h1:Yg/AUMrxAk+dWDUjIig0OyGgFOHFuWNw+t2S/GvO6Mk=
github.com/trufflesecurity/waas-client-library-go v1.0.9 h1:dUS5s8/guamH5gHA0MvTmf5Qw2UILOHEayYIINU8FOw=
github.com/trufflesecurity/waas-client-library-go v1.0.9/go.mod h1:0O/2T0CPq0wm8Wl9KEJklKbiRNQNZ1MOh4nCYY5V2zo=
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/vbatts/tar-split v0.11.6 h1:4SjTW5+PU11n6fZenf2IPoV8/tz3AaYHMWjf23envGs=
github.com/vbatts/tar-split v0.11.6/go.mod h1:dqKNtesIOr2j2Qv3W/cHjnvk9I8+G7oAkFDFN6TCBEI=
github.com/vbatts/tar-split v0.12.1 h1:CqKoORW7BUWBe7UL/iqTVvkTBOF8UvOMKOIZykxnnbo=
github.com/vbatts/tar-split v0.12.1/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA=
github.com/wasilibs/go-re2 v1.9.0 h1:kjAd8qbNvV4Ve2Uf+zrpTCrDHtqH4dlsRXktywo73JQ=
github.com/wasilibs/go-re2 v1.9.0/go.mod h1:0sRtscWgpUdNA137bmr1IUgrRX0Su4dcn9AEe61y+yI=
github.com/wasilibs/wazero-helpers v0.0.0-20240620070341-3dff1577cd52 h1:OvLBa8SqJnZ6P+mjlzc2K7PM22rRUPE1x32G9DTPrC4=
@@ -811,8 +750,8 @@ github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8
github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
github.com/xo/dburl v0.23.7 h1:UCiK8Dyll38NdDHVi7UOxhz5/ugWuyQGgQHdxfdEQDY=
github.com/xo/dburl v0.23.7/go.mod h1:uazlaAQxj4gkshhfuuYyvwCBouOmNnG2aDxTCFZpmL4=
github.com/xo/dburl v0.23.8 h1:NwFghJfjaUW7tp+WE5mTLQQCfgseRsvgXjlSvk7x4t4=
github.com/xo/dburl v0.23.8/go.mod h1:uazlaAQxj4gkshhfuuYyvwCBouOmNnG2aDxTCFZpmL4=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
@@ -821,50 +760,48 @@ github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zU
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.7/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.5.4 h1:2uY/xC0roWy8IBEGLgB1ywIoEJFGmRrX21YQcvGZzjU=
github.com/yuin/goldmark v1.5.4/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark-emoji v1.0.2 h1:c/RgTShNgHTtc6xdz2KKI74jJr6rWi7FPgnP9GAsO5s=
github.com/yuin/goldmark-emoji v1.0.2/go.mod h1:RhP/RWpexdp+KHs7ghKnifRoIs/Bq4nDS7tRbCkOwKY=
github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
github.com/yuin/goldmark-emoji v1.0.5 h1:EMVWyCGPlXJfUXBXpuMu+ii3TIaxbVBnEX9uaDC4cIk=
github.com/yuin/goldmark-emoji v1.0.5/go.mod h1:tTkZEbwu5wkPmgTcitqddVxY9osFZiavD+r4AzQrh1U=
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM=
github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
gitlab.com/gitlab-org/api/client-go v0.127.0 h1:8xnxcNKGF2gDazEoMs+hOZfOspSSw8D0vAoWhQk9U+U=
gitlab.com/gitlab-org/api/client-go v0.127.0/go.mod h1:bYC6fPORKSmtuPRyD9Z2rtbAjE7UeNatu2VWHRf4/LE=
go.einride.tech/aip v0.60.0 h1:h6bgabZ5BCfAptbGex8jbh3VvPBRLa6xq+pQ1CAjHYw=
go.einride.tech/aip v0.60.0/go.mod h1:SdLbSbgSU60Xkb4TMkmsZEQPHeEWx0ikBoq5QnqZvdg=
go.mongodb.org/mongo-driver v1.17.3 h1:TQyXhnsWfWtgAhMtOgtYHMTkZIfBTpMTsMnd9ZBeHxQ=
go.mongodb.org/mongo-driver v1.17.3/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
gitlab.com/gitlab-org/api/client-go v0.129.0 h1:o9KLn6fezmxBQWYnQrnilwyuOjlx4206KP0bUn3HuBE=
gitlab.com/gitlab-org/api/client-go v0.129.0/go.mod h1:ZhSxLAWadqP6J9lMh40IAZOlOxBLPRh7yFOXR/bMJWM=
go.mongodb.org/mongo-driver v1.17.4 h1:jUorfmVzljjr0FLzYQsGP8cgN/qzzxlY9Vh0C9KFXVw=
go.mongodb.org/mongo-driver v1.17.4/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
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/detectors/gcp v1.34.0 h1:JRxssobiPg23otYU5SbWtQC//snGVIM3Tx6QRzlQBao=
go.opentelemetry.io/contrib/detectors/gcp v1.34.0/go.mod h1:cV4BMFcscUR/ckqLkbfQmF0PRsq8w/lMGzdbCSveBHo=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
go.opentelemetry.io/contrib/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8FktLnIcy6jJNyOCQw=
go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.62.0 h1:rbRJ8BBoVMsQShESYZ0FkvcITu8X8QNwJogcLUmDNNw=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.62.0/go.mod h1:ru6KHrNtNHxM4nD/vd6QrLVWgKhxPYgblq4VAtNawTQ=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0 h1:wpMfgF8E1rkrT1Z6meFh1NDtownE9Ii3n3X2GJYjsaU=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0/go.mod h1:wAy0T/dUbs468uOlkT31xjvqQgEVXv58BRFWEgn5v/0=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0 h1:WDdP9acbMYjbKIyJUhTvtzj601sVJOqgWdUxSdR/Ysc=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0/go.mod h1:BLbf7zbNIONBLPwvFnwNHGj4zge8uTCM/UPIVW1Mq2I=
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
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.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw=
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
@@ -873,8 +810,8 @@ go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwE
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=
go.uber.org/mock v0.5.1 h1:ASgazW/qBmR+A32MYFDB6E2POoTgOwT509VP0CT/fjs=
go.uber.org/mock v0.5.1/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
@@ -890,8 +827,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
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.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -920,10 +857,9 @@ golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
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.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -945,15 +881,13 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
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/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98=
golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -964,10 +898,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/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-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.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-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -990,7 +922,6 @@ golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7w
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-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1010,13 +941,13 @@ 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.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.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.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -1026,12 +957,12 @@ 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.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
golang.org/x/time v0.12.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=
@@ -1058,12 +989,9 @@ golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapK
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -1076,10 +1004,8 @@ google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsb
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.229.0 h1:p98ymMtqeJ5i3lIBMj5MpR9kzIIgzpHHh8vQ+vgAzx8=
google.golang.org/api v0.229.0/go.mod h1:wyDfmq5g1wYJWn29O22FDWN48P7Xcz0xz+LBpptYvB0=
google.golang.org/api v0.232.0 h1:qGnmaIMf7KcuwHOlF3mERVzChloDYwRfOJOrHt8YC3I=
google.golang.org/api v0.232.0/go.mod h1:p9QCfBWZk1IJETUdbTKloR5ToFdKbYh2fkjsUL6vNoY=
google.golang.org/api v0.247.0 h1:tSd/e0QrUlLsrwMKmkbQhYVa109qIintOls2Wh6bngc=
google.golang.org/api v0.247.0/go.mod h1:r1qZOPmxXffXg6xS5uhx16Fa/UFY8QU/K4bfKrnvovM=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -1099,18 +1025,12 @@ google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvx
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb h1:ITgPrl429bc6+2ZraNSzMDk3I95nmQln2fuPstKwFDE=
google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:sAo5UzpjUwgFBCzupwhcLcxHVDK7vG5IqI30YnwX2eE=
google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 h1:1tXaIXCracvtsRxSBsYDiSBN0cuJvM7QYW+MrpIRY78=
google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:49MsLSx0oWMOZqcpB3uL8ZOkAh1+TndpJ8ONoCBWiZk=
google.golang.org/genproto/googleapis/api v0.0.0-20250414145226-207652e42e2e h1:UdXH7Kzbj+Vzastr5nVfccbmFsmYNygVLSPk1pEfDoY=
google.golang.org/genproto/googleapis/api v0.0.0-20250414145226-207652e42e2e/go.mod h1:085qFyf2+XaZlRdCgKNCIZ3afY2p4HHZdoIRpId8F4A=
google.golang.org/genproto/googleapis/api v0.0.0-20250505200425-f936aa4a68b2 h1:vPV0tzlsK6EzEDHNNH5sa7Hs9bd7iXR7B1tSiPepkV0=
google.golang.org/genproto/googleapis/api v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:pKLAc5OolXC3ViWGI62vvC0n10CpwAtRcTNCFwTKBEw=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250414145226-207652e42e2e h1:ztQaXfzEXTmCBvbtWYRhJxW+0iJcz2qXfd38/e9l7bA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250414145226-207652e42e2e/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250505200425-f936aa4a68b2 h1:IqsN8hx+lWLqlN+Sc3DoMy/watjofWiU8sRFgQ8fhKM=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4=
google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s=
google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c h1:AtEkQdl5b6zsybXcbz00j1LwNodDuH6hVifIaNqk7NQ=
google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c/go.mod h1:ea2MjsO70ssTfCjiwHgI0ZFqcw45Ksuk2ckf9G468GA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c h1:qXWI/sQtv5UKboZ/zUk7h+mrf/lXORyI+n9DKDAusdg=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@@ -1120,18 +1040,16 @@ google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI=
google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM=
google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4=
google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
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-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -1156,8 +1074,8 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+2 -2
View File
@@ -57,7 +57,7 @@ Note: Be sure to update the tests to reference the new secret values in GSM, or
3. Add a 'version' field in ExtraData for both existing and new detector versions.
4. Update the existing detector in DefaultDetectors in `/pkg/engine/defaults.go`
4. Update the existing detector in DefaultDetectors in `/pkg/engine/defaults/defaults.go`
5. Proceed from step 3 of [Creating a new Secret Scanner](#creating-a-new-secret-scanner)
@@ -87,7 +87,7 @@ Note: Be sure to update the tests to reference the new secret values in GSM, or
4. Found and unverified (indeterminately due to an unexpected API response)
5. Not found
6. Any false positive cases that you come across
5. Add your new detector to DefaultDetectors in `/pkg/engine/defaults.go`
5. Add your new detector to DefaultDetectors in `/pkg/engine/defaults/defaults.go`
6. Create a merge request for review. CI tests must be passing.
## Addendum
+4 -4
View File
@@ -53,7 +53,7 @@ Note: Be sure to update the tests to reference the new secret values in GSM, or
3. Add a 'version' field in ExtraData for both existing and new detector versions.
4. Update the existing detector in DefaultDetectors in `/pkg/engine/defaults.go`
4. Update the existing detector in DefaultDetectors in `/pkg/engine/defaults/defaults.go`
5. Proceed from step 3 of [Creating a new Secret Scanner](#creating-a-new-secret-scanner)
@@ -71,8 +71,8 @@ Note: Be sure to update the tests to reference the new secret values in GSM, or
```
4. Add the Secret Detector to TruffleHog's Default Detectors
Add the secret scanner to the [`pkg/engine/defaults.go`](https://github.com/trufflesecurity/trufflehog/blob/main/pkg/engine/defaults.go) file like [`github.com/trufflesecurity/trufflehog/v3/pkg/detectors/<detector_name>`](https://github.com/trufflesecurity/trufflehog/blob/b71ea27a696bdf1c3141f637fda4ee4936c2f2d6/pkg/engine/defaults.go#L9) and
[`<detector_name>.Scanner{}`](https://github.com/trufflesecurity/trufflehog/blob/b71ea27a696bdf1c3141f637fda4ee4936c2f2d6/pkg/engine/defaults.go#L1546)
Add the secret scanner to the [`pkg/engine/defaults/defaults.go`](https://github.com/trufflesecurity/trufflehog/blob/main/pkg/engine/defaults/defaults.go) file like [`github.com/trufflesecurity/trufflehog/v3/pkg/detectors/<detector_name>`](https://github.com/trufflesecurity/trufflehog/blob/b71ea27a696bdf1c3141f637fda4ee4936c2f2d6/pkg/engine/defaults/defaults.go#L9) and
[`<detector_name>.Scanner{}`](https://github.com/trufflesecurity/trufflehog/blob/b71ea27a696bdf1c3141f637fda4ee4936c2f2d6/pkg/engine/defaults/defaults.go#L1546)
5. Complete the Secret Detector.
@@ -83,7 +83,7 @@ Note: Be sure to update the tests to reference the new secret values in GSM, or
2. Update the verifier code to use a non-destructive API call that can determine whether the secret is valid or not.
* Make sure you understand [verification indeterminacy](#verification-indeterminacy).
3. Create a [test for the detector](#testing-the-detector).
4. Add your new detector to DefaultDetectors in `/pkg/engine/defaults.go`.
4. Add your new detector to DefaultDetectors in `/pkg/engine/defaults/defaults.go`.
5. Create a pull request for review.
### Testing the Detector
+2 -2
View File
@@ -181,7 +181,7 @@ func main() {
defer sem.Release(1)
defer wgChunkers.Done()
logger.Info("cloning repo", "repo", r)
path, repo, err := git.CloneRepoUsingUnauthenticated(ctx, r)
path, repo, err := git.CloneRepoUsingUnauthenticated(ctx, r, "")
if err != nil {
logFatal(err, "error cloning repo", "repo", r)
}
@@ -197,7 +197,7 @@ func main() {
SkipBinaries: true,
SkipArchives: false,
Concurrency: runtime.NumCPU(),
SourceMetadataFunc: func(file, email, commit, timestamp, repository string, line int64) *source_metadatapb.MetaData {
SourceMetadataFunc: func(file, email, commit, timestamp, repository, repositoryLocalPath string, line int64) *source_metadatapb.MetaData {
return &source_metadatapb.MetaData{
Data: &source_metadatapb.MetaData_Git{
Git: &source_metadatapb.Git{
+146 -49
View File
@@ -58,7 +58,7 @@ var (
concurrency = cli.Flag("concurrency", "Number of concurrent workers.").Default(strconv.Itoa(runtime.NumCPU())).Int()
noVerification = cli.Flag("no-verification", "Don't verify the results.").Bool()
onlyVerified = cli.Flag("only-verified", "Only output verified results.").Hidden().Bool()
results = cli.Flag("results", "Specifies which type(s) of results to output: verified, unknown, unverified, filtered_unverified. Defaults to verified,unverified,unknown.").String()
results = cli.Flag("results", "Specifies which type(s) of results to output: verified (confirmed valid by API), unknown (verification failed due to error), unverified (detected but not verified), filtered_unverified (unverified but would have been filtered out). Defaults to verified,unverified,unknown.").String()
noColor = cli.Flag("no-color", "Disable colorized output").Bool()
noColour = cli.Flag("no-colour", "Alias for --no-color").Hidden().Bool()
@@ -72,6 +72,7 @@ var (
printAvgDetectorTime = cli.Flag("print-avg-detector-time", "Print the average time spent on each detector.").Bool()
noUpdate = cli.Flag("no-update", "Don't check for updates.").Bool()
fail = cli.Flag("fail", "Exit with code 183 if results are found.").Bool()
failOnScanErrors = cli.Flag("fail-on-scan-errors", "Exit with non-zero error code if an error occurs during the scan.").Bool()
verifiers = cli.Flag("verifier", "Set custom verification endpoints.").StringMap()
customVerifiersOnly = cli.Flag("custom-verifiers-only", "Only use custom verification endpoints.").Bool()
detectorTimeout = cli.Flag("detector-timeout", "Maximum time to spend scanning chunks per detector (e.g., 30s).").Duration()
@@ -90,18 +91,21 @@ var (
skipAdditionalRefs = cli.Flag("skip-additional-refs", "Skip additional references.").Bool()
userAgentSuffix = cli.Flag("user-agent-suffix", "Suffix to add to User-Agent.").String()
gitScan = cli.Command("git", "Find credentials in git repositories.")
gitScanURI = gitScan.Arg("uri", "Git repository URL. https://, file://, or ssh:// schema expected.").Required().String()
gitScanIncludePaths = gitScan.Flag("include-paths", "Path to file with newline separated regexes for files to include in scan.").Short('i').String()
gitScanExcludePaths = gitScan.Flag("exclude-paths", "Path to file with newline separated regexes for files to exclude in scan.").Short('x').String()
gitScanExcludeGlobs = gitScan.Flag("exclude-globs", "Comma separated list of globs to exclude in scan. This option filters at the `git log` level, resulting in faster scans.").String()
gitScanSinceCommit = gitScan.Flag("since-commit", "Commit to start scan from.").String()
gitScanBranch = gitScan.Flag("branch", "Branch to scan.").String()
gitScanMaxDepth = gitScan.Flag("max-depth", "Maximum depth of commits to scan.").Int()
gitScanBare = gitScan.Flag("bare", "Scan bare repository (e.g. useful while using in pre-receive hooks)").Bool()
_ = gitScan.Flag("allow", "No-op flag for backwards compat.").Bool()
_ = gitScan.Flag("entropy", "No-op flag for backwards compat.").Bool()
_ = gitScan.Flag("regex", "No-op flag for backwards compat.").Bool()
gitScan = cli.Command("git", "Find credentials in git repositories.")
gitScanURI = gitScan.Arg("uri", "Git repository URL. https://, file://, or ssh:// schema expected.").Required().String()
gitScanIncludePaths = gitScan.Flag("include-paths", "Path to file with newline separated regexes for files to include in scan.").Short('i').String()
gitScanExcludePaths = gitScan.Flag("exclude-paths", "Path to file with newline separated regexes for files to exclude in scan.").Short('x').String()
gitScanExcludeGlobs = gitScan.Flag("exclude-globs", "Comma separated list of globs to exclude in scan. This option filters at the `git log` level, resulting in faster scans.").String()
gitScanSinceCommit = gitScan.Flag("since-commit", "Commit to start scan from.").String()
gitScanBranch = gitScan.Flag("branch", "Branch to scan.").String()
gitScanMaxDepth = gitScan.Flag("max-depth", "Maximum depth of commits to scan.").Int()
gitScanBare = gitScan.Flag("bare", "Scan bare repository (e.g. useful while using in pre-receive hooks)").Bool()
gitClonePath = gitScan.Flag("clone-path", "Custom path where the repository should be cloned (default: temp dir).").String()
gitNoCleanup = gitScan.Flag("no-cleanup", "Do not delete cloned repositories after scanning (can only be used with --clone-path).").Bool()
gitTrustLocalGitConfig = gitScan.Flag("trust-local-git-config", "Trust local git config.").Bool()
_ = gitScan.Flag("allow", "No-op flag for backwards compat.").Bool()
_ = gitScan.Flag("entropy", "No-op flag for backwards compat.").Bool()
_ = gitScan.Flag("regex", "No-op flag for backwards compat.").Bool()
githubScan = cli.Command("github", "Find credentials in GitHub repositories.")
githubScanEndpoint = githubScan.Flag("endpoint", "GitHub endpoint.").Default("https://api.github.com").String()
@@ -120,6 +124,9 @@ var (
githubScanGistComments = githubScan.Flag("gist-comments", "Include gist comments in scan.").Bool()
githubCommentsTimeframeDays = githubScan.Flag("comments-timeframe", "Number of days in the past to review when scanning issue, PR, and gist comments.").Uint32()
githubAuthInUrl = githubScan.Flag("auth-in-url", "Embed authentication credentials in repository URLs instead of using secure HTTP headers").Bool()
githubClonePath = githubScan.Flag("clone-path", "Custom path where the repository should be cloned (default: temp dir).").String()
githubNoCleanup = githubScan.Flag("no-cleanup", "Do not delete cloned repositories after scanning (can only be used with --clone-path).").Bool()
githubIgnoreGists = githubScan.Flag("ignore-gists", "Ignore all gists in scan.").Bool()
// GitHub Cross Fork Object Reference Experimental Feature
githubExperimentalScan = cli.Command("github-experimental", "Run an experimental GitHub scan. Must specify at least one experimental sub-module to run: object-discovery.")
@@ -136,11 +143,14 @@ var (
gitlabScanEndpoint = gitlabScan.Flag("endpoint", "GitLab endpoint.").Default("https://gitlab.com").String()
gitlabScanRepos = gitlabScan.Flag("repo", "GitLab repo url. You can repeat this flag. Leave empty to scan all repos accessible with provided credential. Example: https://gitlab.com/org/repo.git").Strings()
gitlabScanToken = gitlabScan.Flag("token", "GitLab token. Can be provided with environment variable GITLAB_TOKEN.").Envar("GITLAB_TOKEN").Required().String()
gitlabScanGroupIds = gitlabScan.Flag("group-id", "GitLab group ID. If provided, it will scan the group and its subgroups. You can repeat this flag.").Strings()
gitlabScanIncludePaths = gitlabScan.Flag("include-paths", "Path to file with newline separated regexes for files to include in scan.").Short('i').String()
gitlabScanExcludePaths = gitlabScan.Flag("exclude-paths", "Path to file with newline separated regexes for files to exclude in scan.").Short('x').String()
gitlabScanIncludeRepos = gitlabScan.Flag("include-repos", `Repositories to include in an org scan. This can also be a glob pattern. You can repeat this flag. Must use Gitlab repo full name. Example: "trufflesecurity/trufflehog", "trufflesecurity/t*"`).Strings()
gitlabScanExcludeRepos = gitlabScan.Flag("exclude-repos", `Repositories to exclude in an org scan. This can also be a glob pattern. You can repeat this flag. Must use Gitlab repo full name. Example: "trufflesecurity/driftwood", "trufflesecurity/d*"`).Strings()
gitlabAuthInUrl = gitlabScan.Flag("auth-in-url", "Embed authentication credentials in repository URLs instead of using secure HTTP headers").Bool()
gitlabClonePath = gitlabScan.Flag("clone-path", "Custom path where the repository should be cloned (default: temp dir)").String()
gitlabNoCleanup = gitlabScan.Flag("no-cleanup", "Do not delete cloned repositories after scanning (can only be used with --clone-path).").Bool()
filesystemScan = cli.Command("filesystem", "Find credentials in a filesystem.")
filesystemPaths = filesystemScan.Arg("path", "Path to file or directory to scan.").Strings()
@@ -178,15 +188,17 @@ var (
syslogProtocol = syslogScan.Flag("protocol", "Protocol to listen on. udp or tcp").String()
syslogTLSCert = syslogScan.Flag("cert", "Path to TLS cert.").String()
syslogTLSKey = syslogScan.Flag("key", "Path to TLS key.").String()
syslogFormat = syslogScan.Flag("format", "Log format. Can be rfc3164 or rfc5424").String()
syslogFormat = syslogScan.Flag("format", "Log format. Can be rfc3164 or rfc5424").Required().String()
circleCiScan = cli.Command("circleci", "Scan CircleCI")
circleCiScanToken = circleCiScan.Flag("token", "CircleCI token. Can also be provided with environment variable").Envar("CIRCLECI_TOKEN").Required().String()
dockerScan = cli.Command("docker", "Scan Docker Image")
dockerScanImages = dockerScan.Flag("image", "Docker image to scan. Use the file:// prefix to point to a local tarball, otherwise a image registry is assumed.").Required().Strings()
dockerScanToken = dockerScan.Flag("token", "Docker bearer token. Can also be provided with environment variable").Envar("DOCKER_TOKEN").String()
dockerExcludePaths = dockerScan.Flag("exclude-paths", "Comma separated list of paths to exclude from scan").String()
dockerScan = cli.Command("docker", "Scan Docker Image")
dockerScanImages = dockerScan.Flag("image", "Docker image to scan. Use the file:// prefix to point to a local tarball, the docker:// prefix to point to the docker daemon, otherwise an image registry is assumed.").Strings()
dockerScanToken = dockerScan.Flag("token", "Docker bearer token. Can also be provided with environment variable").Envar("DOCKER_TOKEN").String()
dockerExcludePaths = dockerScan.Flag("exclude-paths", "Comma separated list of paths to exclude from scan").String()
dockerScanNamespace = dockerScan.Flag("namespace", "Docker namespace (organization or user). For non-Docker Hub registries, include the registry address as well (e.g., ghcr.io/namespace or quay.io/namespace).").String()
dockerScanRegistryToken = dockerScan.Flag("registry-token", "Optional Docker registry access token. Provide this if you want to include private images within the specified namespace.").String()
travisCiScan = cli.Command("travisci", "Scan TravisCI")
travisCiScanToken = travisCiScan.Flag("token", "TravisCI token. Can also be provided with environment variable").Envar("TRAVISCI_TOKEN").Required().String()
@@ -275,8 +287,9 @@ func init() {
cli.Version("trufflehog " + version.BuildVersion)
// Support -h for help
// Support -h for help and write it to stdout.
cli.HelpFlag.Short('h')
cli.UsageWriter(os.Stdout)
// Check if the TUI environment variable is set.
if ok, err := strconv.ParseBool(os.Getenv("TUI_PARENT")); err == nil {
@@ -449,6 +462,16 @@ func run(state overseer.State) {
// OSS Default APK handling on
feature.EnableAPKHandler.Store(true)
// OSS Default Use Git Mirror on
feature.UseGitMirror.Store(true)
// OSS Default simplified gitlab enumeration
feature.UseSimplifiedGitlabEnumeration.Store(true)
feature.GitlabProjectsPerPage.Store(100)
// OSS Default using github graphql api for issues, pr's and comments
feature.UseGithubGraphQLAPI.Store(false)
conf := &config.Config{}
if *configFilename != "" {
var err error
@@ -694,32 +717,48 @@ func runSingleScan(ctx context.Context, cmd string, cfg engine.Config) (metrics,
}
eng.Start(ctx)
persistGitRepo := *gitNoCleanup || *githubNoCleanup || *gitlabNoCleanup
gitCloneTempPath := ""
defer func() {
// Clean up temporary artifacts.
if err := cleantemp.CleanTempArtifacts(ctx); err != nil {
ctx.Logger().Error(err, "error cleaning temp artifacts")
}
if *jsonLegacy {
// If JSON legacy is enabled, that means the cloned repos are not deleted yet
// because they were needed for outputting legacy JSON.
// We only clean them up here if the user did not request to persist them.
if !persistGitRepo {
if err := cleantemp.CleanTempDirsForLegacyJSON(gitCloneTempPath); err != nil {
ctx.Logger().Error(err, "error cleaning temp artifacts for legacy JSON")
}
}
}
}()
var refs []sources.JobProgressRef
switch cmd {
case gitScan.FullCommand():
// validate the commit for local repository only
if *gitScanSinceCommit != "" && strings.HasPrefix(*gitScanURI, "file") {
if !isValidCommit(*gitScanURI, *gitScanSinceCommit) {
ctx.Logger().Info("Warning: The provided commit hash appears to be invalid.")
}
if err := validateClonePath(*gitClonePath, *gitNoCleanup); err != nil {
return scanMetrics, err
}
gitCfg := sources.GitConfig{
URI: *gitScanURI,
IncludePathsFile: *gitScanIncludePaths,
ExcludePathsFile: *gitScanExcludePaths,
HeadRef: *gitScanBranch,
BaseRef: *gitScanSinceCommit,
MaxDepth: *gitScanMaxDepth,
Bare: *gitScanBare,
ExcludeGlobs: *gitScanExcludeGlobs,
URI: *gitScanURI,
IncludePathsFile: *gitScanIncludePaths,
ExcludePathsFile: *gitScanExcludePaths,
HeadRef: *gitScanBranch,
BaseRef: *gitScanSinceCommit,
MaxDepth: *gitScanMaxDepth,
Bare: *gitScanBare,
ExcludeGlobs: *gitScanExcludeGlobs,
ClonePath: *gitClonePath,
NoCleanup: *gitNoCleanup,
PrintLegacyJSON: *jsonLegacy,
TrustLocalGitConfig: *gitTrustLocalGitConfig,
}
if ref, err := eng.ScanGit(ctx, gitCfg); err != nil {
return scanMetrics, fmt.Errorf("failed to scan Git: %v", err)
@@ -727,6 +766,7 @@ func runSingleScan(ctx context.Context, cmd string, cfg engine.Config) (metrics,
refs = []sources.JobProgressRef{ref}
}
case githubScan.FullCommand():
gitCloneTempPath = *githubClonePath
filter, err := common.FilterFromFiles(*githubScanIncludePaths, *githubScanExcludePaths)
if err != nil {
return scanMetrics, fmt.Errorf("could not create filter: %v", err)
@@ -738,6 +778,10 @@ func runSingleScan(ctx context.Context, cmd string, cfg engine.Config) (metrics,
return scanMetrics, fmt.Errorf("invalid config: you cannot specify both organizations and repositories at the same time")
}
if err := validateClonePath(*githubClonePath, *githubNoCleanup); err != nil {
return scanMetrics, err
}
cfg := sources.GithubConfig{
Endpoint: *githubScanEndpoint,
Token: *githubScanToken,
@@ -755,7 +799,12 @@ func runSingleScan(ctx context.Context, cmd string, cfg engine.Config) (metrics,
CommentsTimeframeDays: *githubCommentsTimeframeDays,
Filter: filter,
AuthInUrl: *githubAuthInUrl,
ClonePath: *githubClonePath,
NoCleanup: *githubNoCleanup,
IgnoreGists: *githubIgnoreGists,
PrintLegacyJSON: *jsonLegacy,
}
if ref, err := eng.ScanGitHub(ctx, cfg); err != nil {
return scanMetrics, fmt.Errorf("failed to scan Github: %v", err)
} else {
@@ -775,20 +824,34 @@ func runSingleScan(ctx context.Context, cmd string, cfg engine.Config) (metrics,
refs = []sources.JobProgressRef{ref}
}
case gitlabScan.FullCommand():
gitCloneTempPath = *gitlabClonePath
filter, err := common.FilterFromFiles(*gitlabScanIncludePaths, *gitlabScanExcludePaths)
if err != nil {
return scanMetrics, fmt.Errorf("could not create filter: %v", err)
}
cfg := sources.GitlabConfig{
Endpoint: *gitlabScanEndpoint,
Token: *gitlabScanToken,
Repos: *gitlabScanRepos,
IncludeRepos: *gitlabScanIncludeRepos,
ExcludeRepos: *gitlabScanExcludeRepos,
Filter: filter,
AuthInUrl: *gitlabAuthInUrl,
if len(*gitlabScanRepos) > 0 && len(*gitlabScanGroupIds) > 0 {
return scanMetrics, fmt.Errorf("invalid config: you cannot specify both repositories and groups at the same time")
}
if err := validateClonePath(*gitlabClonePath, *gitlabNoCleanup); err != nil {
return scanMetrics, err
}
cfg := sources.GitlabConfig{
Endpoint: *gitlabScanEndpoint,
Token: *gitlabScanToken,
Repos: *gitlabScanRepos,
GroupIds: *gitlabScanGroupIds,
IncludeRepos: *gitlabScanIncludeRepos,
ExcludeRepos: *gitlabScanExcludeRepos,
Filter: filter,
AuthInUrl: *gitlabAuthInUrl,
ClonePath: *gitlabClonePath,
NoCleanup: *gitlabNoCleanup,
PrintLegacyJSON: *jsonLegacy,
}
if ref, err := eng.ScanGitLab(ctx, cfg); err != nil {
return scanMetrics, fmt.Errorf("failed to scan GitLab: %v", err)
} else {
@@ -873,11 +936,25 @@ func runSingleScan(ctx context.Context, cmd string, cfg engine.Config) (metrics,
refs = []sources.JobProgressRef{ref}
}
case dockerScan.FullCommand():
if *dockerScanImages != nil && *dockerScanNamespace != "" {
return scanMetrics, fmt.Errorf("invalid config: you cannot specify both images and namespace at the same time")
}
if *dockerScanImages == nil && *dockerScanNamespace == "" {
return scanMetrics, fmt.Errorf("invalid config: both images and namespace cannot be empty; one is required")
}
if *dockerScanRegistryToken != "" && *dockerScanNamespace == "" {
return scanMetrics, fmt.Errorf("invalid config: registry token can only be used with registry namespace")
}
cfg := sources.DockerConfig{
BearerToken: *dockerScanToken,
Images: *dockerScanImages,
UseDockerKeychain: *dockerScanToken == "",
ExcludePaths: strings.Split(*dockerExcludePaths, ","),
Namespace: *dockerScanNamespace,
RegistryToken: *dockerScanRegistryToken,
}
if ref, err := eng.ScanDocker(ctx, cfg); err != nil {
return scanMetrics, fmt.Errorf("failed to scan Docker: %v", err)
@@ -1014,8 +1091,12 @@ func runSingleScan(ctx context.Context, cmd string, cfg engine.Config) (metrics,
}
// Print any non-fatal errors reported during the scan.
var retErr error
for _, ref := range refs {
if errs := ref.Snapshot().Errors; len(errs) > 0 {
if *failOnScanErrors {
retErr = fmt.Errorf("encountered errors during scan")
}
errMsgs := make([]string, len(errs))
for i := 0; i < len(errs); i++ {
errMsgs[i] = errs[i].Error()
@@ -1032,7 +1113,7 @@ func runSingleScan(ctx context.Context, cmd string, cfg engine.Config) (metrics,
printAverageDetectorTime(eng)
}
return metrics{Metrics: eng.GetMetrics(), hasFoundResults: eng.HasFoundResults()}, nil
return metrics{Metrics: eng.GetMetrics(), hasFoundResults: eng.HasFoundResults()}, retErr
}
// parseResults ensures that users provide valid CSV input to `--results`.
@@ -1096,14 +1177,30 @@ func printAverageDetectorTime(e *engine.Engine) {
}
}
// Function to check if the commit is valid
func isValidCommit(uri, commit string) bool {
// handle file:// urls
repoPath, _ := strings.CutPrefix(uri, "file://") // remove the prefix to validate against the repo path
output, err := exec.Command("git", "-C", repoPath, "cat-file", "-t", commit).Output()
if err != nil {
return false
// validateClonePath ensures that --clone-path, if provided, exists and is a directory.
// It also verifies that --no-cleanup is only allowed when --clone-path is set.
// Note: without a custom clone path, repositories are cloned into temporary directories, which should never be retained.
func validateClonePath(clonePath string, noCleanup bool) error {
if noCleanup && clonePath == "" {
return fmt.Errorf("invalid configuration: --no-cleanup can only be used together with --clone-path")
}
return strings.TrimSpace(string(output)) == "commit"
if clonePath == "" {
return nil
}
info, err := os.Stat(clonePath)
if err != nil {
if os.IsNotExist(err) {
return fmt.Errorf("path provided to --clone-path: %q does not exist", clonePath)
}
return fmt.Errorf("failed to access --clone-path %q: %w", clonePath, err)
}
if !info.IsDir() {
return fmt.Errorf("path provided to --clone-path: %q is not a directory", clonePath)
}
return nil
}
+1
View File
@@ -44,6 +44,7 @@ type (
Binding struct {
Resource Resource
Permission Permission
Condition string
}
)
+1 -1
View File
@@ -46,7 +46,7 @@ func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
result := analyzers.AnalyzerResult{}
// resoures/permission setup
// resources/permission setup
permissions := allPermissions()
userResource := analyzers.Resource{
Name: info.Data.Name,
@@ -416,7 +416,7 @@ func AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {
}, nil
}
// AnalyzePermissions prints the permissions of a HuggingFace API key
// AnalyzeAndPrintPermissions prints the permissions of a HuggingFace API key
func AnalyzeAndPrintPermissions(cfg *config.Config, key string) {
info, err := AnalyzePermissions(cfg, key)
if err != nil {
@@ -78,7 +78,7 @@ type Resource struct {
MetaData map[string]string
}
// appendResources append resource to secret info resources list
// appendResource append resource to secret info resources list
func (s *SecretInfo) appendResource(resource Resource) {
s.mu.Lock()
defer s.mu.Unlock()
@@ -38,7 +38,7 @@ var (
webhooksKey: "/v2/webhooks",
/*
TODO:
release piplelines: https://launchdarkly.com/docs/api/release-pipelines-beta/get-all-release-pipelines (Beta)
release pipelines: https://launchdarkly.com/docs/api/release-pipelines-beta/get-all-release-pipelines (Beta)
insight deployments: https://launchdarkly.com/docs/api/insights-deployments-beta/get-deployments (Beta)
delivery configuration: https://launchdarkly.com/docs/api/integration-delivery-configurations-beta/get-integration-delivery-configuration-by-environment (Beta)
metrics: https://launchdarkly.com/docs/api/metrics-beta/get-metric-groups (Beta)
+1 -1
View File
@@ -269,7 +269,7 @@ func AnalyzeAndPrintPermissions(cfg *config.Config, key string) {
}
if info.organization == nil {
color.Yellow("\n[i] No permissions were verified for this key because the key does not have one of the necessary permissions (user:read or organization:read) required to verifiy other permissions.")
color.Yellow("\n[i] No permissions were verified for this key because the key does not have one of the necessary permissions (user:read or organization:read) required to verify other permissions.")
}
if info.orgPermissions != nil {
+26
View File
@@ -117,3 +117,29 @@ func CleanTempArtifacts(ctx logContext.Context) error {
return nil
}
// CleanTempDirsForLegacyJSON removes all directories that start with "trufflehog-"
// from either the provided clonePath (if not empty) or the OS temp directory.
func CleanTempDirsForLegacyJSON(baseDir string) error {
// If no custom clone path was provided, clean repos from the OS temp directory
// since that's where they were cloned during the scan.
if baseDir == "" {
baseDir = os.TempDir()
}
entries, err := os.ReadDir(baseDir)
if err != nil {
return err
}
for _, entry := range entries {
if entry.IsDir() && strings.HasPrefix(entry.Name(), "trufflehog-") {
fullPath := filepath.Join(baseDir, entry.Name())
if err := os.RemoveAll(fullPath); err != nil {
return err
}
}
}
return nil
}
+32
View File
@@ -28,3 +28,35 @@ func TestExecName(t *testing.T) {
assert.True(t, found)
}
func TestCleanTempDirsForLegacyJSON(t *testing.T) {
baseDir := t.TempDir()
// Create dirs that should be deleted
dir1 := filepath.Join(baseDir, "trufflehog-123")
dir2 := filepath.Join(baseDir, "trufflehog-456")
assert.NoError(t, os.Mkdir(dir1, 0o755))
assert.NoError(t, os.Mkdir(dir2, 0o755))
// Create dirs that should NOT be deleted
keepDir := filepath.Join(baseDir, "keepme-123")
assert.NoError(t, os.Mkdir(keepDir, 0o755))
// Create a file with trufflehog- prefix (should not be deleted because only dirs are deleted)
keepFile := filepath.Join(baseDir, "trufflehog-file")
assert.NoError(t, os.WriteFile(keepFile, []byte("data"), 0o644))
err := CleanTempDirsForLegacyJSON(baseDir)
assert.NoError(t, err)
_, err = os.Stat(dir1)
assert.True(t, os.IsNotExist(err))
_, err = os.Stat(dir2)
assert.True(t, os.IsNotExist(err))
_, err = os.Stat(keepDir)
assert.NoError(t, err)
_, err = os.Stat(keepFile)
assert.NoError(t, err)
}
-5
View File
@@ -1,5 +0,0 @@
package common
import (
_ "github.com/tailscale/depaware/depaware"
)
+41 -2
View File
@@ -108,6 +108,45 @@ func NewCustomTransport(T http.RoundTripper) *CustomTransport {
return &CustomTransport{T}
}
type InstrumentedTransport struct {
T http.RoundTripper
}
func (t *InstrumentedTransport) RoundTrip(req *http.Request) (*http.Response, error) {
sanitizedURL := sanitizeURL(req.URL.String())
// increment counter for the URL
recordHTTPRequest(sanitizedURL)
// Record start time for latency measurement
start := time.Now()
resp, err := t.T.RoundTrip(req)
// Time the latency
duration := time.Since(start)
if err != nil {
recordNetworkError(sanitizedURL)
return nil, err
}
if resp != nil {
// record latency and increment counter for non-200 status code
recordHTTPResponse(sanitizedURL, resp.StatusCode, duration.Seconds())
}
return resp, err
}
func NewInstrumentedTransport(T http.RoundTripper) *InstrumentedTransport {
if T == nil {
T = http.DefaultTransport
}
return &InstrumentedTransport{T}
}
func ConstantResponseHttpClient(statusCode int, body string) *http.Client {
return &http.Client{
Timeout: DefaultResponseTimeout,
@@ -223,7 +262,7 @@ var saneTransport = &http.Transport{
func SaneHttpClient() *http.Client {
httpClient := &http.Client{}
httpClient.Timeout = DefaultResponseTimeout
httpClient.Transport = NewCustomTransport(saneTransport)
httpClient.Transport = NewInstrumentedTransport(NewCustomTransport(saneTransport))
return httpClient
}
@@ -231,6 +270,6 @@ func SaneHttpClient() *http.Client {
func SaneHttpClientTimeOut(timeout time.Duration) *http.Client {
httpClient := &http.Client{}
httpClient.Timeout = timeout
httpClient.Transport = NewCustomTransport(nil)
httpClient.Transport = NewInstrumentedTransport(NewCustomTransport(nil))
return httpClient
}
+108
View File
@@ -0,0 +1,108 @@
package common
import (
"net/url"
"strconv"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)
var (
httpRequestsTotal = promauto.NewCounterVec(
prometheus.CounterOpts{
Namespace: MetricsNamespace,
Subsystem: "http_client",
Name: "requests_total",
Help: "Total number of HTTP requests made, labeled by URL.",
},
[]string{"url"},
)
httpRequestDuration = promauto.NewHistogramVec(
prometheus.HistogramOpts{
Namespace: MetricsNamespace,
Subsystem: "http_client",
Name: "request_duration_seconds",
Help: "HTTP request latency in seconds, labeled by URL.",
Buckets: prometheus.DefBuckets,
},
[]string{"url"},
)
httpNon200ResponsesTotal = promauto.NewCounterVec(
prometheus.CounterOpts{
Namespace: MetricsNamespace,
Subsystem: "http_client",
Name: "non_200_responses_total",
Help: "Total number of non-200 HTTP responses, labeled by URL and status code.",
},
[]string{"url", "status_code"},
)
)
// sanitizeURL sanitizes a URL to avoid high cardinality metrics.
// It keeps only the host and path, removing query parameters, fragments, and user info.
func sanitizeURL(rawURL string) string {
if rawURL == "" {
return "unknown"
}
parsedURL, err := url.Parse(rawURL)
if err != nil {
return "invalid_url"
}
// Build sanitized URL with just scheme, host, and path
sanitized := &url.URL{
Scheme: parsedURL.Scheme,
Host: parsedURL.Host,
Path: parsedURL.Path,
}
// If host is empty, try to extract from the raw URL
if sanitized.Host == "" {
// For relative URLs or malformed URLs, just use a placeholder
return "relative_or_invalid"
}
// Normalize path
if sanitized.Path == "" {
sanitized.Path = "/"
}
// Limit path length to avoid extremely long paths creating high cardinality
if len(sanitized.Path) > 100 {
sanitized.Path = sanitized.Path[:100] + "..."
}
result := sanitized.String()
// Final fallback to avoid empty strings
if result == "" {
return "unknown"
}
return result
}
// recordHTTPRequest records metrics for an HTTP request.
func recordHTTPRequest(sanitizedURL string) {
httpRequestsTotal.WithLabelValues(sanitizedURL).Inc()
}
// recordHTTPResponse records metrics for an HTTP response.
func recordHTTPResponse(sanitizedURL string, statusCode int, durationSeconds float64) {
// Record latency
httpRequestDuration.WithLabelValues(sanitizedURL).Observe(durationSeconds)
// Record non-200 responses
if statusCode != 200 {
httpNon200ResponsesTotal.WithLabelValues(sanitizedURL, strconv.Itoa(statusCode)).Inc()
}
}
// recordNetworkError records metrics for failed HTTP response
func recordNetworkError(sanitizedURL string) {
httpNon200ResponsesTotal.WithLabelValues(sanitizedURL, "network_error").Inc()
}
+170
View File
@@ -6,11 +6,14 @@ import (
"net/http"
"net/http/httptest"
"slices"
"strings"
"testing"
"time"
"github.com/hashicorp/go-retryablehttp"
"github.com/prometheus/client_golang/prometheus/testutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestRetryableHTTPClientCheckRetry(t *testing.T) {
@@ -269,3 +272,170 @@ func TestRetryableHTTPClientTimeout(t *testing.T) {
})
}
}
func TestSanitizeURL(t *testing.T) {
testCases := []struct {
name string
input string
expected string
}{
{
name: "valid https URL",
input: "https://api.example.com/v1/users",
expected: "https://api.example.com/v1/users",
},
{
name: "URL with query parameters",
input: "https://api.example.com/search?q=secret&limit=10",
expected: "https://api.example.com/search",
},
{
name: "URL with fragment",
input: "https://example.com/page#section",
expected: "https://example.com/page",
},
{
name: "URL with user info",
input: "https://user:[email protected]/path",
expected: "https://api.example.com/path",
},
{
name: "empty URL",
input: "",
expected: "unknown",
},
{
name: "invalid URL",
input: "not-a-url",
expected: "relative_or_invalid",
},
{
name: "very long path",
input: "https://example.com/" + strings.Repeat("a", 150),
expected: "https://example.com/" + strings.Repeat("a", 99) + "...", // 99 + 1 ("/") = 100 chars
},
{
name: "root path",
input: "https://example.com",
expected: "https://example.com/",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := sanitizeURL(tc.input)
assert.Equal(t, tc.expected, result)
})
}
}
func TestSaneHttpClientMetrics(t *testing.T) {
// Create a test server that returns different status codes
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/success":
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("success"))
case "/error":
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte("error"))
case "/notfound":
w.WriteHeader(http.StatusNotFound)
_, _ = w.Write([]byte("not found"))
default:
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("default"))
}
}))
defer server.Close()
// Create a SaneHttpClient
client := SaneHttpClient()
testCases := []struct {
name string
path string
expectedStatusCode int
expectsNon200 bool
}{
{
name: "successful request",
path: "/success",
expectedStatusCode: 200,
expectsNon200: false,
},
{
name: "server error request",
path: "/error",
expectedStatusCode: 500,
expectsNon200: true,
},
{
name: "not found request",
path: "/notfound",
expectedStatusCode: 404,
expectsNon200: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
var requestURL string
if strings.HasPrefix(tc.path, "http") {
requestURL = tc.path
} else {
requestURL = server.URL + tc.path
}
// Get initial metric values
sanitizedURL := sanitizeURL(requestURL)
initialRequestsTotal := testutil.ToFloat64(httpRequestsTotal.WithLabelValues(sanitizedURL))
// Make the request
resp, err := client.Get(requestURL)
require.NoError(t, err)
defer resp.Body.Close()
assert.Equal(t, tc.expectedStatusCode, resp.StatusCode)
// Check that request counter was incremented
requestsTotal := testutil.ToFloat64(httpRequestsTotal.WithLabelValues(sanitizedURL))
assert.Equal(t, initialRequestsTotal+1, requestsTotal)
})
}
}
func TestInstrumentedTransport(t *testing.T) {
// Create a mock transport that we can control
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("test response"))
}))
defer server.Close()
// Create instrumented transport
transport := NewInstrumentedTransport(nil)
client := &http.Client{
Transport: transport,
Timeout: 5 * time.Second,
}
// Get initial metric value
sanitizedURL := sanitizeURL(server.URL)
initialCount := testutil.ToFloat64(httpRequestsTotal.WithLabelValues(sanitizedURL))
// Make a request
resp, err := client.Get(server.URL)
require.NoError(t, err)
defer resp.Body.Close()
// Verify the request was successful
assert.Equal(t, http.StatusOK, resp.StatusCode)
// Verify metrics were recorded
finalCount := testutil.ToFloat64(httpRequestsTotal.WithLabelValues(sanitizedURL))
assert.Equal(t, initialCount+1, finalCount)
// Note: Testing histogram metrics is complex due to the way Prometheus handles them
// The main thing is that the request completed successfully and counters were incremented
}
+23
View File
@@ -27,6 +27,29 @@ func Recover(ctx context.Context) {
}
}
// RecoverWithHandler handles panics and reports to Sentry, then turns control
// over to a provided function. This permits extra reporting in the same scope
// without re-panicking, as recover() clears the state after it's called. Does
// NOT block to flush sentry report.
func RecoverWithHandler(ctx context.Context, callback func(error)) {
if err := recover(); err != nil {
panicStack := string(debug.Stack())
if eventID := sentry.CurrentHub().Recover(err); eventID != nil {
ctx.Logger().Info("panic captured", "event_id", *eventID)
}
ctx.Logger().Error(fmt.Errorf("panic"), panicStack,
"recover", err,
)
switch v := err.(type) {
case error:
callback(fmt.Errorf("panic: %w", v))
default:
callback(fmt.Errorf("panic: %v", v))
}
}
}
// RecoverWithExit handles panics and reports to Sentry before exiting.
func RecoverWithExit(ctx context.Context) {
if err := recover(); err != nil {
+20 -2
View File
@@ -16,6 +16,13 @@ var (
defaultLogger logr.Logger
)
// logEntryKey is used to store a reference to the logger in the
// context.Context key/value bag. This is used for regaining the logger in case
// the context is converted into a different type.
const logEntryKey logEntryKeyT = 0
type logEntryKeyT int
func init() {
defaultLogger, _ = log.New("context", log.WithConsoleSink(os.Stderr))
}
@@ -132,12 +139,14 @@ func Cause(ctx context.Context) error {
// the value added to the structured log values (if the key is a string).
func WithValue(parent Context, key, val any) Context {
logger := parent.Logger()
parentCtx := context.WithValue(parent, key, val)
if k, ok := key.(string); ok {
logger = logger.WithValues(k, val)
parentCtx = context.WithValue(parentCtx, logEntryKey, logger)
}
return logCtx{
log: logger,
Context: context.WithValue(parent, key, val),
Context: parentCtx,
}
}
@@ -155,7 +164,7 @@ func WithValues(parent Context, keyAndVals ...any) Context {
func WithLogger(parent context.Context, logger logr.Logger) Context {
return logCtx{
log: logger,
Context: parent,
Context: context.WithValue(parent, logEntryKey, logger),
}
}
@@ -163,9 +172,18 @@ func WithLogger(parent context.Context, logger logr.Logger) Context {
// is already a Context, that will be returned, otherwise a default logger will
// be added.
func AddLogger(parent context.Context) Context {
// If the context.Context is already a Context, return that.
if loggerCtx, ok := parent.(Context); ok {
return loggerCtx
}
// If the logger exists in the grab bag (and is the correct type),
// return that.
if logEntryVal := parent.Value(logEntryKey); logEntryVal != nil {
if logger, ok := logEntryVal.(logr.Logger); ok {
return WithLogger(parent, logger)
}
}
// Otherwise, add the default logger.
return WithLogger(parent, defaultLogger)
}
+31
View File
@@ -185,3 +185,34 @@ func TestCause(t *testing.T) {
cancel(err)
assert.Equal(t, err, Cause(ctx))
}
// TestBuriedLogger tests when a logging context is wrapped by a non-logging
// implementation that we can still regain the original logger.
func TestBuriedLogger(t *testing.T) {
var buffer bytes.Buffer
logger, sync := log.New("test",
log.WithConsoleSink(&buffer),
)
defer func(prevLogger logr.Logger) {
defaultLogger = prevLogger
}(defaultLogger)
SetDefaultLogger(logger)
// Create a context with a key/value log entry.
ctx := WithValue(Background(), "log", "entry")
// Convert it to a stdlib context.
stdCtx := context.WithValue(ctx, "std", "entry") //nolint:staticcheck
// Try to get the logger back again.
ctx = AddLogger(stdCtx)
ctx.Logger().Info("test")
assert.Nil(t, sync())
logs := strings.Split(strings.TrimSpace(buffer.String()), "\n")
// Logger has the key/value log entry.
assert.Equal(t, 1, len(logs))
assert.Contains(t, logs[0], `{"log": "entry"}`)
// Grab bag still has both values.
assert.Equal(t, "entry", ctx.Value("log"))
assert.Equal(t, "entry", ctx.Value("std"))
}
+11 -4
View File
@@ -1,6 +1,6 @@
# TruffleHog Custom Detector Setup Guide
This guide will walk you through setting up a custom detector in TruffleHog to identify specific patterns unique to your project.
This guide will walk you through setting up a custom detector in TruffleHog to identify specific patterns unique to your project. For users of Trufflehog Enterprise, this guide applies to that environment as well.
## Steps to Set Up a Custom Detector
@@ -40,9 +40,16 @@ This guide will walk you through setting up a custom detector in TruffleHog to i
**Other allowed parameters:**
- **`primary_regex_name`**: This parameter allows you designate the primary regex pattern when multiple regex patterns are defined in the regex section. If a match is found, the match for the designated primary regex will be used to determine the line number. The value must be one of the names specified in the regex section.
- **`exclude_regexes_capture`**: This parameter allows you to define regex patterns to exclude specific parts of a detected secret. If a match is found within the detected secret, the portion matching this regex is excluded from the result.
- **`exclude_regexes_match`**: This parameter enables you to define regex patterns to exclude entire matches from being reported as secrets.
- **`exclude_regexes_match`**: This parameter enables you to define regex patterns to exclude entire matches from being reported as secrets. This applies to the entire matched string, not just the token.
- **`entropy`**: This parameter is used to assess the randomness of detected strings. High entropy often indicates that a string is a potential secret, such as an API key or password, due to its complexity and unpredictability. It helps in filtering false-positives. While an entropy threshold of `3` can be a starting point, it's essential to adjust this value based on your project's specific requirements and the nature of the data you have.
- **`exclude_words`**: This parameter allows you to specify a list of words that, if present in a detected string, will cause TruffleHog to ignore that string.
- **`exclude_words`**: This parameter allows you to specify a list of words that, if present in a detected string, will cause TruffleHog to ignore that string. This is a substring match and does not enforce word boundaries. It applies only to the token.
- **`validations`**: This parameter lets you define extra validation rules for each regex specified in the regex option. These rules address limitations of Go's RE2 engine, such as the lack of lookahead support, and are applied after a regex match to help reduce false positives.
Available validation options:
- **`contains_digit`**: Ensures the match contains at least one numeric digit (0-9). Useful for API keys or tokens that must include numbers.
- **`contains_lowercase`**: Ensures the match contains at least one lowercase letter (a-z). Common requirement for passwords and mixed-case tokens.
- **`contains_uppercase`**: Ensures the match contains at least one uppercase letter (A-Z). Helps validate tokens that follow mixed-case conventions.
- **`contains_special_char`**: Ensures the match contains at least one special character from the set `!@#$%^&*()_+-=[]{}|;:,.<>?`. Useful for complex passwords or encoded tokens.
[Here](/examples/generic_with_filters.yml) is an example of a custom detector using these parameters.
@@ -207,4 +214,4 @@ func main() {
log.Fatalf("Server failed: %s", err)
}
}
```
```
+25 -2
View File
@@ -5,8 +5,10 @@ import (
"context"
"encoding/json"
"io"
"maps"
"net/http"
"regexp"
"slices"
"strings"
"golang.org/x/sync/errgroup"
@@ -116,7 +118,7 @@ func (c *CustomRegexWebhook) FromData(ctx context.Context, verify bool, data []b
MatchLoop:
for _, match := range matches {
for _, values := range match {
for key, values := range match {
// attempt to use capture group
secret := values[0]
if len(values) > 1 {
@@ -150,6 +152,26 @@ MatchLoop:
continue MatchLoop
}
}
if validations := c.GetValidations(); validations != nil {
validationRules := []struct {
enabled bool
validator func(string) bool
}{
{validations[key].GetContainsDigit(), ContainsDigit},
{validations[key].GetContainsLowercase(), ContainsLowercase},
{validations[key].GetContainsUppercase(), ContainsUppercase},
{validations[key].GetContainsSpecialChar(), ContainsSpecialChar},
}
for _, rule := range validationRules {
if rule.enabled && !rule.validator(secret) {
// skip this match if a validation rule is enabled but missing from the secret
continue MatchLoop
}
}
}
}
g.Go(func() error {
@@ -194,7 +216,8 @@ func (c *CustomRegexWebhook) createResults(ctx context.Context, match map[string
}
var raw string
for key, values := range match {
for _, key := range slices.Sorted(maps.Keys(match)) {
values := match[key]
// values[0] contains the entire regex match.
secret := values[0]
if len(values) > 1 {
@@ -4,9 +4,13 @@ import (
"context"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/stretchr/testify/assert"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/custom_detectorspb"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
"github.com/trufflesecurity/trufflehog/v3/pkg/protoyaml"
)
@@ -227,6 +231,334 @@ func TestDetectorPrimarySecret(t *testing.T) {
assert.Equal(t, "secret_YI7C90ACY1_yy", results[0].GetPrimarySecretValue())
}
func TestDetectorValidations(t *testing.T) {
type args struct {
CustomRegex *custom_detectorspb.CustomRegex
Data string
}
tests := []struct {
name string
input args
want []detectors.Result
}{
{
name: "custom validation - contains digit",
input: args{
CustomRegex: &custom_detectorspb.CustomRegex{
Name: "test",
Keywords: []string{"password"},
Regex: map[string]string{"password": `([A-Za-z0-9!@#$%^&*()_+=\-]{12,})`},
Validations: map[string]*custom_detectorspb.ValidationConfig{
"password": {
ContainsDigit: true,
},
},
},
Data: `This is custom example
This file has a random text and maybe a secret
Password: MyStr0ngP@ssword!
End of file`,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_CustomRegex,
DetectorName: "test",
Verified: false,
Raw: []byte("MyStr0ngP@ssword!"),
},
},
},
{
name: "custom validation - does not contains digit",
input: args{
CustomRegex: &custom_detectorspb.CustomRegex{
Name: "test",
Keywords: []string{"password"},
Regex: map[string]string{"password": `([A-Za-z0-9!@#$%^&*()_+=\-]{12,})`},
Validations: map[string]*custom_detectorspb.ValidationConfig{
"password": {
ContainsDigit: true,
},
},
},
Data: `This is custom example
This file has a random text and maybe a secret
Password: MyStrongPassword!
End of file`,
},
want: nil,
},
{
name: "custom validation - contains lowercase",
input: args{
CustomRegex: &custom_detectorspb.CustomRegex{
Name: "test",
Keywords: []string{"password"},
Regex: map[string]string{"password": `([A-Za-z0-9!@#$%^&*()_+=\-]{12,})`},
Validations: map[string]*custom_detectorspb.ValidationConfig{
"password": {
ContainsLowercase: true,
},
},
},
Data: `This is custom example
This file has a random text and maybe a secret
Password: MyStrongPassword!
End of file`,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_CustomRegex,
DetectorName: "test",
Verified: false,
Raw: []byte("MyStrongPassword!"),
},
},
},
{
name: "custom validation - does not contains lowercase",
input: args{
CustomRegex: &custom_detectorspb.CustomRegex{
Name: "test",
Keywords: []string{"password"},
Regex: map[string]string{"password": `([A-Za-z0-9!@#$%^&*()_+=\-]{12,})`},
Validations: map[string]*custom_detectorspb.ValidationConfig{
"password": {
ContainsLowercase: true,
},
},
},
Data: `This is custom example
This file has a random text and maybe a secret
Password: MYSTRONGPASSWORD!
End of file`,
},
want: nil,
},
{
name: "custom validation - contains uppercase",
input: args{
CustomRegex: &custom_detectorspb.CustomRegex{
Name: "test",
Keywords: []string{"password"},
Regex: map[string]string{"password": `([A-Za-z0-9!@#$%^&*()_+=\-]{12,})`},
Validations: map[string]*custom_detectorspb.ValidationConfig{
"password": {
ContainsUppercase: true,
},
},
},
Data: `This is custom example
This file has a random text and maybe a secret
Password: MyStrongPassword!
End of file`,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_CustomRegex,
DetectorName: "test",
Verified: false,
Raw: []byte("MyStrongPassword!"),
},
},
},
{
name: "custom validation - does not contains uppercase",
input: args{
CustomRegex: &custom_detectorspb.CustomRegex{
Name: "test",
Keywords: []string{"password"},
Regex: map[string]string{"password": `([A-Za-z0-9!@#$%^&*()_+=\-]{12,})`},
Validations: map[string]*custom_detectorspb.ValidationConfig{
"password": {
ContainsUppercase: true,
},
},
},
Data: `This is custom example
This file has a random text and maybe a secret
Password: mystrongpassword!
End of file`,
},
want: nil,
},
{
name: "custom validation - contains special character",
input: args{
CustomRegex: &custom_detectorspb.CustomRegex{
Name: "test",
Keywords: []string{"password"},
Regex: map[string]string{"password": `([A-Za-z0-9!@#$%^&*()_+=\-]{12,})`},
Validations: map[string]*custom_detectorspb.ValidationConfig{
"password": {
ContainsSpecialChar: true,
},
},
},
Data: `This is custom example
This file has a random text and maybe a secret
Password: MyStr@ngP@ssword!
End of file`,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_CustomRegex,
DetectorName: "test",
Verified: false,
Raw: []byte("MyStr@ngP@ssword!"),
},
},
},
{
name: "custom validation - does not contains special character",
input: args{
CustomRegex: &custom_detectorspb.CustomRegex{
Name: "test",
Keywords: []string{"password"},
Regex: map[string]string{"password": `([A-Za-z0-9!@#$%^&*()_+=\-]{12,})`},
Validations: map[string]*custom_detectorspb.ValidationConfig{
"password": {
ContainsSpecialChar: true,
},
},
},
Data: `This is custom example
This file has a random text and maybe a secret
Password: MyStrongPassword
End of file`,
},
want: nil,
},
{
name: "custom validation - contains uppercase and special characters",
input: args{
CustomRegex: &custom_detectorspb.CustomRegex{
Name: "test",
Keywords: []string{"password"},
Regex: map[string]string{"password": `([A-Za-z0-9!@#$%^&*()_+=\-]{12,})`},
Validations: map[string]*custom_detectorspb.ValidationConfig{
"password": {
ContainsUppercase: true,
ContainsSpecialChar: true,
},
},
},
Data: `This is custom example
This file has a random text and maybe a secret
Password: MyStrongP@ssword
End of file`,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_CustomRegex,
DetectorName: "test",
Verified: false,
Raw: []byte("MyStrongP@ssword"),
},
},
},
{
name: "custom validation - contains uppercase but does not contain special characters",
input: args{
CustomRegex: &custom_detectorspb.CustomRegex{
Name: "test",
Keywords: []string{"password"},
Regex: map[string]string{"password": `([A-Za-z0-9!@#$%^&*()_+=\-]{12,})`},
Validations: map[string]*custom_detectorspb.ValidationConfig{
"password": {
ContainsUppercase: true,
ContainsSpecialChar: true,
},
},
},
Data: `This is custom example
This file has a random text and maybe a secret
Password: MyStrongPassword
End of file`,
},
want: nil,
},
{
name: "custom validation - wrong regex name in validations",
input: args{
CustomRegex: &custom_detectorspb.CustomRegex{
Name: "test",
Keywords: []string{"password"},
Regex: map[string]string{"password": `([A-Za-z0-9!@#$%^&*()_+=\-]{12,})`},
Validations: map[string]*custom_detectorspb.ValidationConfig{
"wrong": {
ContainsUppercase: true,
},
},
},
Data: `This is custom example
This file has a random text and maybe a secret
Password: mystrongp@ssword
End of file`,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_CustomRegex,
DetectorName: "test",
Verified: false,
Raw: []byte("mystrongp@ssword"),
},
},
},
{
name: "custom validation - multiple regex validations",
input: args{
CustomRegex: &custom_detectorspb.CustomRegex{
Name: "test",
Keywords: []string{"password", "api_key"},
Regex: map[string]string{
"password": `([A-Za-z0-9!@#$%^&*()_+=\-]{12,})`,
"api_key": `([a-f0-9_-]{32})`,
},
Validations: map[string]*custom_detectorspb.ValidationConfig{
"password": {
ContainsUppercase: true,
ContainsSpecialChar: true,
},
"api_key": {
ContainsSpecialChar: true,
},
},
},
Data: `This is custom example
This file has a random text and maybe a secret
Password: MyStrongP@ssword
API_Key: c392c9837d69b44c764cbf260b-e6184 // should be detected
API_Key: c392c9837d69b44c764cbf260be6184 // should be filtered by validation
End of file`,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_CustomRegex,
DetectorName: "test",
Verified: false,
Raw: []byte("c392c9837d69b44c764cbf260b-e6184MyStrongP@ssword"),
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
detector, err := NewWebhookCustomRegex(tt.input.CustomRegex)
assert.NoError(t, err)
results, err := detector.FromData(context.Background(), false, []byte(tt.input.Data))
assert.NoError(t, err)
ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "ExtraData", "verificationError", "primarySecret")
if diff := cmp.Diff(results, tt.want, ignoreOpts); diff != "" {
t.Errorf("CustomDetector.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
}
})
}
}
func BenchmarkProductIndices(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = productIndices(3, 2, 6)
+44
View File
@@ -107,3 +107,47 @@ func ValidateRegexVars(regex map[string]string, body ...string) error {
}
return nil
}
// === Custom Validations ===
// ContainsDigit checks if string contains at least one digit
func ContainsDigit(s string) bool {
for i := 0; i < len(s); i++ {
char := s[i]
if char >= '0' && char <= '9' {
return true
}
}
return false
}
// ContainsLowercase checks if string contains at least one lowercase letter
func ContainsLowercase(s string) bool {
for i := 0; i < len(s); i++ {
char := s[i]
if char >= 'a' && char <= 'z' {
return true
}
}
return false
}
// ContainsUppercase checks if string contains at least one uppercase letter
func ContainsUppercase(s string) bool {
for i := 0; i < len(s); i++ {
char := s[i]
if char >= 'A' && char <= 'Z' {
return true
}
}
return false
}
// ContainsSpecialChar checks if string contains at least one special character
func ContainsSpecialChar(s string) bool {
specialChars := "!@#$%^&*()_+-=[]{}|;:,.<>?"
return strings.ContainsAny(s, specialChars)
}
+119 -1
View File
@@ -1,6 +1,8 @@
package custom_detectors
import "testing"
import (
"testing"
)
func TestCustomDetectorsKeywordValidation(t *testing.T) {
tests := []struct {
@@ -232,3 +234,119 @@ func TestCustomDetectorsVerifyRegexVarsValidation(t *testing.T) {
})
}
}
func TestContainsDigit(t *testing.T) {
type args struct {
s string
}
tests := []struct {
name string
args args
want bool
}{
{
name: "contains digit",
args: args{s: "lzscqf&60M"},
want: true,
},
{
name: "does not contains digit",
args: args{s: "ZlDQOdaM*vsT"},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := ContainsDigit(tt.args.s); got != tt.want {
t.Errorf("ContainsDigit() = %v, want %v", got, tt.want)
}
})
}
}
func TestContainsLowercase(t *testing.T) {
type args struct {
s string
}
tests := []struct {
name string
args args
want bool
}{
{
name: "contains lower case",
args: args{s: "g0AJBHdnhRG2"},
want: true,
},
{
name: "does not contains lower case",
args: args{s: "V7T#MEA6@+TN"},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := ContainsLowercase(tt.args.s); got != tt.want {
t.Errorf("ContainsDigit() = %v, want %v", got, tt.want)
}
})
}
}
func TestContainsUppercase(t *testing.T) {
type args struct {
s string
}
tests := []struct {
name string
args args
want bool
}{
{
name: "contains upper case",
args: args{s: "G1sKkJeKlSQf"},
want: true,
},
{
name: "does not contains upper case",
args: args{s: "pq6-14ydz1@d"},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := ContainsUppercase(tt.args.s); got != tt.want {
t.Errorf("ContainsDigit() = %v, want %v", got, tt.want)
}
})
}
}
func TestContainsSpecialChar(t *testing.T) {
type args struct {
s string
}
tests := []struct {
name string
args args
want bool
}{
{
name: "contains upper case",
args: args{s: "HP$gE7s=do0B"},
want: true,
},
{
name: "does not contains upper case",
args: args{s: "w9gvBYctrSjB"},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := ContainsSpecialChar(tt.args.s); got != tt.want {
t.Errorf("ContainsDigit() = %v, want %v", got, tt.want)
}
})
}
}
+3 -3
View File
@@ -155,7 +155,7 @@ func BenchmarkFromChunkSmall(b *testing.B) {
d := Base64{}
data := detectors.MustGetBenchmarkData()["small"]
for n := 0; n < b.N; n++ {
for b.Loop() {
d.FromChunk(&sources.Chunk{Data: data})
}
}
@@ -164,7 +164,7 @@ func BenchmarkFromChunkMedium(b *testing.B) {
d := Base64{}
data := detectors.MustGetBenchmarkData()["medium"]
for n := 0; n < b.N; n++ {
for b.Loop() {
d.FromChunk(&sources.Chunk{Data: data})
}
}
@@ -173,7 +173,7 @@ func BenchmarkFromChunkLarge(b *testing.B) {
d := Base64{}
data := detectors.MustGetBenchmarkData()["big"]
for n := 0; n < b.N; n++ {
for b.Loop() {
d.FromChunk(&sources.Chunk{Data: data})
}
}
+167 -33
View File
@@ -23,6 +23,33 @@ var (
// Common escape sequence used in programming languages.
escapePat = regexp.MustCompile(`(?i:\\{1,2}u)([a-fA-F0-9]{4})`)
// Additional Unicode escape formats from dencode.com
// \u{X} format - Rust, Swift, some JS, etc. (variable length hex in braces)
braceEscapePat = regexp.MustCompile(`\\u\{([a-fA-F0-9]{1,6})\}`)
// \U00XXXXXX format - Python, etc. (8-digit format for non-BMP characters)
longEscapePat = regexp.MustCompile(`\\U([a-fA-F0-9]{8})`)
// \x{X} format - Perl (variable length hex in braces)
perlEscapePat = regexp.MustCompile(`\\x\{([a-fA-F0-9]{1,6})\}`)
// \X format - CSS (hex without padding). Go's regexp (RE2) has no look-ahead, so we
// include the delimiter (whitespace, another backslash, or end-of-string) in the
// match using a non-capturing group. The delimiter is later re-inserted by the
// decoder when necessary.
cssEscapePat = regexp.MustCompile(`\\([a-fA-F0-9]{1,6})(?:\s|\\|$)`)
// &#xX; format - HTML/XML (hex with semicolon)
htmlEscapePat = regexp.MustCompile(`&#x([a-fA-F0-9]{1,6});`)
// %uXXXX format - Percent-encoding (non-standard)
percentEscapePat = regexp.MustCompile(`%u([a-fA-F0-9]{4})`)
// // 0xX format - Hexadecimal notation with space separation
// Note: Commenting out for now due to high memory overhead. Review ways to handle this.
// hexEscapePat = regexp.MustCompile(`0x([a-fA-F0-9]{1,6})(?:\s|$)`)
)
func (d *EscapedUnicode) Type() detectorspb.DecoderType {
@@ -39,13 +66,38 @@ func (d *EscapedUnicode) FromChunk(chunk *sources.Chunk) *DecodableChunk {
chunkData = bytes.Clone(chunk.Data)
matched = false
)
if codePointPat.Match(chunkData) {
// Process patterns in priority order - more specific patterns first
// This prevents conflicts where multiple patterns match the same input
// Long escape format (8 hex digits) - highest priority
if longEscapePat.Match(chunkData) {
matched = true
chunkData = decodeCodePoint(chunkData)
}
if escapePat.Match(chunkData) {
chunkData = decodeLongEscape(chunkData)
} else if braceEscapePat.Match(chunkData) {
matched = true
chunkData = decodeBraceEscape(chunkData)
} else if perlEscapePat.Match(chunkData) {
matched = true
chunkData = decodePerlEscape(chunkData)
} else if htmlEscapePat.Match(chunkData) {
matched = true
chunkData = decodeHtmlEscape(chunkData)
} else if percentEscapePat.Match(chunkData) {
matched = true
chunkData = decodePercentEscape(chunkData)
} else if escapePat.Match(chunkData) {
matched = true
chunkData = decodeEscaped(chunkData)
} else if codePointPat.Match(chunkData) {
matched = true
chunkData = decodeCodePoint(chunkData)
} else if cssEscapePat.Match(chunkData) {
matched = true
chunkData = decodeCssEscape(chunkData)
// } else if hexEscapePat.Match(chunkData) {
// matched = true
// chunkData = decodeHexEscape(chunkData)
}
if matched {
@@ -71,6 +123,34 @@ func (d *EscapedUnicode) FromChunk(chunk *sources.Chunk) *DecodableChunk {
const maxBytesPerRune = 4
const spaceChar = byte(' ')
// decodeWithPattern replaces escape sequences matched by re with their UTF-8
// equivalents. The regex *must* have the first capturing group contain the
// hexadecimal code-point digits. Any invalid value (> 0x10FFFF or parse error)
// is skipped. The replacement walks matches in reverse order to avoid index
// shifts.
func decodeWithPattern(input []byte, re *regexp.Regexp) []byte {
indices := re.FindAllSubmatchIndex(input, -1)
if len(indices) == 0 {
return input
}
utf8Bytes := make([]byte, maxBytesPerRune)
for i := len(indices) - 1; i >= 0; i-- {
m := indices[i]
start, end := m[0], m[1]
hexStart, hexEnd := m[2], m[3]
cp, err := strconv.ParseUint(string(input[hexStart:hexEnd]), 16, 32)
if err != nil || cp > 0x10FFFF {
continue
}
utf8Len := utf8.EncodeRune(utf8Bytes, rune(cp))
input = append(input[:start], append(utf8Bytes[:utf8Len], input[end:]...)...)
}
return input
}
func decodeCodePoint(input []byte) []byte {
// Find all Unicode escape sequences in the input byte slice
indices := codePointPat.FindAllSubmatchIndex(input, -1)
@@ -112,33 +192,87 @@ func decodeCodePoint(input []byte) []byte {
}
func decodeEscaped(input []byte) []byte {
// Find all Unicode escape sequences in the input byte slice
indices := escapePat.FindAllSubmatchIndex(input, -1)
// Iterate over found indices in reverse order to avoid modifying the slice length
utf8Bytes := make([]byte, maxBytesPerRune)
for i := len(indices) - 1; i >= 0; i-- {
matches := indices[i]
startIndex := matches[0]
hexStartIndex := matches[2]
endIndex := matches[3]
// Extract the hexadecimal value from the escape sequence
hexValue := string(input[hexStartIndex:endIndex])
// Parse the hexadecimal value to an integer
unicodeInt, err := strconv.ParseInt(hexValue, 16, 32)
if err != nil {
// If there's an error, continue to the next escape sequence
continue
}
// Convert the Unicode code point to a UTF-8 representation
utf8Len := utf8.EncodeRune(utf8Bytes, rune(unicodeInt))
// Replace the escape sequence with the UTF-8 representation
input = append(input[:startIndex], append(utf8Bytes[:utf8Len], input[endIndex:]...)...)
}
return input
return decodeWithPattern(input, escapePat)
}
// decodeBraceEscape handles \u{X} format - Rust, Swift, some JS, etc.
func decodeBraceEscape(input []byte) []byte {
return decodeWithPattern(input, braceEscapePat)
}
// decodeLongEscape handles \U00XXXXXX format - Python, etc.
func decodeLongEscape(input []byte) []byte {
return decodeWithPattern(input, longEscapePat)
}
// decodePerlEscape handles \x{X} format - Perl
func decodePerlEscape(input []byte) []byte {
return decodeWithPattern(input, perlEscapePat)
}
// decodeCssEscape handles \X format - CSS (hex without padding, with space delimiter or end of string or next hex sequence)
func decodeCssEscape(input []byte) []byte {
return decodeWithPattern(input, cssEscapePat)
}
// decodeHtmlEscape handles &#xX; format - HTML/XML
func decodeHtmlEscape(input []byte) []byte {
return decodeWithPattern(input, htmlEscapePat)
}
// decodePercentEscape handles %uXXXX format - Percent-encoding (non-standard)
func decodePercentEscape(input []byte) []byte {
return decodeWithPattern(input, percentEscapePat)
}
// decodeHexEscape handles 0xX format - Hexadecimal notation with space separation
// func decodeHexEscape(input []byte) []byte {
// // This format requires consecutive 0xNN sequences to be considered for decoding
// // We'll look for patterns of multiple consecutive hex values
// hexPattern := regexp.MustCompile(`(?:0x[a-fA-F0-9]{1,2}(?:\s+|$))+`)
// matches := hexPattern.FindAll(input, -1)
// if len(matches) == 0 {
// return input
// }
// result := input
// for _, match := range matches {
// // Extract individual hex values
// individualHex := regexp.MustCompile(`0x([a-fA-F0-9]{1,2})`)
// hexMatches := individualHex.FindAllSubmatch(match, -1)
// // Only decode if we have multiple consecutive hex values (likely to be a Unicode string)
// if len(hexMatches) < 3 {
// continue
// }
// var decoded []byte
// for _, hexMatch := range hexMatches {
// hexValue := string(hexMatch[1])
// if len(hexValue) == 1 {
// hexValue = "0" + hexValue // Pad single digit hex values
// }
// unicodeInt, err := strconv.ParseUint(hexValue, 16, 32)
// if err != nil || unicodeInt > 0x10FFFF {
// break
// }
// if unicodeInt <= 0x7F {
// // ASCII character
// decoded = append(decoded, byte(unicodeInt))
// } else {
// // Unicode character
// utf8Bytes := make([]byte, maxBytesPerRune)
// utf8Len := utf8.EncodeRune(utf8Bytes, rune(unicodeInt))
// decoded = append(decoded, utf8Bytes[:utf8Len]...)
// }
// }
// // Replace the original sequence with decoded bytes
// result = bytes.Replace(result, match, decoded, 1)
// }
// return result
// }
+228
View File
@@ -0,0 +1,228 @@
package decoders
import (
"testing"
"github.com/trufflesecurity/trufflehog/v3/pkg/sources"
)
// Benchmark data for testing
var (
// Original formats
originalUnicodeData = []byte("\\u0041\\u004b\\u0049\\u0041\\u0055\\u004d\\u0034\\u0047\\u0036\\u004f\\u0036\\u004e\\u0041\\u004b\\u0045\\u0037\\u004c\\u0043\\u0044\\u004a")
codePointData = []byte("U+0041 U+004B U+0049 U+0041 U+0055 U+004D U+0034 U+0047 U+0036 U+004F U+0036 U+004E U+0041 U+004B U+0045 U+0037 U+004C U+0043 U+0044 U+004A")
// New formats
braceEscapeData = []byte("\\u{41}\\u{4b}\\u{49}\\u{41}\\u{55}\\u{4d}\\u{34}\\u{47}\\u{36}\\u{4f}\\u{36}\\u{4e}\\u{41}\\u{4b}\\u{45}\\u{37}\\u{4c}\\u{43}\\u{44}\\u{4a}")
longEscapeData = []byte("\\U00000041\\U0000004b\\U00000049\\U00000041\\U00000055\\U0000004d\\U00000034\\U00000047\\U00000036\\U0000004f\\U00000036\\U0000004e\\U00000041\\U0000004b\\U00000045\\U00000037\\U0000004c\\U00000043\\U00000044\\U0000004a")
perlEscapeData = []byte("\\x{41}\\x{4b}\\x{49}\\x{41}\\x{55}\\x{4d}\\x{34}\\x{47}\\x{36}\\x{4f}\\x{36}\\x{4e}\\x{41}\\x{4b}\\x{45}\\x{37}\\x{4c}\\x{43}\\x{44}\\x{4a}")
cssEscapeData = []byte("\\41 \\4b \\49 \\41 \\55 \\4d \\34 \\47 \\36 \\4f \\36 \\4e \\41 \\4b \\45 \\37 \\4c \\43 \\44 \\4a ")
htmlEscapeData = []byte("&#x41;&#x4b;&#x49;&#x41;&#x55;&#x4d;&#x34;&#x47;&#x36;&#x4f;&#x36;&#x4e;&#x41;&#x4b;&#x45;&#x37;&#x4c;&#x43;&#x44;&#x4a;")
percentEscapeData = []byte("%u0041%u004b%u0049%u0041%u0055%u004d%u0034%u0047%u0036%u004f%u0036%u004e%u0041%u004b%u0045%u0037%u004c%u0043%u0044%u004a")
//hexEscapeData = []byte("0x41 0x4b 0x49 0x41 0x55 0x4d 0x34 0x47 0x36 0x4f 0x36 0x4e 0x41 0x4b 0x45 0x37 0x4c 0x43 0x44 0x4a ")
// Mixed content (more realistic scenario)
mixedContentData = []byte(`
const config = {
apiKey: "\\u0041\\u004b\\u0049\\u0041\\u0055\\u004d\\u0034\\u0047\\u0036\\u004f\\u0036\\u004e\\u0041\\u004b\\u0045\\u0037\\u004c\\u0043\\u0044\\u004a",
secretKey: "\\u{6e}\\u{62}\\u{75}\\u{68}\\u{7a}\\u{4b}\\u{79}\\u{39}\\u{50}\\u{50}\\u{7a}\\u{32}\\u{7a}\\u{47}\\u{33}\\u{47}\\u{54}\\u{4a}\\u{71}\\u{4b}\\u{45}\\u{43}\\u{6e}\\u{71}\\u{4c}\\u{41}\\u{78}\\u{43}\\u{76}\\u{2f}\\u{36}\\u{68}\\u{43}\\u{6a}\\u{6b}\\u{50}\\u{68}\\u{66}\\u{58}\\u{6f}",
htmlToken: "&#x41;&#x4b;&#x49;&#x41;&#x55;&#x4d;&#x34;&#x47;&#x36;&#x4f;&#x36;&#x4e;&#x41;&#x4b;&#x45;&#x37;&#x4c;&#x43;&#x44;&#x4a;",
normalText: "This is normal text that should not be processed"
}
`)
// Large data for stress testing
largeData = func() []byte {
data := make([]byte, 0, 10000)
for i := 0; i < 100; i++ {
data = append(data, originalUnicodeData...)
data = append(data, braceEscapeData...)
data = append(data, longEscapeData...)
data = append(data, htmlEscapeData...)
data = append(data, []byte(" normal text ")...)
}
return data
}()
// No Unicode data (worst case for performance)
noUnicodeData = []byte(`
This is a large block of text with no Unicode escape sequences.
It contains various programming constructs like:
- Variable declarations: var x = 123;
- Function calls: doSomething(param1, param2);
- Comments: /* this is a comment */
- Strings: "hello world"
- Numbers: 42, 3.14159, 0xFF
- But no Unicode escapes that would trigger our decoders.
This simulates the common case where files don't contain Unicode escapes.
`)
)
// Benchmark individual decoder functions
func BenchmarkDecodeOriginalEscape(b *testing.B) {
for b.Loop() {
_ = decodeEscaped(originalUnicodeData)
}
}
func BenchmarkDecodeCodePoint(b *testing.B) {
for b.Loop() {
_ = decodeCodePoint(codePointData)
}
}
func BenchmarkDecodeBraceEscape(b *testing.B) {
for b.Loop() {
_ = decodeBraceEscape(braceEscapeData)
}
}
func BenchmarkDecodeLongEscape(b *testing.B) {
for b.Loop() {
_ = decodeLongEscape(longEscapeData)
}
}
func BenchmarkDecodePerlEscape(b *testing.B) {
for b.Loop() {
_ = decodePerlEscape(perlEscapeData)
}
}
func BenchmarkDecodeCssEscape(b *testing.B) {
for b.Loop() {
_ = decodeCssEscape(cssEscapeData)
}
}
func BenchmarkDecodeHtmlEscape(b *testing.B) {
for b.Loop() {
_ = decodeHtmlEscape(htmlEscapeData)
}
}
func BenchmarkDecodePercentEscape(b *testing.B) {
for b.Loop() {
_ = decodePercentEscape(percentEscapeData)
}
}
// func BenchmarkDecodeHexEscape(b *testing.B) {
// for i := 0; i < b.N; i++ {
// _ = decodeHexEscape(hexEscapeData)
// }
// }
// Benchmark the full FromChunk method with different data types
func BenchmarkFromChunk_OriginalFormat(b *testing.B) {
decoder := &EscapedUnicode{}
chunk := &sources.Chunk{Data: originalUnicodeData}
for b.Loop() {
_ = decoder.FromChunk(chunk)
}
}
func BenchmarkFromChunk_BraceFormat(b *testing.B) {
decoder := &EscapedUnicode{}
chunk := &sources.Chunk{Data: braceEscapeData}
for b.Loop() {
_ = decoder.FromChunk(chunk)
}
}
func BenchmarkFromChunk_LongFormat(b *testing.B) {
decoder := &EscapedUnicode{}
chunk := &sources.Chunk{Data: longEscapeData}
for b.Loop() {
_ = decoder.FromChunk(chunk)
}
}
func BenchmarkFromChunk_HtmlFormat(b *testing.B) {
decoder := &EscapedUnicode{}
chunk := &sources.Chunk{Data: htmlEscapeData}
for b.Loop() {
_ = decoder.FromChunk(chunk)
}
}
func BenchmarkFromChunk_MixedContent(b *testing.B) {
decoder := &EscapedUnicode{}
chunk := &sources.Chunk{Data: mixedContentData}
for b.Loop() {
_ = decoder.FromChunk(chunk)
}
}
func BenchmarkFromChunk_NoUnicode(b *testing.B) {
decoder := &EscapedUnicode{}
chunk := &sources.Chunk{Data: noUnicodeData}
for b.Loop() {
_ = decoder.FromChunk(chunk)
}
}
func BenchmarkFromChunk_LargeData(b *testing.B) {
decoder := &EscapedUnicode{}
chunk := &sources.Chunk{Data: largeData}
for b.Loop() {
_ = decoder.FromChunk(chunk)
}
}
// Benchmark regex matching performance (most expensive operation)
func BenchmarkRegexMatching_AllPatterns(b *testing.B) {
testData := mixedContentData
for b.Loop() {
// Simulate the pattern matching in FromChunk
_ = longEscapePat.Match(testData)
_ = braceEscapePat.Match(testData)
_ = perlEscapePat.Match(testData)
_ = htmlEscapePat.Match(testData)
_ = percentEscapePat.Match(testData)
_ = escapePat.Match(testData)
_ = codePointPat.Match(testData)
_ = cssEscapePat.Match(testData)
//_ = hexEscapePat.Match(testData)
}
}
func BenchmarkRegexMatching_NoMatch(b *testing.B) {
testData := noUnicodeData
for b.Loop() {
// Simulate the pattern matching in FromChunk on data with no matches
_ = longEscapePat.Match(testData)
_ = braceEscapePat.Match(testData)
_ = perlEscapePat.Match(testData)
_ = htmlEscapePat.Match(testData)
_ = percentEscapePat.Match(testData)
_ = escapePat.Match(testData)
_ = codePointPat.Match(testData)
_ = cssEscapePat.Match(testData)
//_ = hexEscapePat.Match(testData)
}
}
// Memory allocation benchmarks
func BenchmarkFromChunk_MemoryAllocation(b *testing.B) {
decoder := &EscapedUnicode{}
chunk := &sources.Chunk{Data: mixedContentData}
b.ReportAllocs()
for b.Loop() {
result := decoder.FromChunk(chunk)
if result != nil {
// Prevent compiler optimization
_ = result.Data
}
}
}
+133
View File
@@ -54,6 +54,139 @@ func TestUnicodeEscape_FromChunk(t *testing.T) {
},
},
// New test cases for additional Unicode escape formats
// \u{X} format - Rust, Swift, some JS, etc.
{
name: "[brace] \\u{X} format - Rust/Swift style",
chunk: &sources.Chunk{
Data: []byte("\\u{74}\\u{6f}\\u{6b}\\u{65}\\u{6e}\\u{3a}\\u{20}\\u{22}\\u{67}\\u{68}\\u{70}\\u{5f}\\u{49}\\u{77}\\u{64}\\u{4d}\\u{78}\\u{39}\\u{57}\\u{46}\\u{57}\\u{52}\\u{52}\\u{66}\\u{4d}\\u{68}\\u{54}\\u{59}\\u{69}\\u{61}\\u{56}\\u{6a}\\u{5a}\\u{37}\\u{38}\\u{4a}\\u{66}\\u{75}\\u{61}\\u{6d}\\u{76}\\u{6e}\\u{30}\\u{59}\\u{57}\\u{52}\\u{4d}\\u{30}\\u{22}"),
},
want: &sources.Chunk{
Data: []byte("token: \"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\""),
},
},
// \U00XXXXXX format - Python, etc.
{
name: "[long] \\U00XXXXXX format - Python style",
chunk: &sources.Chunk{
Data: []byte("\\U00000074\\U0000006f\\U0000006b\\U00000065\\U0000006e\\U0000003a\\U00000020\\U00000022\\U00000067\\U00000068\\U00000070\\U0000005f\\U00000049\\U00000077\\U00000064\\U0000004d\\U00000078\\U00000039\\U00000057\\U00000046\\U00000057\\U00000052\\U00000052\\U00000066\\U0000004d\\U00000068\\U00000054\\U00000059\\U00000069\\U00000061\\U00000056\\U0000006a\\U0000005a\\U00000037\\U00000038\\U0000004a\\U00000066\\U00000075\\U00000061\\U0000006d\\U00000076\\U0000006e\\U00000030\\U00000059\\U00000057\\U00000052\\U0000004d\\U00000030\\U00000022"),
},
want: &sources.Chunk{
Data: []byte("token: \"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\""),
},
},
// \x{X} format - Perl
{
name: "[perl] \\x{X} format - Perl style",
chunk: &sources.Chunk{
Data: []byte("\\x{74}\\x{6f}\\x{6b}\\x{65}\\x{6e}\\x{3a}\\x{20}\\x{22}\\x{67}\\x{68}\\x{70}\\x{5f}\\x{49}\\x{77}\\x{64}\\x{4d}\\x{78}\\x{39}\\x{57}\\x{46}\\x{57}\\x{52}\\x{52}\\x{66}\\x{4d}\\x{68}\\x{54}\\x{59}\\x{69}\\x{61}\\x{56}\\x{6a}\\x{5a}\\x{37}\\x{38}\\x{4a}\\x{66}\\x{75}\\x{61}\\x{6d}\\x{76}\\x{6e}\\x{30}\\x{59}\\x{57}\\x{52}\\x{4d}\\x{30}\\x{22}"),
},
want: &sources.Chunk{
Data: []byte("token: \"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\""),
},
},
// \X format - CSS (space delimited)
// ToDo: Look into supporting CSS where there is no whitespace ex: \013322\013171\013001. Currently not supported by this implementation.
{
name: "[css] \\X format - CSS style",
chunk: &sources.Chunk{
Data: []byte("\\74 \\6f \\6b \\65 \\6e \\3a \\20 \\22 \\67 \\68 \\70 \\5f \\49 \\77 \\64 \\4d \\78 \\39 \\57 \\46 \\57 \\52 \\52 \\66 \\4d \\68 \\54 \\59 \\69 \\61 \\56 \\6a \\5a \\37 \\38 \\4a \\66 \\75 \\61 \\6d \\76 \\6e \\30 \\59 \\57 \\52 \\4d \\30 \\22 "),
},
want: &sources.Chunk{
Data: []byte("token: \"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\""),
},
},
// &#xX; format - HTML/XML
{
name: "[html] &#xX; format - HTML/XML style",
chunk: &sources.Chunk{
Data: []byte("&#x74;&#x6f;&#x6b;&#x65;&#x6e;&#x3a;&#x20;&#x22;&#x67;&#x68;&#x70;&#x5f;&#x49;&#x77;&#x64;&#x4d;&#x78;&#x39;&#x57;&#x46;&#x57;&#x52;&#x52;&#x66;&#x4d;&#x68;&#x54;&#x59;&#x69;&#x61;&#x56;&#x6a;&#x5a;&#x37;&#x38;&#x4a;&#x66;&#x75;&#x61;&#x6d;&#x76;&#x6e;&#x30;&#x59;&#x57;&#x52;&#x4d;&#x30;&#x22;"),
},
want: &sources.Chunk{
Data: []byte("token: \"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\""),
},
},
// %uXXXX format - Percent-encoding (non-standard)
{
name: "[percent] %uXXXX format - Percent encoding",
chunk: &sources.Chunk{
Data: []byte("%u0074%u006f%u006b%u0065%u006e%u003a%u0020%u0022%u0067%u0068%u0070%u005f%u0049%u0077%u0064%u004d%u0078%u0039%u0057%u0046%u0057%u0052%u0052%u0066%u004d%u0068%u0054%u0059%u0069%u0061%u0056%u006a%u005a%u0037%u0038%u004a%u0066%u0075%u0061%u006d%u0076%u006e%u0030%u0059%u0057%u0052%u004d%u0030%u0022"),
},
want: &sources.Chunk{
Data: []byte("token: \"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\""),
},
},
// // 0xX format - Hexadecimal notation with space separation
// {
// name: "[hex] 0xX format - Hex with spaces",
// chunk: &sources.Chunk{
// Data: []byte("0x74 0x6f 0x6b 0x65 0x6e 0x3a 0x20 0x22 0x67 0x68 0x70 0x5f 0x49 0x77 0x64 0x4d 0x78 0x39 0x57 0x46 0x57 0x52 0x52 0x66 0x4d 0x68 0x54 0x59 0x69 0x61 0x56 0x6a 0x5a 0x37 0x38 0x4a 0x66 0x75 0x61 0x6d 0x76 0x6e 0x30 0x59 0x57 0x52 0x4d 0x30 0x22 "),
// },
// want: &sources.Chunk{
// Data: []byte("token: \"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\""),
// },
// },
// // 0xX format - Hexadecimal notation with comma separation
// {
// name: "[hex] 0xX format - Hex with commas",
// chunk: &sources.Chunk{
// Data: []byte("0x74,0x6f,0x6b,0x65,0x6e,0x3a,0x20,0x22,0x67,0x68,0x70,0x5f,0x49,0x77,0x64,0x4d,0x78,0x39,0x57,0x46,0x57,0x52,0x52,0x66,0x4d,0x68,0x54,0x59,0x69,0x61,0x56,0x6a,0x5a,0x37,0x38,0x4a,0x66,0x75,0x61,0x6d,0x76,0x6e,0x30,0x59,0x57,0x52,0x4d,0x30,0x22"),
// },
// want: &sources.Chunk{
// Data: []byte("token: \"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\""),
// },
// },
// Test cases for mixed content with new formats
{
name: "[mixed] \\u{X} in code context",
chunk: &sources.Chunk{
Data: []byte("const secret = \"\\u{41}\\u{4b}\\u{49}\\u{41}\\u{55}\\u{4d}\\u{34}\\u{47}\\u{36}\\u{4f}\\u{36}\\u{4e}\\u{41}\\u{4b}\\u{45}\\u{37}\\u{4c}\\u{43}\\u{44}\\u{4a}\";"),
},
want: &sources.Chunk{
Data: []byte("const secret = \"AKIAUM4G6O6NAKE7LCDJ\";"),
},
},
{
name: "[mixed] HTML entity in web context",
chunk: &sources.Chunk{
Data: []byte("<span>AWS Key: &#x41;&#x4b;&#x49;&#x41;&#x55;&#x4d;&#x34;&#x47;&#x36;&#x4f;&#x36;&#x4e;&#x41;&#x4b;&#x45;&#x37;&#x4c;&#x43;&#x44;&#x4a;</span>"),
},
want: &sources.Chunk{
Data: []byte("<span>AWS Key: AKIAUM4G6O6NAKE7LCDJ</span>"),
},
},
// Test cases for higher Unicode values (non-BMP)
{
name: "[emoji] \\u{X} with emoji",
chunk: &sources.Chunk{
Data: []byte("\\u{1f600} Happy face emoji"),
},
want: &sources.Chunk{
Data: []byte("😀 Happy face emoji"),
},
},
{
name: "[emoji] \\U00XXXXXX with emoji",
chunk: &sources.Chunk{
Data: []byte("\\U0001f600 Happy face emoji"),
},
want: &sources.Chunk{
Data: []byte("😀 Happy face emoji"),
},
},
// nothing
{
name: "no escaped",
+13 -1
View File
@@ -27,6 +27,18 @@ func TestUTF16Decoder(t *testing.T) {
expected: []byte("Hello World"),
expectNil: false,
},
{
name: "Valid UTF-16LE input with BOM (FF FE)",
input: []byte{255, 254, 72, 0, 101, 0, 108, 0, 108, 0, 111, 0, 32, 0, 87, 0, 111, 0, 114, 0, 108, 0, 100, 0},
expected: []byte("Hello World"),
expectNil: false,
},
{
name: "Valid UTF-16BE input with BOM (FE FF)",
input: []byte{254, 255, 0, 72, 0, 101, 0, 108, 0, 108, 0, 111, 0, 32, 0, 87, 0, 111, 0, 114, 0, 108, 0, 100},
expected: []byte("Hello World"),
expectNil: false,
},
{
name: "Invalid UTF-16 input (it's UTF-8)",
input: []byte("Hello World!"),
@@ -88,7 +100,7 @@ func BenchmarkUtf16ToUtf8(b *testing.B) {
// Example UTF-16LE encoded data
data := []byte{72, 0, 101, 0, 108, 0, 108, 0, 111, 0, 32, 0, 87, 0, 111, 0, 114, 0, 108, 0, 100, 0}
for n := 0; n < b.N; n++ {
for b.Loop() {
_, _ = utf16ToUTF8(data)
}
}
+1 -1
View File
@@ -375,7 +375,7 @@ go
away.`)
func Benchmark_extractSubstrings(b *testing.B) {
for i := 0; i < b.N; i++ {
for b.Loop() {
extractSubstrings(testBytes)
}
}
+53 -25
View File
@@ -2,20 +2,15 @@ package abstract
import (
"context"
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
)
var (
validPattern = "qwerty12345iugt67s7a7sa0akhsxz82"
invalidPattern = "zxcvbr12345iugt67s7a7sa0akhsXz820"
)
func TestAbstract_Pattern(t *testing.T) {
d := Scanner{}
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
@@ -26,19 +21,58 @@ func TestAbstract_Pattern(t *testing.T) {
want []string
}{
{
name: "valid pattern",
input: fmt.Sprintf("abstract token = '%s'", validPattern),
want: []string{validPattern},
name: "valid pattern",
input: `
[INFO] Sending request to abstract API
[DEBUG] Using API_KEY=oxpf4a93fjovt0v1z6lltcbcizlrml98
[INFO] Response received: 200 OK
`,
want: []string{"oxpf4a93fjovt0v1z6lltcbcizlrml98"},
},
{
name: "valid pattern - out of prefix range",
input: fmt.Sprintf("abstract token keyword is not close to the real token = '%s'", validPattern),
want: nil,
name: "valid pattern - xml",
input: `
<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
<scope>GLOBAL</scope>
<id>{abstract}</id>
<secret>{abstract AQAAABAAA 5422358j60yxo9nc0dbpxby602tsxd6j}</secret>
<description>configuration for production</description>
<creationDate>2023-05-18T14:32:10Z</creationDate>
<owner>jenkins-admin</owner>
</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
`,
want: []string{"5422358j60yxo9nc0dbpxby602tsxd6j"},
},
{
name: "invalid pattern",
input: fmt.Sprintf("abstract = '%s'", invalidPattern),
want: nil,
name: "valid pattern - two keys",
input: `
[INFO] Sending request to abstract API
[DEBUG] Using API_KEY=oxpf4a93fjovt0v1z6lltcbcizlrml98
[Error] Response received: 401 UnAuthorized
[INFO] Sending request to abstract API
[DEBUG] Using API_KEY=muytrs09876iugt67s7a7sa0akhsxz82
[INFO] Response received: 200 OK
`,
want: []string{"oxpf4a93fjovt0v1z6lltcbcizlrml98", "muytrs09876iugt67s7a7sa0akhsxz82"},
},
{
name: "valid pattern - out of prefix range",
input: `
[INFO] Sending request to abstract API
[INFO] Processing request
[Info] Response received: 200 OK
[DEBUG] Used API_KEY=oxpf4a93fjovt0v1z6lltcbcizlrml98
`,
want: nil,
},
{
name: "invalid pattern",
input: `
[INFO] Sending request to abstract API
[DEBUG] Using API_KEY=zxcvbr12345iugt67s7a7sa0akhsXz820
[ERROR] Response received: 400 BadRequest
`,
want: nil,
},
}
@@ -46,22 +80,15 @@ func TestAbstract_Pattern(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
if len(matchedDetectors) == 0 {
t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
return
}
results, err := d.FromData(context.Background(), false, []byte(test.input))
if err != nil {
t.Errorf("error = %v", err)
return
}
require.NoError(t, err)
if len(results) != len(test.want) {
if len(results) == 0 {
t.Errorf("did not receive result")
} else {
t.Errorf("expected %d results, only received %d", len(test.want), len(results))
}
t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
return
}
@@ -73,6 +100,7 @@ func TestAbstract_Pattern(t *testing.T) {
actual[string(r.Raw)] = struct{}{}
}
}
expected := make(map[string]struct{}, len(test.want))
for _, v := range test.want {
expected[v] = struct{}{}
+41 -25
View File
@@ -2,20 +2,15 @@ package abuseipdb
import (
"context"
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
)
var (
validPattern = "123abcdef456ghijkl789mnopqr012stuvwx3455123abcdef456ghijkl789mnopqr012stuvwx3455"
invalidPattern = "123abcdef456Ghijkl789mnopqr012stuvwx3455123abcdef456ghijkl789mnopqr012stuvwX"
)
func TestAbuseipdb_Pattern(t *testing.T) {
d := Scanner{}
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
@@ -26,19 +21,46 @@ func TestAbuseipdb_Pattern(t *testing.T) {
want []string
}{
{
name: "valid pattern",
input: fmt.Sprintf("abuseipdb token = '%s'", validPattern),
want: []string{validPattern},
name: "valid pattern",
input: `
[INFO] Sending request to abuseipdb API
[DEBUG] Using API_KEY=o8oqti3tghu2xic76ii4t7jb9bxuzd4200j1yrkdjl6s8834hx4dgz1wwo90diqraakjd13sljcjkfnf
[INFO] Response received: 200 OK
`,
want: []string{"o8oqti3tghu2xic76ii4t7jb9bxuzd4200j1yrkdjl6s8834hx4dgz1wwo90diqraakjd13sljcjkfnf"},
},
{
name: "valid pattern - out of prefix range",
input: fmt.Sprintf("abuseipdb token keyword is not close to the real token = '%s'", validPattern),
want: nil,
name: "valid pattern - xml",
input: `
<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
<scope>GLOBAL</scope>
<id>{abuseipdb}</id>
<secret>{abuseipdb AQAAABAAA zgtj0q3v38u4pthc6nmy02n60bj244u5o9j47ln1jlue5mxzaasfi29x4dzcbxroawvkm26thtr61066}</secret>
<description>configuration for production</description>
<creationDate>2023-05-18T14:32:10Z</creationDate>
<owner>jenkins-admin</owner>
</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
`,
want: []string{"zgtj0q3v38u4pthc6nmy02n60bj244u5o9j47ln1jlue5mxzaasfi29x4dzcbxroawvkm26thtr61066"},
},
{
name: "invalid pattern",
input: fmt.Sprintf("abuseipdb = '%s'", invalidPattern),
want: nil,
name: "valid pattern - out of prefix range",
input: `
[INFO] Sending request to abuseipdb API
[INFO] Processing request
[Info] Response received: 200 OK
[DEBUG] Used API_KEY=o8oqti3tghu2xic76ii4t7jb9bxuzd4200j1yrkdjl6s8834hx4dgz1wwo90diqraakjd13sljcjkfnf
`,
want: nil,
},
{
name: "invalid pattern",
input: `
[INFO] Sending request to abuseipdb API
[DEBUG] Using API_KEY=7e4abcdef456Ghijkl789mnopqr012stuvwx3455123abcdef456ghijkl789mnopqr012stuvwX
[ERROR] Response received: 400 BadRequest
`,
want: nil,
},
}
@@ -46,22 +68,15 @@ func TestAbuseipdb_Pattern(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
if len(matchedDetectors) == 0 {
t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
return
}
results, err := d.FromData(context.Background(), false, []byte(test.input))
if err != nil {
t.Errorf("error = %v", err)
return
}
require.NoError(t, err)
if len(results) != len(test.want) {
if len(results) == 0 {
t.Errorf("did not receive result")
} else {
t.Errorf("expected %d results, only received %d", len(test.want), len(results))
}
t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
return
}
@@ -73,6 +88,7 @@ func TestAbuseipdb_Pattern(t *testing.T) {
actual[string(r.Raw)] = struct{}{}
}
}
expected := make(map[string]struct{}, len(test.want))
for _, v := range test.want {
expected[v] = struct{}{}
+41 -25
View File
@@ -2,20 +2,15 @@ package abyssale
import (
"context"
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
)
var (
validPattern = "123abcDEF456ghiJKL789mnoPQR012stuVWX3456"
invalidPattern = "123abcDEF456ghiJKL789mnoPQR012stuVWX"
)
func TestAbyssale_Pattern(t *testing.T) {
d := Scanner{}
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
@@ -26,19 +21,46 @@ func TestAbyssale_Pattern(t *testing.T) {
want []string
}{
{
name: "valid pattern",
input: fmt.Sprintf("abyssale token = '%s'", validPattern),
want: []string{validPattern},
name: "valid pattern",
input: `
[INFO] Sending request to abyssale API
[DEBUG] Using API_KEY=rWE8I0axy6Fvw40RE8tsNS3L7zBU5vAhEnW4hq9G
[INFO] Response received: 200 OK
`,
want: []string{"rWE8I0axy6Fvw40RE8tsNS3L7zBU5vAhEnW4hq9G"},
},
{
name: "valid pattern - out of prefix range",
input: fmt.Sprintf("abyssale token keyword is not close to the real token = '%s'", validPattern),
want: nil,
name: "valid pattern - xml",
input: `
<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
<scope>GLOBAL</scope>
<id>{abyssale}</id>
<secret>{abyssale AQAAABAAA xTiPNSDg6JjzG8fWoLb8JlE8SBcMKkCx2fZLZD91}</secret>
<description>configuration for production</description>
<creationDate>2023-05-18T14:32:10Z</creationDate>
<owner>jenkins-admin</owner>
</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
`,
want: []string{"xTiPNSDg6JjzG8fWoLb8JlE8SBcMKkCx2fZLZD91"},
},
{
name: "invalid pattern",
input: fmt.Sprintf("abyssale = '%s'", invalidPattern),
want: nil,
name: "valid pattern - out of prefix range",
input: `
[INFO] Sending request to abyssale API
[INFO] Processing request
[Info] Response received: 200 OK
[DEBUG] Used API_KEY=rWE8I0axy6Fvw40RE8tsNS3L7zBU5vAhEnW4hq9G
`,
want: nil,
},
{
name: "invalid pattern",
input: `
[INFO] Sending request to abyssale API
[DEBUG] Using API_KEY=rWE8_0axy6Fvw40RE8tsNS3L7zBU5vAhEnW4hq9G
[ERROR] Response received: 400 BadRequest
`,
want: nil,
},
}
@@ -46,22 +68,15 @@ func TestAbyssale_Pattern(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
if len(matchedDetectors) == 0 {
t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
return
}
results, err := d.FromData(context.Background(), false, []byte(test.input))
if err != nil {
t.Errorf("error = %v", err)
return
}
require.NoError(t, err)
if len(results) != len(test.want) {
if len(results) == 0 {
t.Errorf("did not receive result")
} else {
t.Errorf("expected %d results, only received %d", len(test.want), len(results))
}
t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
return
}
@@ -73,6 +88,7 @@ func TestAbyssale_Pattern(t *testing.T) {
actual[string(r.Raw)] = struct{}{}
}
}
expected := make(map[string]struct{}, len(test.want))
for _, v := range test.want {
expected[v] = struct{}{}
+79
View File
@@ -0,0 +1,79 @@
package detectors
// AccountFilter implements account-based filtering functionality that detectors can embed
// to gain allow and deny list capabilities for account IDs.
type AccountFilter struct {
allowedAccounts map[string]struct{}
deniedAccounts map[string]struct{}
}
// SetAllowedAccounts configures the allowed account IDs.
// If set, only accounts in this list will be verified.
func (a *AccountFilter) SetAllowedAccounts(accountIDs []string) {
if len(accountIDs) == 0 {
a.allowedAccounts = nil
return
}
accounts := make(map[string]struct{}, len(accountIDs))
for _, accountID := range accountIDs {
accounts[accountID] = struct{}{}
}
a.allowedAccounts = accounts
}
// SetDeniedAccounts configures the denied account IDs.
// Accounts in this list will never be verified.
func (a *AccountFilter) SetDeniedAccounts(accountIDs []string) {
if len(accountIDs) == 0 {
a.deniedAccounts = nil
return
}
accounts := make(map[string]struct{}, len(accountIDs))
for _, accountID := range accountIDs {
accounts[accountID] = struct{}{}
}
a.deniedAccounts = accounts
}
// ShouldSkipAccount checks if an account ID should be skipped for verification
// based on allow and deny lists.
//
// Precedence: deny list > allow list (if account is in both, it's denied)
func (a *AccountFilter) ShouldSkipAccount(accountID string) bool {
// Check deny list first - takes precedence
if len(a.deniedAccounts) > 0 {
if _, isDenied := a.deniedAccounts[accountID]; isDenied {
return true
}
}
// Check allow list - if populated, account must be in it
if len(a.allowedAccounts) > 0 {
if _, isAllowed := a.allowedAccounts[accountID]; !isAllowed {
return true
}
}
// Account is allowed for verification
return false
}
// IsInDenyList checks if an account ID is in the deny list
func (a *AccountFilter) IsInDenyList(accountID string) bool {
if len(a.deniedAccounts) == 0 {
return false
}
_, isDenied := a.deniedAccounts[accountID]
return isDenied
}
// IsInAllowList checks if an account ID is in the allow list
func (a *AccountFilter) IsInAllowList(accountID string) bool {
if len(a.allowedAccounts) == 0 {
return false
}
_, isAllowed := a.allowedAccounts[accountID]
return isAllowed
}
+112
View File
@@ -0,0 +1,112 @@
package detectors
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestEmbeddedAccountFilter(t *testing.T) {
type Scanner struct{ AccountFilter }
t.Run("no filtering configured - should not skip", func(t *testing.T) {
var s Scanner // Fresh instance for this test
shouldSkip := s.ShouldSkipAccount("test-account")
assert.False(t, shouldSkip)
assert.False(t, s.IsInDenyList("test-account"))
assert.False(t, s.IsInAllowList("test-account"))
})
t.Run("allowed accounts only", func(t *testing.T) {
var s Scanner // Fresh instance for this test
s.SetAllowedAccounts([]string{"allowed-account-1", "allowed-account-2"})
// Account in allow list - should not skip
shouldSkip := s.ShouldSkipAccount("allowed-account-1")
assert.False(t, shouldSkip)
assert.True(t, s.IsInAllowList("allowed-account-1"))
// Account not in allow list - should skip
shouldSkip = s.ShouldSkipAccount("other-account")
assert.True(t, shouldSkip)
assert.False(t, s.IsInAllowList("other-account"))
})
t.Run("denied accounts only", func(t *testing.T) {
var s Scanner // Fresh instance for this test
s.SetDeniedAccounts([]string{"denied-account-1", "denied-account-2"})
// Account in deny list - should skip
shouldSkip := s.ShouldSkipAccount("denied-account-1")
assert.True(t, shouldSkip)
assert.True(t, s.IsInDenyList("denied-account-1"))
// Account not in deny list - should not skip (no allow list restrictions)
shouldSkip = s.ShouldSkipAccount("other-account")
assert.False(t, shouldSkip)
assert.False(t, s.IsInDenyList("other-account"))
})
t.Run("deny list takes precedence over allow list", func(t *testing.T) {
var s Scanner // Fresh instance for this test
s.SetAllowedAccounts([]string{"conflicted-account", "allowed-only-account"})
s.SetDeniedAccounts([]string{"conflicted-account"}) // Same account in both lists
// Account is in both allow and deny lists - deny takes precedence
shouldSkip := s.ShouldSkipAccount("conflicted-account")
assert.True(t, shouldSkip)
assert.True(t, s.IsInDenyList("conflicted-account"))
assert.True(t, s.IsInAllowList("conflicted-account"))
// Account only in allow list - should not skip
shouldSkip = s.ShouldSkipAccount("allowed-only-account")
assert.False(t, shouldSkip)
assert.False(t, s.IsInDenyList("allowed-only-account"))
assert.True(t, s.IsInAllowList("allowed-only-account"))
})
t.Run("allow list with denied account not in allow list", func(t *testing.T) {
var s Scanner // Fresh instance for this test
s.SetAllowedAccounts([]string{"trusted-account"}) // Allow one account
s.SetDeniedAccounts([]string{"blocked-account"}) // Deny different account
// Account in deny list (not in allow list) - should skip due to deny list
shouldSkip := s.ShouldSkipAccount("blocked-account")
assert.True(t, shouldSkip)
assert.True(t, s.IsInDenyList("blocked-account"))
assert.False(t, s.IsInAllowList("blocked-account"))
// Account in allow list (not in deny list) - should not skip
shouldSkip = s.ShouldSkipAccount("trusted-account")
assert.False(t, shouldSkip)
assert.False(t, s.IsInDenyList("trusted-account"))
assert.True(t, s.IsInAllowList("trusted-account"))
// Account in neither list - should skip due to allow list restriction
shouldSkip = s.ShouldSkipAccount("unknown-account")
assert.True(t, shouldSkip)
assert.False(t, s.IsInDenyList("unknown-account"))
assert.False(t, s.IsInAllowList("unknown-account"))
})
t.Run("clearing lists", func(t *testing.T) {
var s Scanner // Fresh instance for this test
s.SetAllowedAccounts([]string{"initial-allowed"})
s.SetDeniedAccounts([]string{"initial-denied"})
// Verify initial state
assert.True(t, s.ShouldSkipAccount("random-account")) // Not in allow list
assert.True(t, s.ShouldSkipAccount("initial-denied")) // In deny list
// Clear allowed accounts with nil
s.SetAllowedAccounts(nil)
assert.False(t, s.ShouldSkipAccount("random-account")) // No allow list restriction
assert.True(t, s.ShouldSkipAccount("initial-denied")) // Still in deny list
// Clear denied accounts with empty slice
s.SetDeniedAccounts([]string{})
assert.False(t, s.ShouldSkipAccount("initial-denied")) // No longer denied
assert.False(t, s.ShouldSkipAccount("initial-allowed")) // No restrictions
})
}
@@ -2,21 +2,15 @@ package accuweather
import (
"context"
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
)
var (
validPattern = "DqFtwc490oPc%xaE67sBSF741M56%sd091A"
invalidPattern = "DqFtwc490oPc%xaE67sBSF741M56=sd091A"
validPatternLowEntropy = "DsFtwfaEsAPS%eaEsaESEsFesfMsfMsDmdA"
)
func TestAccuWeather_Pattern(t *testing.T) {
d := Scanner{}
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
@@ -27,24 +21,55 @@ func TestAccuWeather_Pattern(t *testing.T) {
want []string
}{
{
name: "valid pattern",
input: fmt.Sprintf("accuweather token = '%s'", validPattern),
want: []string{validPattern},
name: "valid pattern",
input: `
[INFO] Sending request to accuweather API
[DEBUG] Using API_KEY=WAgP6m4gYc1qe%HnjWAAF5HBKL%i6kwrsbD
[INFO] Response received: 200 OK
`,
want: []string{"WAgP6m4gYc1qe%HnjWAAF5HBKL%i6kwrsbD"},
},
{
name: "valid pattern - out of prefix range",
input: fmt.Sprintf("accuweather token keyword is not close to the real token = '%s'", validPattern),
want: nil,
name: "valid pattern - xml",
input: `
<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
<scope>GLOBAL</scope>
<id>{accuweather}</id>
<secret>{accuweather AQAAABAAA ErOAU9rTSuX6IfHFGsJbpK3bCC1jIEX%gtj}</secret>
<description>configuration for production</description>
<creationDate>2023-05-18T14:32:10Z</creationDate>
<owner>jenkins-admin</owner>
</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
`,
want: []string{"ErOAU9rTSuX6IfHFGsJbpK3bCC1jIEX%gtj"},
},
{
name: "invalid pattern",
input: fmt.Sprintf("accuweather = '%s'", invalidPattern),
want: nil,
name: "valid pattern - out of prefix range",
input: `
[INFO] Sending request to accuweather API
[INFO] Processing request
[Info] Response received: 200 OK
[DEBUG] Used API_KEY=WAgP6m4gYc1qe%HnjWAAF5HBKL%i6kwrsbD
`,
want: nil,
},
{
name: "valid pattern - Shannon entropy below threshold",
input: fmt.Sprintf("accuweather = '%s'", validPatternLowEntropy),
want: nil,
name: "invalid pattern",
input: `
[INFO] Sending request to accuweather API
[DEBUG] Using API_KEY=WAgP6m4gYc1qe$HnjWAAF5HBKL%i6kwrsbD
[Error] Response received: 400 BadRequest
`,
want: nil,
},
{
name: "valid pattern - Shannon entropy below threshold",
input: `
[INFO] Sending request to accuweather API
[DEBUG] Using API_KEY=WAAP6A4gYA1qA%HAaWAAFAHBAL%a6kwwwbD
[ERROR] Response received: 400 BadRequest
`,
want: nil,
},
}
@@ -52,22 +77,15 @@ func TestAccuWeather_Pattern(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
if len(matchedDetectors) == 0 {
t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
return
}
results, err := d.FromData(context.Background(), false, []byte(test.input))
if err != nil {
t.Errorf("error = %v", err)
return
}
require.NoError(t, err)
if len(results) != len(test.want) {
if len(results) == 0 {
t.Errorf("did not receive result")
} else {
t.Errorf("expected %d results, only received %d", len(test.want), len(results))
}
t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
return
}
@@ -79,6 +97,7 @@ func TestAccuWeather_Pattern(t *testing.T) {
actual[string(r.Raw)] = struct{}{}
}
}
expected := make(map[string]struct{}, len(test.want))
for _, v := range test.want {
expected[v] = struct{}{}
@@ -2,20 +2,15 @@ package accuweather
import (
"context"
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
)
var (
validPattern = "dqftwc490oPcxae67sBSF741M56d091a"
invalidPattern = "dqftwc49%oPcxae67sBSF741M56f091a"
)
func TestAccuWeather_Pattern(t *testing.T) {
d := Scanner{}
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
@@ -26,19 +21,46 @@ func TestAccuWeather_Pattern(t *testing.T) {
want []string
}{
{
name: "valid pattern",
input: fmt.Sprintf("accuweather token = '%s'", validPattern),
want: []string{validPattern},
name: "valid pattern",
input: `
[INFO] Sending request to accuweather API
[DEBUG] Using API_KEY=Qh6DP6Zf7vHtmnDDsbS219qcz4d883Y9
[INFO] Response received: 200 OK
`,
want: []string{"Qh6DP6Zf7vHtmnDDsbS219qcz4d883Y9"},
},
{
name: "valid pattern - out of prefix range",
input: fmt.Sprintf("accuweather token keyword is not close to the real token = '%s'", validPattern),
want: nil,
name: "valid pattern - xml",
input: `
<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
<scope>GLOBAL</scope>
<id>{accuweather}</id>
<secret>{accuweather AQAAABAAA BJDD9bYh8bR586Wcw3F1lvkUYy3RZZbD}</secret>
<description>configuration for production</description>
<creationDate>2023-05-18T14:32:10Z</creationDate>
<owner>jenkins-admin</owner>
</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
`,
want: []string{"BJDD9bYh8bR586Wcw3F1lvkUYy3RZZbD"},
},
{
name: "invalid pattern",
input: fmt.Sprintf("accuweather = '%s'", invalidPattern),
want: nil,
name: "valid pattern - out of prefix range",
input: `
[INFO] Sending request to accuweather API
[INFO] Processing request
[Info] Response received: 200 OK
[DEBUG] Used API_KEY=Qh6DP6Zf7vHtmnDDsbS219qcz4d883Y9
`,
want: nil,
},
{
name: "invalid pattern",
input: `
[INFO] Sending request to accuweather API
[DEBUG] Using API_KEY=Qh6DP6Zf7vHtm@DDsbS219qcz4d883Y9
[ERROR] Response received: 400 BadRequest
`,
want: nil,
},
}
@@ -46,22 +68,15 @@ func TestAccuWeather_Pattern(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
if len(matchedDetectors) == 0 {
t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
return
}
results, err := d.FromData(context.Background(), false, []byte(test.input))
if err != nil {
t.Errorf("error = %v", err)
return
}
require.NoError(t, err)
if len(results) != len(test.want) {
if len(results) == 0 {
t.Errorf("did not receive result")
} else {
t.Errorf("expected %d results, only received %d", len(test.want), len(results))
}
t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
return
}
@@ -73,6 +88,7 @@ func TestAccuWeather_Pattern(t *testing.T) {
actual[string(r.Raw)] = struct{}{}
}
}
expected := make(map[string]struct{}, len(test.want))
for _, v := range test.want {
expected[v] = struct{}{}
@@ -0,0 +1,167 @@
//go:build detectors
// +build detectors
package adafruitio
import (
"context"
"fmt"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
)
func TestAdafruitIO_FromChunk(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
if err != nil {
t.Fatalf("could not get test secrets from GCP: %s", err)
}
secret := testSecrets.MustGetField("ADAFRUITIO")
inactiveSecret := testSecrets.MustGetField("ADAFRUITIO_INACTIVE")
type args struct {
ctx context.Context
data []byte
verify bool
}
tests := []struct {
name string
s Scanner
args args
want []detectors.Result
wantErr bool
}{
{
name: "found, verified",
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf("You can find a adafruitio secret %s within", secret)),
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_AdafruitIO,
Verified: true,
},
},
wantErr: false,
},
{
name: "found, real secrets, verification error due to timeout",
s: Scanner{client: common.SaneHttpClientTimeOut(10 * time.Microsecond)},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf("You can find a adafruitio secret %s within", secret)),
verify: true,
},
want: func() []detectors.Result {
r := detectors.Result{
DetectorType: detectorspb.DetectorType_AdafruitIO,
Verified: false,
}
r.SetVerificationError(context.DeadlineExceeded)
return []detectors.Result{r}
}(),
wantErr: false,
},
{
name: "found, real secrets, verification error due to unexpected api surface",
s: Scanner{client: common.ConstantResponseHttpClient(500, "{}")},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf("You can find a adafruitio secret %s within", secret)),
verify: true,
},
want: func() []detectors.Result {
r := detectors.Result{
DetectorType: detectorspb.DetectorType_AdafruitIO,
Verified: false,
}
r.SetVerificationError(fmt.Errorf("unexpected HTTP response status 500"))
return []detectors.Result{r}
}(),
wantErr: false,
},
{
name: "found, unverified",
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf("You can find a adafruitio secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_AdafruitIO,
Verified: false,
},
},
wantErr: false,
},
{
name: "not found",
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte("You cannot find the secret within"),
verify: true,
},
want: nil,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
if (err != nil) != tt.wantErr {
t.Errorf("AdafruitIO.FromData() error = %v, wantErr %v", err, tt.wantErr)
return
}
for i := range got {
if len(got[i].Raw) == 0 {
t.Fatalf("no raw secret present: \n %+v", got[i])
}
gotErr := ""
if got[i].VerificationError() != nil {
gotErr = got[i].VerificationError().Error()
}
wantErr := ""
if tt.want[i].VerificationError() != nil {
wantErr = tt.want[i].VerificationError().Error()
}
if gotErr != wantErr {
t.Fatalf("wantVerificationError = %v, verification error = %v", tt.want[i].VerificationError(), got[i].VerificationError())
}
}
ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "RawV2", "verificationError")
if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
t.Errorf("AdafruitIO.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
}
})
}
}
func BenchmarkFromData(benchmark *testing.B) {
ctx := context.Background()
s := Scanner{}
for name, data := range detectors.MustGetBenchmarkData() {
benchmark.Run(name, func(b *testing.B) {
b.ResetTimer()
for n := 0; n < b.N; n++ {
_, err := s.FromData(ctx, false, data)
if err != nil {
b.Fatal(err)
}
}
})
}
}
+63 -138
View File
@@ -1,167 +1,92 @@
//go:build detectors
// +build detectors
package adafruitio
import (
"context"
"fmt"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/stretchr/testify/require"
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
)
func TestAdafruitIO_FromChunk(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
if err != nil {
t.Fatalf("could not get test secrets from GCP: %s", err)
}
secret := testSecrets.MustGetField("ADAFRUITIO")
inactiveSecret := testSecrets.MustGetField("ADAFRUITIO_INACTIVE")
func TestAdafruitio_Pattern(t *testing.T) {
d := Scanner{}
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
type args struct {
ctx context.Context
data []byte
verify bool
}
tests := []struct {
name string
s Scanner
args args
want []detectors.Result
wantErr bool
name string
input string
want []string
}{
{
name: "found, verified",
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf("You can find a adafruitio secret %s within", secret)),
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_AdafruitIO,
Verified: true,
},
},
wantErr: false,
name: "valid pattern",
input: `
[INFO] Sending request to the API
[DEBUG] Using API_KEY=aio_VxEqGaqgMgZej3DceezbBy03eWyW
[INFO] Response received: 200 OK
`,
want: []string{"aio_VxEqGaqgMgZej3DceezbBy03eWyW"},
},
{
name: "found, real secrets, verification error due to timeout",
s: Scanner{client: common.SaneHttpClientTimeOut(10 * time.Microsecond)},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf("You can find a adafruitio secret %s within", secret)),
verify: true,
},
want: func() []detectors.Result {
r := detectors.Result{
DetectorType: detectorspb.DetectorType_AdafruitIO,
Verified: false,
}
r.SetVerificationError(context.DeadlineExceeded)
return []detectors.Result{r}
}(),
wantErr: false,
name: "valid pattern - xml",
input: `
<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
<scope>GLOBAL</scope>
<id>{adafruitio}</id>
<secret>{AQAAABAAA aio_cQD77DF9SgsYbgWcxJbpLOlR5emX}</secret>
<description>configuration for production</description>
<creationDate>2023-05-18T14:32:10Z</creationDate>
<owner>jenkins-admin</owner>
</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
`,
want: []string{"aio_cQD77DF9SgsYbgWcxJbpLOlR5emX"},
},
{
name: "found, real secrets, verification error due to unexpected api surface",
s: Scanner{client: common.ConstantResponseHttpClient(500, "{}")},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf("You can find a adafruitio secret %s within", secret)),
verify: true,
},
want: func() []detectors.Result {
r := detectors.Result{
DetectorType: detectorspb.DetectorType_AdafruitIO,
Verified: false,
}
r.SetVerificationError(fmt.Errorf("unexpected HTTP response status 500"))
return []detectors.Result{r}
}(),
wantErr: false,
},
{
name: "found, unverified",
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf("You can find a adafruitio secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_AdafruitIO,
Verified: false,
},
},
wantErr: false,
},
{
name: "not found",
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte("You cannot find the secret within"),
verify: true,
},
want: nil,
wantErr: false,
name: "invalid pattern",
input: `
[INFO] Sending request to the API
[DEBUG] Using API_KEY=aio_VxEqGaqgMgZej3DceezbBy03eWyWa
[ERROR] Response received: 401 UnAuthorized
`,
want: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
if (err != nil) != tt.wantErr {
t.Errorf("AdafruitIO.FromData() error = %v, wantErr %v", err, tt.wantErr)
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
if len(matchedDetectors) == 0 {
t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
return
}
for i := range got {
if len(got[i].Raw) == 0 {
t.Fatalf("no raw secret present: \n %+v", got[i])
}
gotErr := ""
if got[i].VerificationError() != nil {
gotErr = got[i].VerificationError().Error()
}
wantErr := ""
if tt.want[i].VerificationError() != nil {
wantErr = tt.want[i].VerificationError().Error()
}
if gotErr != wantErr {
t.Fatalf("wantVerificationError = %v, verification error = %v", tt.want[i].VerificationError(), got[i].VerificationError())
}
}
ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "RawV2", "verificationError")
if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
t.Errorf("AdafruitIO.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
}
})
}
}
func BenchmarkFromData(benchmark *testing.B) {
ctx := context.Background()
s := Scanner{}
for name, data := range detectors.MustGetBenchmarkData() {
benchmark.Run(name, func(b *testing.B) {
b.ResetTimer()
for n := 0; n < b.N; n++ {
_, err := s.FromData(ctx, false, data)
if err != nil {
b.Fatal(err)
results, err := d.FromData(context.Background(), false, []byte(test.input))
require.NoError(t, err)
if len(results) != len(test.want) {
t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
return
}
actual := make(map[string]struct{}, len(results))
for _, r := range results {
if len(r.RawV2) > 0 {
actual[string(r.RawV2)] = struct{}{}
} else {
actual[string(r.Raw)] = struct{}{}
}
}
expected := make(map[string]struct{}, len(test.want))
for _, v := range test.want {
expected[v] = struct{}{}
}
if diff := cmp.Diff(expected, actual); diff != "" {
t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
}
})
}
}
+43 -25
View File
@@ -2,20 +2,15 @@ package adobeio
import (
"context"
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
)
var (
validPattern = "zxcv0987mnbv1234poiu6749gtnrfv54/WDcv0981Mn.B"
invalidPattern = "Rzxc#0987$%bv1234poiu6749gtnrfv54"
)
func TestAdobeIO_Pattern(t *testing.T) {
d := Scanner{}
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
@@ -26,19 +21,48 @@ func TestAdobeIO_Pattern(t *testing.T) {
want []string
}{
{
name: "valid pattern",
input: fmt.Sprintf("adobe = '%s'", validPattern),
want: []string{"zxcv0987mnbv1234poiu6749gtnrfv54WDcv0981Mn.B"},
name: "valid pattern",
input: `
[INFO] Sending request to the adobe API
[DEBUG] Using adobe KEY=zoaw0c0m50m0hz2h1fm21y4tqfyl7ifi
[DEBUG] Using adobe ID=qCRbiIy1NJaW
[INFO] Response received: 200 OK
`,
want: []string{"zoaw0c0m50m0hz2h1fm21y4tqfyl7ifiqCRbiIy1NJaW"},
},
{
name: "valid pattern - out of prefix range",
input: fmt.Sprintf("adobe keyword is not close to the real id and secret = '%s'", validPattern),
want: nil,
name: "valid pattern - xml",
input: `
<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
<scope>GLOBAL</scope>
<id>{adobe ftd7hkeafk0q}</id>
<secret>{adobe AQAAABAAA siybmtkgho9nsgjhng5yhp92wnir2a9t}</secret>
<description>configuration for production</description>
<creationDate>2023-05-18T14:32:10Z</creationDate>
<owner>jenkins-admin</owner>
</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
`,
want: []string{"siybmtkgho9nsgjhng5yhp92wnir2a9tftd7hkeafk0q"},
},
{
name: "invalid pattern",
input: fmt.Sprintf("adobe%s", invalidPattern),
want: nil,
name: "valid pattern - out of prefix range",
input: `
[INFO] Sending request to the adobe API
[DEBUG] Using KEY=zoaw0c0m50m0hz2h1fm21y4tqfyl7ifi
[DEBUG] Using ID=qCRbiIy1NJaW
[INFO] Response received: 200 OK
`,
want: nil,
},
{
name: "invalid pattern",
input: `
[INFO] Sending request to the adobe API
[DEBUG] Using adobe KEY=Rzxc#0987$%bv1234poiu6749gtnrfv54
[DEBUG] Using adobe ID=qCRbiIy1NJaW
[ERROR] Response received: 400 BadRequest
`,
want: nil,
},
}
@@ -46,22 +70,15 @@ func TestAdobeIO_Pattern(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
if len(matchedDetectors) == 0 {
t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
return
}
results, err := d.FromData(context.Background(), false, []byte(test.input))
if err != nil {
t.Errorf("error = %v", err)
return
}
require.NoError(t, err)
if len(results) != len(test.want) {
if len(results) == 0 {
t.Errorf("did not receive result")
} else {
t.Errorf("expected %d results, only received %d", len(test.want), len(results))
}
t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
return
}
@@ -73,6 +90,7 @@ func TestAdobeIO_Pattern(t *testing.T) {
actual[string(r.Raw)] = struct{}{}
}
}
expected := make(map[string]struct{}, len(test.want))
for _, v := range test.want {
expected[v] = struct{}{}
+57 -32
View File
@@ -2,21 +2,15 @@ package adzuna
import (
"context"
"fmt"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
)
var (
validPattern = "asdf0987mnbv1234qsxojb6ygb2wsx0o/qsc0f098"
invalidPattern = "as#f0987mnbv1234^sxojb6ygb2wsx0o/qsc0f098"
)
func TestAdzuna_Pattern(t *testing.T) {
d := Scanner{}
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
@@ -27,29 +21,66 @@ func TestAdzuna_Pattern(t *testing.T) {
want []string
}{
{
name: "valid pattern",
input: fmt.Sprintf("adzuna = '%s'", validPattern),
want: []string{"asdf0987mnbv1234qsxojb6ygb2wsx0oqsc0f098"},
name: "valid pattern",
input: `
[INFO] Sending request to the adzuna API
[DEBUG] Using adzuna KEY=smcud4y6elxx7u6q58ewwv8rq01hpi3f
[DEBUG] Using adzuna ID=cxu9w2g6
[INFO] Response received: 200 OK
`,
want: []string{"smcud4y6elxx7u6q58ewwv8rq01hpi3fcxu9w2g6"},
},
{
name: "valid pattern - out of prefix range",
input: fmt.Sprintf("adzuna keyword is not close to the real id and secret = '%s'", validPattern),
want: nil,
name: "valid pattern - xml",
input: `
<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
<scope>GLOBAL</scope>
<id>{adzuna svkit0wx}</id>
<secret>{adzuna AQAAABAAA atubvgvpd6jjo0ac1wjianofnpgr24ac}</secret>
<description>configuration for production</description>
<creationDate>2023-05-18T14:32:10Z</creationDate>
<owner>jenkins-admin</owner>
</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
`,
want: []string{"atubvgvpd6jjo0ac1wjianofnpgr24acsvkit0wx"},
},
{
name: "valid pattern - only key",
input: fmt.Sprintf("adzuna %s", strings.Split(validPattern, "/")[0]),
want: nil,
name: "valid pattern - out of prefix range",
input: `
[INFO] Sending request to the adzuna API
[DEBUG] Using KEY=smcud4y6elxx7u6q58ewwv8rq01hpi3f
[DEBUG] Using ID=cxu9w2g6
[INFO] Response received: 200 OK
`,
want: nil,
},
{
name: "valid pattern - only id",
input: fmt.Sprintf("adzuna %s", strings.Split(validPattern, "/")[1]),
want: nil,
name: "valid pattern - only key",
input: `
[INFO] Sending request to the adzuna API
[DEBUG] Using KEY=smcud4y6elxx7u6q58ewwv8rq01hpi3f
[INFO] Response received: 200 OK
`,
want: nil,
},
{
name: "invalid pattern",
input: fmt.Sprintf("adzuna %s", invalidPattern),
want: nil,
name: "valid pattern - only id",
input: `
[INFO] Sending request to the adzuna API
[DEBUG] Using ID=cxu9w2g6
[INFO] Response received: 200 OK
`,
want: nil,
},
{
name: "invalid pattern",
input: `
[INFO] Sending request to the adzuna API
[DEBUG] Using KEY=sxojb6ygb2wsx0o
[DEBUG] Using ID=cxu9w2g6
[ERROR] Response received: 400 BadRequest
`,
want: nil,
},
}
@@ -57,22 +88,15 @@ func TestAdzuna_Pattern(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
if len(matchedDetectors) == 0 {
t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
return
}
results, err := d.FromData(context.Background(), false, []byte(test.input))
if err != nil {
t.Errorf("error = %v", err)
return
}
require.NoError(t, err)
if len(results) != len(test.want) {
if len(results) == 0 {
t.Errorf("did not receive result")
} else {
t.Errorf("expected %d results, only received %d", len(test.want), len(results))
}
t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
return
}
@@ -84,6 +108,7 @@ func TestAdzuna_Pattern(t *testing.T) {
actual[string(r.Raw)] = struct{}{}
}
}
expected := make(map[string]struct{}, len(test.want))
for _, v := range test.want {
expected[v] = struct{}{}
+57 -32
View File
@@ -2,21 +2,15 @@ package aeroworkflow
import (
"context"
"fmt"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
)
var (
validPattern = "qscVgy!WdvG;^#O:*?TG/806445634"
invalidPattern = "qscVg&!WdvG;^#O:*?TG/8064456A4"
)
func TestAeroWorkflow_Pattern(t *testing.T) {
d := Scanner{}
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
@@ -27,29 +21,66 @@ func TestAeroWorkflow_Pattern(t *testing.T) {
want []string
}{
{
name: "valid pattern",
input: fmt.Sprintf("aeroworkflow = '%s'", validPattern),
want: []string{"qscVgy!WdvG;^#O:*?TG806445634"},
name: "valid pattern",
input: `
[INFO] Sending request to the aeroworkflow API
[DEBUG] Using aeroworkflow KEY=VmFYK7WG3CkgVmTl:c*X
[DEBUG] Using aeroworkflow ID=678436
[INFO] Response received: 200 OK
`,
want: []string{"VmFYK7WG3CkgVmTl:c*X678436"},
},
{
name: "valid pattern - out of prefix range",
input: fmt.Sprintf("aeroworkflow keyword is not close to the real id and secret = '%s'", validPattern),
want: nil,
name: "valid pattern - xml",
input: `
<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
<scope>GLOBAL</scope>
<id>{aeroworkflow 6}</id>
<secret>{aeroworkflow AQAAABAAA XjPSUOhREIN:4HX2#akH}</secret>
<description>configuration for production</description>
<creationDate>2023-05-18T14:32:10Z</creationDate>
<owner>jenkins-admin</owner>
</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
`,
want: []string{"XjPSUOhREIN:4HX2#akH6"},
},
{
name: "valid pattern - only key",
input: fmt.Sprintf("aeroworkflow %s", strings.Split(validPattern, "/")[0]),
want: nil,
name: "valid pattern - out of prefix range",
input: `
[INFO] Sending request to the aeroworkflow API
[DEBUG] Using KEY=VmFYK7WG3CkgVmTl:c*X
[DEBUG] Using ID=678436
[INFO] Response received: 200 OK
`,
want: nil,
},
{
name: "valid pattern - only id",
input: fmt.Sprintf("aeroworkflow %s", strings.Split(validPattern, "/")[1]),
want: nil,
name: "valid pattern - only key",
input: `
[INFO] Sending request to the aeroworkflow API
[DEBUG] Using KEY=VmFYK7WG3CkgVmTl:c*X
[INFO] Response received: 200 OK
`,
want: nil,
},
{
name: "invalid pattern",
input: fmt.Sprintf("aeroworkflow %s", invalidPattern),
want: nil,
name: "valid pattern - only id",
input: `
[INFO] Sending request to the aeroworkflow API
[DEBUG] Using ID=678436
[INFO] Response received: 200 OK
`,
want: nil,
},
{
name: "invalid pattern",
input: `
[INFO] Sending request to the aeroworkflow API
[DEBUG] Using KEY=VmFYK7WG3CkgVmTl:c*X
[DEBUG] Using ID=cxu9w2g6
[ERROR] Response received: 400 BadRequest
`,
want: nil,
},
}
@@ -57,22 +88,15 @@ func TestAeroWorkflow_Pattern(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
if len(matchedDetectors) == 0 {
t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
return
}
results, err := d.FromData(context.Background(), false, []byte(test.input))
if err != nil {
t.Errorf("error = %v", err)
return
}
require.NoError(t, err)
if len(results) != len(test.want) {
if len(results) == 0 {
t.Errorf("did not receive result")
} else {
t.Errorf("expected %d results, only received %d", len(test.want), len(results))
}
t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
return
}
@@ -84,6 +108,7 @@ func TestAeroWorkflow_Pattern(t *testing.T) {
actual[string(r.Raw)] = struct{}{}
}
}
expected := make(map[string]struct{}, len(test.want))
for _, v := range test.want {
expected[v] = struct{}{}
+55 -42
View File
@@ -2,28 +2,15 @@ package agora
import (
"context"
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
)
var (
validKeyPattern = "asdf0987mnbv1234qsxojb6ygb2wsx0o"
validSecretPattern = "beqr7215fr4g6bfjkmnvxrtygb2wsxap"
complexPattern = `agora credentials
these are some example credentials for login.
use these to login.
key: asdf0987mnbv1234qsxojb6ygb2wsx0o
secret: beqr7215fr4g6bfjkmnvxrtygb2wsxap
loginUrl: https://www.agora.com/example_login
`
invalidPattern = "asdf0987mNbv1234qsxojb6ygb2w$x0o/beqr7215fr4g6bfjkmnVxrtygb2wsxap"
)
func TestAgora_Pattern(t *testing.T) {
d := Scanner{}
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
@@ -34,34 +21,66 @@ func TestAgora_Pattern(t *testing.T) {
want []string
}{
{
name: "valid pattern",
input: fmt.Sprintf("agora key='%s' - secret='%s'", validKeyPattern, validSecretPattern),
want: []string{validKeyPattern + validSecretPattern},
name: "valid pattern",
input: `
[INFO] Sending request to the agora API
[DEBUG] Using Token=6p77f9gjhxx9mwdj86of7y7820bh49vw
[DEBUG] Using Secret=qi6txx6vd0qzn6j01xj9rr6clyejvjw5
[INFO] Response received: 200 OK
`,
want: []string{"6p77f9gjhxx9mwdj86of7y7820bh49vwqi6txx6vd0qzn6j01xj9rr6clyejvjw5"},
},
{
name: "valid complex pattern",
input: fmt.Sprintf("agora data='%s'", complexPattern),
want: []string{validKeyPattern + validSecretPattern},
name: "valid pattern - xml",
input: `
<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
<scope>GLOBAL</scope>
<id>{agora 3devtbiys8b282kidr9u78kjq8xdtlo1}</id>
<secret>{AQAAABAAA bc7c6tag5jfuhz4y7v6v05dx2wq2z1ua}</secret>
<description>configuration for production</description>
<creationDate>2023-05-18T14:32:10Z</creationDate>
<owner>jenkins-admin</owner>
</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
`,
want: []string{"3devtbiys8b282kidr9u78kjq8xdtlo1bc7c6tag5jfuhz4y7v6v05dx2wq2z1ua"},
},
{
name: "valid pattern - out of prefix range",
input: fmt.Sprintf("agora keyword is not close to the real key or secret = '%s|%s'", validKeyPattern, validSecretPattern),
want: nil,
name: "valid pattern - out of prefix range",
input: `
[INFO] Sending request to the agora API
[DEBUG] Using 6p77f9gjhxx9mwdj86of7y7820bh49vw
[DEBUG] Using qi6txx6vd0qzn6j01xj9rr6clyejvjw5
[INFO] Response received: 200 OK
`,
want: nil,
},
{
name: "valid pattern - only key",
input: fmt.Sprintf("agora key%s", validKeyPattern),
want: nil,
name: "valid pattern - only key",
input: `
[INFO] Sending request to the agora API
[DEBUG] Using Key=6p77f9gjhxx9mwdj86of7y7820bh49vw
[INFO] Response received: 200 OK
`,
want: nil,
},
{
name: "valid pattern - only secret",
input: fmt.Sprintf("agora secret%s", validSecretPattern),
want: nil,
name: "valid pattern - only secret",
input: `
[INFO] Sending request to the agora API
[DEBUG] Using Secret=qi6txx6vd0qzn6j01xj9rr6clyejvjw5
[INFO] Response received: 200 OK
`,
want: nil,
},
{
name: "invalid pattern",
input: fmt.Sprintf("agora %s", invalidPattern),
want: nil,
name: "invalid pattern",
input: `
[INFO] Sending request to the agora API
[DEBUG] Using KEY=qi6txx6vd0qzn6j01xj9rr6clyejvjw
[DEBUG] Using ID=qi6txx6vd0qzn6j01xj9rr6clyejvjw5yt
[ERROR] Response received: 400 BadRequest
`,
want: nil,
},
}
@@ -69,22 +88,15 @@ func TestAgora_Pattern(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
if len(matchedDetectors) == 0 {
t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
return
}
results, err := d.FromData(context.Background(), false, []byte(test.input))
if err != nil {
t.Errorf("error = %v", err)
return
}
require.NoError(t, err)
if len(results) != len(test.want) {
if len(results) == 0 {
t.Errorf("did not receive result")
} else {
t.Errorf("expected %d results, only received %d", len(test.want), len(results))
}
t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
return
}
@@ -96,6 +108,7 @@ func TestAgora_Pattern(t *testing.T) {
actual[string(r.Raw)] = struct{}{}
}
}
expected := make(map[string]struct{}, len(test.want))
for _, v := range test.want {
expected[v] = struct{}{}
+35 -26
View File
@@ -34,6 +34,14 @@ func (s Scanner) Keywords() []string {
return []string{"aha.io"}
}
func (s Scanner) Type() detectorspb.DetectorType {
return detectorspb.DetectorType_Aha
}
func (s Scanner) Description() string {
return "Aha is a product management software suite. Aha API keys can be used to access and modify product data and workflows."
}
func (s Scanner) getClient() *http.Client {
if s.client != nil {
return s.client
@@ -44,30 +52,39 @@ func (s Scanner) getClient() *http.Client {
// FromData will find and optionally verify Aha secrets in a given set of bytes.
func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
dataStr := string(data)
matches := keyPat.FindAllStringSubmatch(dataStr, -1)
URLmatches := URLPat.FindAllStringSubmatch(dataStr, -1)
resURLMatch := "aha.io"
for _, URLmatch := range URLmatches {
resURLMatch = strings.TrimSpace(URLmatch[1])
var uniqueFoundUrls = make(map[string]struct{})
matches := keyPat.FindAllStringSubmatch(dataStr, -1)
for _, match := range URLPat.FindAllStringSubmatch(dataStr, -1) {
uniqueFoundUrls[match[1]] = struct{}{}
}
// if no url was found use the default
if len(uniqueFoundUrls) == 0 {
uniqueFoundUrls["aha.io"] = struct{}{}
}
for _, match := range matches {
resMatch := strings.TrimSpace(match[1])
for url := range uniqueFoundUrls {
resMatch := strings.TrimSpace(match[1])
s1 := detectors.Result{
DetectorType: detectorspb.DetectorType_Aha,
Raw: []byte(resMatch),
s1 := detectors.Result{
DetectorType: detectorspb.DetectorType_Aha,
Raw: []byte(resMatch),
RawV2: []byte(resMatch + url),
}
if verify {
client := s.getClient()
isVerified, verificationErr := verifyAha(ctx, client, resMatch, url)
s1.Verified = isVerified
s1.SetVerificationError(verificationErr, resMatch)
}
results = append(results, s1)
}
if verify {
client := s.getClient()
isVerified, verificationErr := verifyAha(ctx, client, resMatch, resURLMatch)
s1.Verified = isVerified
s1.SetVerificationError(verificationErr, resMatch)
}
results = append(results, s1)
}
return results, nil
@@ -98,11 +115,3 @@ func verifyAha(ctx context.Context, client *http.Client, resMatch, resURLMatch s
return false, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
}
}
func (s Scanner) Type() detectorspb.DetectorType {
return detectorspb.DetectorType_Aha
}
func (s Scanner) Description() string {
return "Aha is a product management software suite. Aha API keys can be used to access and modify product data and workflows."
}
+55 -38
View File
@@ -2,62 +2,85 @@ package aha
import (
"context"
"fmt"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
)
var (
validPattern = "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff/example.aha.io"
invalidPattern = "00112233445566778899aabbCC$%eeff00112233445566778899aabbccddeeff/example.fake.io"
)
func TestAha_Pattern(t *testing.T) {
d := Scanner{}
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
key := strings.Split(validPattern, "/")[0]
url := strings.Split(validPattern, "/")[1]
tests := []struct {
name string
input string
want []string
}{
{
name: "valid pattern",
input: fmt.Sprintf("aha.io = '%s'", validPattern),
want: []string{key},
name: "valid pattern",
input: `
[INFO] sending request to the aha.io API
[DEBUG] using key = 81a1411a7e276fd88819df3137eb406e0f281f8a8c417947ca4b025890c8541c
[DEBUG] using host = example.aha.io
[INFO] response received: 200 OK
`,
want: []string{"81a1411a7e276fd88819df3137eb406e0f281f8a8c417947ca4b025890c8541cexample.aha.io"},
},
{
name: "valid pattern - detect URL far away from keyword",
input: fmt.Sprintf("aha.io = '%s\n URL is not close to the keyword but should be detected %s'", key, url),
want: []string{key},
name: "valid pattern - xml",
input: `
<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
<scope>GLOBAL</scope>
<id>{aha 3af0b286b668d9636fd68076d6c87a333fe285fd41593cfceab36b35606c915a}</id>
<secret>{AQAAABAAA ACTp3nufSEO791nIReS5udnRVFcG9j6-CqBJogBxo1pbql.aha.io}</secret>
<description>configuration for production</description>
<creationDate>2023-05-18T14:32:10Z</creationDate>
<owner>jenkins-admin</owner>
</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
`,
want: []string{"3af0b286b668d9636fd68076d6c87a333fe285fd41593cfceab36b35606c915aACTp3nufSEO791nIReS5udnRVFcG9j6-CqBJogBxo1pbql.aha.io"},
},
{
name: "valid pattern - key out of prefix range",
input: fmt.Sprintf("aha.io keyword is not close to the real key and secret = '%s'", validPattern),
want: nil,
name: "valid pattern - key out of prefix range",
input: `
[INFO] sending request to the aha.io API
[WARN] Do not commit the secrets
[DEBUG] using key = 81a1411a7e276fd88819df3137eb406e0f281f8a8c417947ca4b025890c8541c
[DEBUG] using host = example.aha.io
[INFO] response received: 200 OK
`,
want: nil,
},
{
name: "valid pattern - only key",
input: fmt.Sprintf("aha.io %s", key),
want: []string{key},
name: "valid pattern - only key",
input: `
[INFO] sending request to the aha.io API
[DEBUG] using key = 81a1411a7e276fd88819df3137eb406e0f281f8a8c417947ca4b025890c8541c
[INFO] response received: 200 OK
`,
want: []string{"81a1411a7e276fd88819df3137eb406e0f281f8a8c417947ca4b025890c8541caha.io"},
},
{
name: "valid pattern - only URL",
input: fmt.Sprintf("aha.io %s", url),
want: nil,
name: "valid pattern - only URL",
input: `
[INFO] sending request to the example.aha.io API
[INFO] response received: 200 OK
`,
want: nil,
},
{
name: "invalid pattern",
input: fmt.Sprintf("aha.io %s", invalidPattern),
want: nil,
name: "invalid pattern",
input: `
[INFO] sending request to the aha.io API
[DEBUG] using key = 81a1411a7e276fd88819df3137eJ406e0f281f8a8c417947ca4b025890c8541c
[DEBUG] using host = 1test.aha.io
[INFO] response received: 200 OK
`,
want: nil,
},
}
@@ -65,22 +88,15 @@ func TestAha_Pattern(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
if len(matchedDetectors) == 0 {
t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
return
}
results, err := d.FromData(context.Background(), false, []byte(test.input))
if err != nil {
t.Errorf("error = %v", err)
return
}
require.NoError(t, err)
if len(results) != len(test.want) {
if len(results) == 0 {
t.Errorf("did not receive result")
} else {
t.Errorf("expected %d results, only received %d", len(test.want), len(results))
}
t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
return
}
@@ -92,6 +108,7 @@ func TestAha_Pattern(t *testing.T) {
actual[string(r.Raw)] = struct{}{}
}
}
expected := make(map[string]struct{}, len(test.want))
for _, v := range test.want {
expected[v] = struct{}{}
@@ -2,21 +2,15 @@ package airbrakeprojectkey
import (
"context"
"fmt"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
)
var (
validPattern = "qwmnerBv56zxpocvkjqr78afvYUx90Op/451298"
invalidPattern = "qwmnerBv56zxpocvkjqr78afvYU$90Op/4512987"
)
func TestAirBrakeProjectKey_Pattern(t *testing.T) {
d := Scanner{}
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
@@ -27,29 +21,67 @@ func TestAirBrakeProjectKey_Pattern(t *testing.T) {
want []string
}{
{
name: "valid pattern",
input: fmt.Sprintf("airbrake = '%s'", validPattern),
want: []string{"qwmnerBv56zxpocvkjqr78afvYUx90Op451298"},
name: "valid pattern",
input: `
[INFO] Sending request to the airbrake API
[DEBUG] Using airbrake Key=7B759RwRR5Txo9pDxXtPNcrTOj0zhvmR
[DEBUG] Using airbrake ID=856019
[INFO] Response received: 200 OK
`,
want: []string{"7B759RwRR5Txo9pDxXtPNcrTOj0zhvmR856019"},
},
{
name: "valid pattern - key out of prefix range",
input: fmt.Sprintf("airbrake keyword is not close to the real key and secret = '%s'", validPattern),
want: nil,
name: "valid pattern - xml",
input: `
<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
<scope>GLOBAL</scope>
<id>{airbrake 691149}</id>
<secret>{airbrake AQAAABAAA hYNK8PlcGXZ6PXXDFJI89LCjpoM8koTx}</secret>
<description>configuration for production</description>
<creationDate>2023-05-18T14:32:10Z</creationDate>
<owner>jenkins-admin</owner>
</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
`,
want: []string{"hYNK8PlcGXZ6PXXDFJI89LCjpoM8koTx691149"},
},
{
name: "valid pattern - only key",
input: fmt.Sprintf("airbrake %s", strings.Split(validPattern, "/")[0]),
want: nil,
name: "valid pattern - key out of prefix range",
input: `
[INFO] airbrake API request handling
[INFO] Sending request to the API
[DEBUG] Using Key=7B759RwRR5Txo9pDxXtPNcrTOj0zhvmR
[DEBUG] Using ID=856019
[INFO] Response received: 200 OK
`,
want: nil,
},
{
name: "valid pattern - only ID",
input: fmt.Sprintf("airbrake %s", strings.Split(validPattern, "/")[0]),
want: nil,
name: "valid pattern - only key",
input: `
[INFO] Sending request to the airbrake API
[DEBUG] Using airbrake Key=7B759RwRR5Txo9pDxXtPNcrTOj0zhvmR
[INFO] Response received: 200 OK
`,
want: nil,
},
{
name: "invalid pattern",
input: fmt.Sprintf("airbrake = '%s'", invalidPattern),
want: nil,
name: "valid pattern - only ID",
input: `
[INFO] Sending request to the airbrake API
[DEBUG] Using airbrake ID=856019
[INFO] Response received: 200 OK
`,
want: nil,
},
{
name: "invalid pattern",
input: `
[INFO] Sending request to the airbrake API
[DEBUG] Using airbrake Key=qwmnerBv56zx**cvkjqr78afvYU$90Op
[DEBUG] Using airbrake ID=856019
[ERROR] Response received: 401 UnAuthorized
`,
want: nil,
},
}
@@ -57,22 +89,15 @@ func TestAirBrakeProjectKey_Pattern(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
if len(matchedDetectors) == 0 {
t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
return
}
results, err := d.FromData(context.Background(), false, []byte(test.input))
if err != nil {
t.Errorf("error = %v", err)
return
}
require.NoError(t, err)
if len(results) != len(test.want) {
if len(results) == 0 {
t.Errorf("did not receive result")
} else {
t.Errorf("expected %d results, only received %d", len(test.want), len(results))
}
t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
return
}
@@ -84,6 +109,7 @@ func TestAirBrakeProjectKey_Pattern(t *testing.T) {
actual[string(r.Raw)] = struct{}{}
}
}
expected := make(map[string]struct{}, len(test.want))
for _, v := range test.want {
expected[v] = struct{}{}
@@ -2,20 +2,15 @@ package airbrakeuserkey
import (
"context"
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
)
var (
validPattern = "qsCGuilpkk2ngrsz75wtYqsCGuilpkk2ngrsz75w"
invalidPattern = "Qs%CGuil#pkk2ngrsz75wtYqsCGuilpkk2ngrsz75w"
)
func TestAirBrakeUserKey_Pattern(t *testing.T) {
d := Scanner{}
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
@@ -26,19 +21,46 @@ func TestAirBrakeUserKey_Pattern(t *testing.T) {
want []string
}{
{
name: "valid pattern",
input: fmt.Sprintf("airbrake = '%s'", validPattern),
want: []string{validPattern},
name: "valid pattern",
input: `
[INFO] Sending request to the airbrake API
[DEBUG] Using Key=qsCGuilpkk2ngrsz75wtYqsCGuilpkk2ngrsz75w
[INFO] Response received: 200 OK
`,
want: []string{"qsCGuilpkk2ngrsz75wtYqsCGuilpkk2ngrsz75w"},
},
{
name: "valid pattern - key out of prefix range",
input: fmt.Sprintf("airbrake keyword is not close to the real key and secret = '%s'", validPattern),
want: nil,
name: "valid pattern - xml",
input: `
<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
<scope>GLOBAL</scope>
<id>{airbrake 691149}</id>
<secret>{airbrake AQAAABAAA UTDwMhGhuk0T04V0yqTqcKIwSSp7syUyQRG8JwoF}</secret>
<description>configuration for production</description>
<creationDate>2023-05-18T14:32:10Z</creationDate>
<owner>jenkins-admin</owner>
</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
`,
want: []string{"UTDwMhGhuk0T04V0yqTqcKIwSSp7syUyQRG8JwoF"},
},
{
name: "invalid pattern",
input: fmt.Sprintf("airbrake = '%s'", invalidPattern),
want: nil,
name: "valid pattern - key out of prefix range",
input: `
[DEBUG] airbrake api processing
[INFO] Sending request to the API
[DEBUG] Using Key=qsCGuilpkk2ngrsz75wtYqsCGuilpkk2ngrsz75w
[INFO] Response received: 200 OK
`,
want: nil,
},
{
name: "invalid pattern",
input: `
[INFO] Sending request to the airbrake API
[DEBUG] Using airbrake Key=Qs%CGuil#pkk2ngrsz75wtYqsCGuilpkk2ngrsz75w
[INFO] Response received: 200 OK
`,
want: nil,
},
}
@@ -46,22 +68,15 @@ func TestAirBrakeUserKey_Pattern(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
if len(matchedDetectors) == 0 {
t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
return
}
results, err := d.FromData(context.Background(), false, []byte(test.input))
if err != nil {
t.Errorf("error = %v", err)
return
}
require.NoError(t, err)
if len(results) != len(test.want) {
if len(results) == 0 {
t.Errorf("did not receive result")
} else {
t.Errorf("expected %d results, only received %d", len(test.want), len(results))
}
t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
return
}
@@ -73,6 +88,7 @@ func TestAirBrakeUserKey_Pattern(t *testing.T) {
actual[string(r.Raw)] = struct{}{}
}
}
expected := make(map[string]struct{}, len(test.want))
for _, v := range test.want {
expected[v] = struct{}{}
+41 -25
View File
@@ -2,20 +2,15 @@ package airship
import (
"context"
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
)
var (
validPattern = "123abcdef456ghijkl789mnopqr012stuvwx3455123abcdef456ghijkl789mnopqr012stuvwx3455trdfrq95acr"
invalidPattern = "123#abcdef456gh$ijkl789mnopqr012stuvwX3455123abcdef456ghijkl789mnopqr012stuvwx3455trdfrq"
)
func TestAirship_Pattern(t *testing.T) {
d := Scanner{}
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
@@ -26,19 +21,46 @@ func TestAirship_Pattern(t *testing.T) {
want []string
}{
{
name: "valid pattern",
input: fmt.Sprintf("airship = '%s'", validPattern),
want: []string{validPattern},
name: "valid pattern",
input: `
[INFO] Sending request to the airship API
[DEBUG] Using Key=O3BV99CUDw3xYUAL0tHGYUe7mOj5PA5vTnLdJwULCTh9dxk9PmmTpL1kI846G3QGIsECVyVSsxZnIbfSwWc8xuX843W
[INFO] Response received: 200 OK
`,
want: []string{"O3BV99CUDw3xYUAL0tHGYUe7mOj5PA5vTnLdJwULCTh9dxk9PmmTpL1kI846G3QGIsECVyVSsxZnIbfSwWc8xuX843W"},
},
{
name: "valid pattern - key out of prefix range",
input: fmt.Sprintf("airship keyword is not close to the real key and secret = '%s'", validPattern),
want: nil,
name: "valid pattern - xml",
input: `
<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
<scope>GLOBAL</scope>
<id>{airship}</id>
<secret>{airship AQAAABAAA oVH3yIO1oAoXpK9Rc01EGNNTuw6d4Zyt07YNFmje644Ht00hvAaYwldNOV9vIPQw6dYHJLRgp2f75YdJ9OiICkYVhMI}</secret>
<description>configuration for production</description>
<creationDate>2023-05-18T14:32:10Z</creationDate>
<owner>jenkins-admin</owner>
</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
`,
want: []string{"oVH3yIO1oAoXpK9Rc01EGNNTuw6d4Zyt07YNFmje644Ht00hvAaYwldNOV9vIPQw6dYHJLRgp2f75YdJ9OiICkYVhMI"},
},
{
name: "invalid pattern",
input: fmt.Sprintf("airship = '%s'", invalidPattern),
want: nil,
name: "valid pattern - key out of prefix range",
input: `
[DEBUG] airship api processing
[INFO] Sending request to the API
[DEBUG] Using Key=O3BV99CUDw3xYUAL0tHGYUe7mOj5PA5vTnLdJwULCTh9dxk9PmmTpL1kI846G3QGIsECVyVSsxZnIbfSwWc8xuX843W
[INFO] Response received: 200 OK
`,
want: nil,
},
{
name: "invalid pattern",
input: `
[INFO] Sending request to the airship API
[DEBUG] Using Key=O3BV99CUDw3xY#AL0tHGYUe7mOj5PA5vTnLdJwULCTh9dxk9PmmTpL1kI846G3QGIsECVyVSsxZnIbfSwWc8xuX843W
[ERROR] Response received: 401 UnAuthorized
`,
want: nil,
},
}
@@ -46,22 +68,15 @@ func TestAirship_Pattern(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
if len(matchedDetectors) == 0 {
t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
return
}
results, err := d.FromData(context.Background(), false, []byte(test.input))
if err != nil {
t.Errorf("error = %v", err)
return
}
require.NoError(t, err)
if len(results) != len(test.want) {
if len(results) == 0 {
t.Errorf("did not receive result")
} else {
t.Errorf("expected %d results, only received %d", len(test.want), len(results))
}
t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
return
}
@@ -73,6 +88,7 @@ func TestAirship_Pattern(t *testing.T) {
actual[string(r.Raw)] = struct{}{}
}
}
expected := make(map[string]struct{}, len(test.want))
for _, v := range test.want {
expected[v] = struct{}{}
@@ -1,160 +0,0 @@
package airtableapikey
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
regexp "github.com/wasilibs/go-re2"
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
)
type Scanner struct {
client *http.Client
detectors.DefaultMultiPartCredentialProvider
}
// Ensure the Scanner satisfies the interface at compile time.
var _ detectors.Detector = (*Scanner)(nil)
var (
defaultClient = common.SaneHttpClient()
appPat = regexp.MustCompile(`(app[\w-]{14})`) // could be part of url
keyPat = regexp.MustCompile(`\b(key[a-zA-Z0-9_-]{14})\b`)
personalPat = regexp.MustCompile(`(\bpat[[:alnum:]]{14}\.[[:alnum:]]{64}\b)`)
)
// Keywords are used for efficiently pre-filtering chunks.
// Use identifiers in the secret preferably, or the provider name.
func (s Scanner) Keywords() []string {
return []string{"airtable"}
}
type response struct {
Error struct {
Type string `json:"type"`
Message string `json:"message"`
} `json:"error"`
}
// FromData will find and optionally verify AirtableApiKey secrets in a given set of bytes.
func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
dataStr := string(data)
appMatches := make(map[string]struct{})
for _, matches := range appPat.FindAllStringSubmatch(dataStr, -1) {
appMatches[matches[1]] = struct{}{}
}
keyMatches := make(map[string]struct{})
for _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {
keyMatches[matches[1]] = struct{}{}
}
for _, matches := range personalPat.FindAllStringSubmatch(dataStr, -1) {
keyMatches[matches[1]] = struct{}{}
}
for keyMatch := range keyMatches {
var (
r *detectors.Result
appMatch string
)
for a := range appMatches {
appMatch = a
if verify {
client := s.client
if client == nil {
client = defaultClient
}
isVerified, verificationErr := verifyMatch(ctx, client, appMatch, keyMatch)
if isVerified {
r = createResult(appMatch, keyMatch, isVerified, verificationErr)
break
}
}
}
if r == nil {
if len(appMatches) != 1 {
appMatch = ""
}
r = createResult(appMatch, keyMatch, false, nil)
}
results = append(results, *r)
}
return results, nil
}
func createResult(app string, key string, verified bool, err error) *detectors.Result {
r := &detectors.Result{
DetectorType: detectorspb.DetectorType_AirtableApiKey,
Raw: []byte(key),
Redacted: app,
Verified: verified,
}
if app != "" {
r.RawV2 = []byte(fmt.Sprintf(`%s:%s`, app, key))
}
if err != nil {
r.SetVerificationError(err, key)
}
return r
}
func verifyMatch(ctx context.Context, client *http.Client, app string, key string) (bool, error) {
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.airtable.com/v0/"+app+"/Projects", nil)
if err != nil {
return false, err
}
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", key))
res, err := client.Do(req)
if err != nil {
return false, err
}
defer func() {
_, _ = io.Copy(io.Discard, res.Body)
_ = res.Body.Close()
}()
switch res.StatusCode {
case http.StatusOK:
return true, nil
case http.StatusUnauthorized:
return false, nil
case http.StatusForbidden:
var resp response
if err = json.NewDecoder(res.Body).Decode(&resp); err != nil {
return false, err
}
// check if the error is due to invalid permissions or model not found
if resp.Error.Type == "INVALID_PERMISSIONS_OR_MODEL_NOT_FOUND" {
// The key is verified as it works, but the user must enumerate the tables or permissions for the key.
return true, nil
}
return false, nil
default:
return false, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
}
}
func (s Scanner) Type() detectorspb.DetectorType {
return detectorspb.DetectorType_AirtableApiKey
}
func (s Scanner) Description() string {
return "Airtable is a cloud collaboration service that offers database-like features. Airtable API keys can be used to access and modify data within Airtable bases."
}
+1 -1
View File
@@ -75,7 +75,7 @@ func verifyMatch(ctx context.Context, client *http.Client, token string) (bool,
endpoint := "https://api.airtable.com/v0/meta/whoami"
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
if err != nil {
return false, nil, nil
return false, nil, err
}
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
@@ -2,21 +2,15 @@ package airtableoauth
import (
"context"
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
)
var (
validPattern1 = "oaajtCy2lVMUN1Cm5.v1.eyJ1c2VySWQiOiJ1c3JjQ09QVlJudGlrU1lzdyIsImV4cGlyZXNBdCI6IjIwMjUtMDItMDNUMTk6NTY6MzcuMDAwWiIsIm9hdXRoQXBwbGljYXRpb25JZCI6Im9hcG14aXcyUlRrVGlzcHJIIiwic2VjcmV0IjoiMzczNThlNzdlZjlhMjljY2Q5MWIwNmNlNTdkZDYxNDg0MWVmNmIyOWYwYjQ5ZWE0MTMxZGI4NzBkNTAzYTE1NyJ9.0d67c8b334048135a93615610445e4aa90c6af6222392b49eea9419e1d6717d0"
validPattern2 = "oaaRYiYSlTFXZzxDM.v1.eyJ1c2VySWQiOiJ1c3JjQ09QVlJudGlrU1lzdyIsIm9hdXRoQXBwbGljYXRpb25JZCI6Im9hcG14aXcyUlRrVGlzcHJIIiwiZXhwaXJlc0F0IjoiMjAyNS0wMS0yOVQwMDowMTo0NC4wMDBaIiwic2VjcmV0IjoiZjYyOWE1MWVkM2M0ZjU5ODlmOTcyMDU1ZjkwODk3NDA4NmU0NjQxY2JhODU5Y2FhZTJkZjliMWQwODg0ZjIzMiJ9.27a8998029ac9bdd599b435572821dcb63c60cbf62b9cb2ba2a73511e5553d66"
invalidPattern = "oaaRYiYSlTFXZzxDM.v2.eyJ1c2VySWQiOiJ1c3JjQ09QVlJudGlrU1lzdyIsIm9hdXRoQXBwbGljYXRpb25JZCI6Im9hcG14aXcyUlRrVGlzcHJIIiwiZXhwaXJlc0F0IjoiMjAyNS0wMS0yOVQwMDowMTo0NC4wMDBaIiwic2VjcmV0IjoiZjYyOWE1MWVkM2M0ZjU5ODlmOTcyMDU1ZjkwODk3NDA4NmU0NjQxY2JhODU5Y2FhZTJkZjliMWQwODg0ZjIzMiJ9.27a8998029ac9bdd599b435572821dcb63c60cbf62b9cb2ba2a73511e5553d66"
)
func TestAirtableoauth_Pattern(t *testing.T) {
d := Scanner{}
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
@@ -26,20 +20,50 @@ func TestAirtableoauth_Pattern(t *testing.T) {
want []string
}{
{
name: "typical pattern",
input: fmt.Sprintf("airtable token = '%s'", validPattern1),
want: []string{validPattern1},
name: "valid pattern",
input: `
[INFO] Sending request to the airtable API
[DEBUG] Using Key=oaajtCy2lVMUN1Cm5.v1.eyJ1c2VySWQiOiJ1c3JjQ09QVlJudGlrU1lzdyIsImV4cGlyZXNBdCI6IjIwMjUtMDItMDNUMTk6NTY6MzcuMDAwWiIsIm9hdXRoQXBwbGljYXRpb25JZCI6Im9hcG14aXcyUlRrVGlzcHJIIiwic2VjcmV0IjoiMzczNThlNzdlZjlhMjljY2Q5MWIwNmNlNTdkZDYxNDg0MWVmNmIyOWYwYjQ5ZWE0MTMxZGI4NzBkNTAzYTE1NyJ9.0d67c8b334048135a93615610445e4aa90c6af6222392b49eea9419e1d6717d0
[INFO] Response received: 200 OK
`,
want: []string{"oaajtCy2lVMUN1Cm5.v1.eyJ1c2VySWQiOiJ1c3JjQ09QVlJudGlrU1lzdyIsImV4cGlyZXNBdCI6IjIwMjUtMDItMDNUMTk6NTY6MzcuMDAwWiIsIm9hdXRoQXBwbGljYXRpb25JZCI6Im9hcG14aXcyUlRrVGlzcHJIIiwic2VjcmV0IjoiMzczNThlNzdlZjlhMjljY2Q5MWIwNmNlNTdkZDYxNDg0MWVmNmIyOWYwYjQ5ZWE0MTMxZGI4NzBkNTAzYTE1NyJ9.0d67c8b334048135a93615610445e4aa90c6af6222392b49eea9419e1d6717d0"},
},
{
name: "valid pattern - xml",
input: `
<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
<scope>GLOBAL</scope>
<id>{airtable}</id>
<secret>{airtable AQAAABAAA iKMJv6D1mmUvunFTZLfm4RrYhdrt5JCBMv.v1.r8IBnGw7b_vW0fl0MDJqPRUEsDdHtNYW9ANwPFm40V_M4knoEaulKL-5lmtWoRq9fjG-GORe8efob5e658nTiOkdYC.8a8d3}</secret>
<description>configuration for production</description>
<creationDate>2023-05-18T14:32:10Z</creationDate>
<owner>jenkins-admin</owner>
</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
`,
want: []string{"iKMJv6D1mmUvunFTZLfm4RrYhdrt5JCBMv.v1.r8IBnGw7b_vW0fl0MDJqPRUEsDdHtNYW9ANwPFm40V_M4knoEaulKL-5lmtWoRq9fjG-GORe8efob5e658nTiOkdYC.8a8d3"},
},
{
name: "finds all matches",
input: fmt.Sprintf(`airtable token 1 = '%s'
airtabl token 2 = '%s'`, validPattern1, validPattern2),
want: []string{validPattern1, validPattern2},
input: `
[INFO] Sending request to the airtable API
[DEBUG] Using Key=oaajtCy2lVMUN1Cm5.v1.eyJ1c2VySWQiOiJ1c3JjQ09QVlJudGlrU1lzdyIsImV4cGlyZXNBdCI6IjIwMjUtMDItMDNUMTk6NTY6MzcuMDAwWiIsIm9hdXRoQXBwbGljYXRpb25JZCI6Im9hcG14aXcyUlRrVGlzcHJIIiwic2VjcmV0IjoiMzczNThlNzdlZjlhMjljY2Q5MWIwNmNlNTdkZDYxNDg0MWVmNmIyOWYwYjQ5ZWE0MTMxZGI4NzBkNTAzYTE1NyJ9.0d67c8b334048135a93615610445e4aa90c6af6222392b49eea9419e1d6717d0
[ERROR] Response received: 401 UnAuthorized
[DEBUG] Using Key=oaaRYiYSlTFXZzxDM.v1.eyJ1c2VySWQiOiJ1c3JjQ09QVlJudGlrU1lzdyIsIm9hdXRoQXBwbGljYXRpb25JZCI6Im9hcG14aXcyUlRrVGlzcHJIIiwiZXhwaXJlc0F0IjoiMjAyNS0wMS0yOVQwMDowMTo0NC4wMDBaIiwic2VjcmV0IjoiZjYyOWE1MWVkM2M0ZjU5ODlmOTcyMDU1ZjkwODk3NDA4NmU0NjQxY2JhODU5Y2FhZTJkZjliMWQwODg0ZjIzMiJ9.27a8998029ac9bdd599b435572821dcb63c60cbf62b9cb2ba2a73511e5553d66
[INFO] Response received: 200 OK
`,
want: []string{
"oaajtCy2lVMUN1Cm5.v1.eyJ1c2VySWQiOiJ1c3JjQ09QVlJudGlrU1lzdyIsImV4cGlyZXNBdCI6IjIwMjUtMDItMDNUMTk6NTY6MzcuMDAwWiIsIm9hdXRoQXBwbGljYXRpb25JZCI6Im9hcG14aXcyUlRrVGlzcHJIIiwic2VjcmV0IjoiMzczNThlNzdlZjlhMjljY2Q5MWIwNmNlNTdkZDYxNDg0MWVmNmIyOWYwYjQ5ZWE0MTMxZGI4NzBkNTAzYTE1NyJ9.0d67c8b334048135a93615610445e4aa90c6af6222392b49eea9419e1d6717d0",
"oaaRYiYSlTFXZzxDM.v1.eyJ1c2VySWQiOiJ1c3JjQ09QVlJudGlrU1lzdyIsIm9hdXRoQXBwbGljYXRpb25JZCI6Im9hcG14aXcyUlRrVGlzcHJIIiwiZXhwaXJlc0F0IjoiMjAyNS0wMS0yOVQwMDowMTo0NC4wMDBaIiwic2VjcmV0IjoiZjYyOWE1MWVkM2M0ZjU5ODlmOTcyMDU1ZjkwODk3NDA4NmU0NjQxY2JhODU5Y2FhZTJkZjliMWQwODg0ZjIzMiJ9.27a8998029ac9bdd599b435572821dcb63c60cbf62b9cb2ba2a73511e5553d66",
},
},
{
name: "invalid pattern",
input: fmt.Sprintf("airtable token = '%s'", invalidPattern),
want: []string{},
name: "invalid pattern",
input: `
[INFO] Sending request to the airtable API
[DEBUG] Using Key=oaaRYiYSlTFXZzxDM.v2.eyJ1c2VySWQiOiJ1c3JjQ09QVlJudGlrU1lzdyIsIm9hdXRoQXBwbGljYXRpb25JZCI6Im9hcG14aXcyUlRrVGlzcHJIIiwiZXhwaXJlc0F0IjoiMjAyNS0wMS0yOVQwMDowMTo0NC4wMDBaIiwic2VjcmV0IjoiZjYyOWE1MWVkM2M0ZjU5ODlmOTcyMDU1ZjkwODk3NDA4NmU0NjQxY2JhODU5Y2FhZTJkZjliMWQwODg0ZjIzMiJ9.27a8998029ac9bdd599b435572821dcb63c60cbf62b9cb2ba2a73511e5553d66
[ERROR] Response received: 401 UnAuthorized
`,
want: []string{},
},
}
@@ -47,22 +71,15 @@ func TestAirtableoauth_Pattern(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
if len(matchedDetectors) == 0 {
t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
return
}
results, err := d.FromData(context.Background(), false, []byte(test.input))
if err != nil {
t.Errorf("error = %v", err)
return
}
require.NoError(t, err)
if len(results) != len(test.want) {
if len(results) == 0 {
t.Errorf("did not receive result")
} else {
t.Errorf("expected %d results, only received %d", len(test.want), len(results))
}
t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
return
}
@@ -74,6 +91,7 @@ func TestAirtableoauth_Pattern(t *testing.T) {
actual[string(r.Raw)] = struct{}{}
}
}
expected := make(map[string]struct{}, len(test.want))
for _, v := range test.want {
expected[v] = struct{}{}
@@ -70,7 +70,7 @@ func verifyMatch(ctx context.Context, client *http.Client, token string) (bool,
endpoint := "https://api.airtable.com/v0/meta/whoami"
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
if err != nil {
return false, nil, nil
return false, nil, err
}
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
@@ -2,21 +2,15 @@ package airtablepersonalaccesstoken
import (
"context"
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
)
var (
validPattern1 = "patfqpIZBPU6EAt5x.458546d9c77b21f8a98141f2a4039d5626010f19efc16c20d57c4f41d44c8c85"
validPattern2 = "pat0VXr5I2HcapZE8.da2606afb7d97e936719ec952a4a18b44045e385d4ddf4f38dcc246fb63f0165"
invalidPattern = "tokfqpIZBPU6EAt5x.458546d9c77b21f8a98141f2a403-d5626010f19efc16c20d57c4f41d44c8c85"
)
func TestAirtablepersonalaccesstoken_Pattern(t *testing.T) {
d := Scanner{}
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
@@ -26,20 +20,50 @@ func TestAirtablepersonalaccesstoken_Pattern(t *testing.T) {
want []string
}{
{
name: "typical pattern",
input: fmt.Sprintf("airtable token = '%s'", validPattern1),
want: []string{validPattern1},
name: "valid pattern",
input: `
[INFO] Sending request to the airtable API
[DEBUG] Using Key=patfqpIZBPU6EAt5x.458546d9c77b21f8a98141f2a4039d5626010f19efc16c20d57c4f41d44c8c85
[INFO] Response received: 200 OK
`,
want: []string{"patfqpIZBPU6EAt5x.458546d9c77b21f8a98141f2a4039d5626010f19efc16c20d57c4f41d44c8c85"},
},
{
name: "valid pattern - xml",
input: `
<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
<scope>GLOBAL</scope>
<id>{airtable}</id>
<secret>{airtable AQAAABAAA pat2kATFGrujqJTbT.e2082656c470902d83b47dc804e693df1deb30161affbda39d879a2cf44bef13}</secret>
<description>configuration for production</description>
<creationDate>2023-05-18T14:32:10Z</creationDate>
<owner>jenkins-admin</owner>
</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
`,
want: []string{"pat2kATFGrujqJTbT.e2082656c470902d83b47dc804e693df1deb30161affbda39d879a2cf44bef13"},
},
{
name: "finds all matches",
input: fmt.Sprintf(`airtable token 1 = '%s'
airtable token 2 = '%s'`, validPattern1, validPattern2),
want: []string{validPattern1, validPattern2},
input: `
[INFO] Sending request to the API
[DEBUG] Using airtable Key=patfqpIZBPU6EAt5x.458546d9c77b21f8a98141f2a4039d5626010f19efc16c20d57c4f41d44c8c85
[ERROR] Response received: 401 UnAuthorized
[DEBUG] Using airtable Key=pat0VXr5I2HcapZE8.da2606afb7d97e936719ec952a4a18b44045e385d4ddf4f38dcc246fb63f0165
[INFO] Response received: 200 OK
`,
want: []string{
"patfqpIZBPU6EAt5x.458546d9c77b21f8a98141f2a4039d5626010f19efc16c20d57c4f41d44c8c85",
"pat0VXr5I2HcapZE8.da2606afb7d97e936719ec952a4a18b44045e385d4ddf4f38dcc246fb63f0165",
},
},
{
name: "invalid pattern",
input: fmt.Sprintf("airtable token = '%s'", invalidPattern),
want: []string{},
name: "invalid pattern",
input: `
[INFO] Sending request to the airtable API
[DEBUG] Using Key=patfqpIZBPU6EAt5xe.458546d9c77b21f8a98141f2a403-d5626010f19efc16c20d57c4f41d44c8c85
[ERROR] Response received: 401 UnAuthorized
`,
want: []string{},
},
}
@@ -47,22 +71,15 @@ func TestAirtablepersonalaccesstoken_Pattern(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
if len(matchedDetectors) == 0 {
t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
return
}
results, err := d.FromData(context.Background(), false, []byte(test.input))
if err != nil {
t.Errorf("error = %v", err)
return
}
require.NoError(t, err)
if len(results) != len(test.want) {
if len(results) == 0 {
t.Errorf("did not receive result")
} else {
t.Errorf("expected %d results, only received %d", len(test.want), len(results))
}
t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
return
}
@@ -74,6 +91,7 @@ func TestAirtablepersonalaccesstoken_Pattern(t *testing.T) {
actual[string(r.Raw)] = struct{}{}
}
}
expected := make(map[string]struct{}, len(test.want))
for _, v := range test.want {
expected[v] = struct{}{}
+41 -25
View File
@@ -2,20 +2,15 @@ package airvisual
import (
"context"
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
)
var (
validPattern = "qscgyygcsq-wdvvok7slklklaasnd8afafxd"
invalidPattern = "wdvvok7slklklaasnd8afafxd"
)
func TestAirVisual_Pattern(t *testing.T) {
d := Scanner{}
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
@@ -26,19 +21,46 @@ func TestAirVisual_Pattern(t *testing.T) {
want []string
}{
{
name: "valid pattern",
input: fmt.Sprintf("airvisual = '%s'", validPattern),
want: []string{validPattern},
name: "valid pattern",
input: `
[INFO] Sending request to the airvisual API
[DEBUG] Using Key=qscgyygcsq-wdvvok7slklklaasnd8afafxd
[INFO] Response received: 200 OK
`,
want: []string{"qscgyygcsq-wdvvok7slklklaasnd8afafxd"},
},
{
name: "valid pattern - key out of prefix range",
input: fmt.Sprintf("airvisual keyword is not close to the real key and secret = '%s'", validPattern),
want: nil,
name: "valid pattern - xml",
input: `
<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
<scope>GLOBAL</scope>
<id>{airvisual}</id>
<secret>{airvisual AQAAABAAA rtcbsxiee3d5au8ik14g-8iqrsu8thl1pku8}</secret>
<description>configuration for production</description>
<creationDate>2023-05-18T14:32:10Z</creationDate>
<owner>jenkins-admin</owner>
</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
`,
want: []string{"rtcbsxiee3d5au8ik14g-8iqrsu8thl1pku8"},
},
{
name: "invalid pattern",
input: fmt.Sprintf("airvisual key: '%s'", invalidPattern),
want: nil,
name: "valid pattern - key out of prefix range",
input: `
[DEBUG] airvisual api processing
[INFO] Sending request to the API
[DEBUG] Using Key=qscgyygcsq-wdvvok7slklklaasnd8afafxd
[INFO] Response received: 200 OK
`,
want: nil,
},
{
name: "invalid pattern",
input: `
[INFO] Sending request to the airvisual API
[DEBUG] Using Key=wdvvok7slklklaasnd8afafxd
[ERROR] Response received: 401 UnAuthorized
`,
want: nil,
},
}
@@ -46,22 +68,15 @@ func TestAirVisual_Pattern(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
if len(matchedDetectors) == 0 {
t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
return
}
results, err := d.FromData(context.Background(), false, []byte(test.input))
if err != nil {
t.Errorf("error = %v", err)
return
}
require.NoError(t, err)
if len(results) != len(test.want) {
if len(results) == 0 {
t.Errorf("did not receive result")
} else {
t.Errorf("expected %d results, only received %d", len(test.want), len(results))
}
t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
return
}
@@ -73,6 +88,7 @@ func TestAirVisual_Pattern(t *testing.T) {
actual[string(r.Raw)] = struct{}{}
}
}
expected := make(map[string]struct{}, len(test.want))
for _, v := range test.want {
expected[v] = struct{}{}
+41 -25
View File
@@ -2,20 +2,15 @@ package aiven
import (
"context"
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
)
var (
validPattern = `ABCdef123/+==ABCdef123/+==ABCdef123/+==ABCdef123/+==ABCdef123/+==ABCdef123/+==ABCdef123/+==ABCdef123/+==ABCdef123/+==ABCdef123/+==ABCdef123/+==ABCdef123/+==ABCdef123/+==ABCdef123/+==ABCdef123/+==ABCdef123/+==ABCdef123/+==ABCdef123/+==ABCdef123/+==ABCdef123/+==ABCdef123/+==ABCdef123/+==ABCdef123/+==ABCdef123/+==ABCdef123/+==ABCdef123/+==ABCdef123/+==ABCdef123/+==ABCdef12`
invalidPattern = `ABCdef123`
)
func TestAiven_Pattern(t *testing.T) {
d := Scanner{}
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
@@ -26,19 +21,46 @@ func TestAiven_Pattern(t *testing.T) {
want []string
}{
{
name: "valid pattern",
input: fmt.Sprintf("aiven = '%s'", validPattern),
want: []string{validPattern},
name: "valid pattern",
input: `
[INFO] Sending request to the aiven API
[DEBUG] Using Key = yb+Ygm82FfUworm2exB+Uk255p0uQKmmfx4ut1KfsZ3YI3Gp2xPYyxZgrwYabMxXXO4WPsK7xlLJRy0BWIpM2SKnzA2p69P8aOmYbl24ZiVGlLXyQxeVDDy7gru5Yzt=Y1UDLBpsW=hhGIKsrPgc/7hpxuEfEqbXJe5IBYO484F+ekaTmYN4nTF94O==3WuG+WuSW7zaYzXH1V==kZFj07zBtmShS0z/lW=N3HipH=oJjXI2pyFxU+A7vM9yHdUHoiZEOVoWsyp5zO1ajBOqFr=3jIIaXWmbH33dP2ZNQFJhqbeg6JlXA9GpfMFht5=ZCC1IirWCNp=UILbmZtvu9d2M8U0YNHwAGKtjrPS5lZvAU+W5s2Ti
[INFO] Response received: 200 OK
`,
want: []string{"yb+Ygm82FfUworm2exB+Uk255p0uQKmmfx4ut1KfsZ3YI3Gp2xPYyxZgrwYabMxXXO4WPsK7xlLJRy0BWIpM2SKnzA2p69P8aOmYbl24ZiVGlLXyQxeVDDy7gru5Yzt=Y1UDLBpsW=hhGIKsrPgc/7hpxuEfEqbXJe5IBYO484F+ekaTmYN4nTF94O==3WuG+WuSW7zaYzXH1V==kZFj07zBtmShS0z/lW=N3HipH=oJjXI2pyFxU+A7vM9yHdUHoiZEOVoWsyp5zO1ajBOqFr=3jIIaXWmbH33dP2ZNQFJhqbeg6JlXA9GpfMFht5=ZCC1IirWCNp=UILbmZtvu9d2M8U0YNHwAGKtjrPS5lZvAU+W5s2Ti"},
},
{
name: "valid pattern - key out of prefix range",
input: fmt.Sprintf("aiven keyword is not close to the real key and secret = '%s'", validPattern),
want: nil,
name: "valid pattern - xml",
input: `
<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
<scope>GLOBAL</scope>
<id>{aiven}</id>
<secret>{aiven AQAAABAAA IGhXNR6g7rogABp/H2iDQu7TgkXpvn9KnwzJfeh+8p7M=JVsI2QoQ38mmQHt450bQC4wBOGFhV+9QT2KGWSMfTOxTUrUXygaLlwsXo/RBxKXyOdh=/L8EGGrqG6=qbd0UzDAfc0xeAfXd30RGj+Ypsrrvdda=ZPa32BBID5r2ClfJSbgpfWIpVC1b5vlqCdy5LIWABZJzjBC5VweqZ04XFaCh+15NuSQ4E0KdGwPdkrfxxjY20I1wDvlKxzxL7dfCly3KVlQv7KBEFSLaLRNRocPYToUXqU4yAXKvXf03K=k1mahpxFUp94c35k/n055LVs=xbyL6AKdW=sCCa1AFIYKBDMBprTsZ6Al7DHx=XA6qLNWYxS7}</secret>
<description>configuration for production</description>
<creationDate>2023-05-18T14:32:10Z</creationDate>
<owner>jenkins-admin</owner>
</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
`,
want: []string{"IGhXNR6g7rogABp/H2iDQu7TgkXpvn9KnwzJfeh+8p7M=JVsI2QoQ38mmQHt450bQC4wBOGFhV+9QT2KGWSMfTOxTUrUXygaLlwsXo/RBxKXyOdh=/L8EGGrqG6=qbd0UzDAfc0xeAfXd30RGj+Ypsrrvdda=ZPa32BBID5r2ClfJSbgpfWIpVC1b5vlqCdy5LIWABZJzjBC5VweqZ04XFaCh+15NuSQ4E0KdGwPdkrfxxjY20I1wDvlKxzxL7dfCly3KVlQv7KBEFSLaLRNRocPYToUXqU4yAXKvXf03K=k1mahpxFUp94c35k/n055LVs=xbyL6AKdW=sCCa1AFIYKBDMBprTsZ6Al7DHx=XA6qLNWYxS7"},
},
{
name: "invalid pattern",
input: fmt.Sprintf("aiven = '%s'", invalidPattern),
want: nil,
name: "valid pattern - key out of prefix range",
input: `
[DEBUG] aiven api processing
[INFO] Sending request to the API
[DEBUG] Using Key=yb+Ygm82FfUworm2exB+Uk255p0uQKmmfx4ut1KfsZ3YI3Gp2xPYyxZgrwYabMxXXO4WPsK7xlLJRy0BWIpM2SKnzA2p69P8aOmYbl24ZiVGlLXyQxeVDDy7gru5Yzt=Y1UDLBpsW=hhGIKsrPgc/7hpxuEfEqbXJe5IBYO484F+ekaTmYN4nTF94O==3WuG+WuSW7zaYzXH1V==kZFj07zBtmShS0z/lW=N3HipH=oJjXI2pyFxU+A7vM9yHdUHoiZEOVoWsyp5zO1ajBOqFr=3jIIaXWmbH33dP2ZNQFJhqbeg6JlXA9GpfMFht5=ZCC1IirWCNp=UILbmZtvu9d2M8U0YNHwAGKtjrPS5lZvAU+W5s2Ti
[INFO] Response received: 200 OK
`,
want: nil,
},
{
name: "invalid pattern",
input: `
[INFO] Sending request to the aiven API
[DEBUG] Using Key=SSs8PGhwqWzb4qfqiwLV/bNHfiQ2VSKyX88AAYm3+CGHbTe/FYXRNOYYHO=PXwuL/GftiES7j8ffzWW9p1dAyNc6hZZpoazmd+Vf1kbukZSL8QO/LdKFI/YFlupu0dELqQVHeZi/cJlnp6aQeY7zIJiHhJS51ZVdOamc=zOUMebry3BYOo2LhYIz+mLND7s5/cHZZpkEvTXrKnVf4vdYMl+fawv84AYCTo9pry8FQBsqRex2HL98kAiqhVYG+nLyRz/hZCo8owaRkzli1BUT4O63TSKJIgnECOBvyZz7o+yX92BhDe+B2Tllk3y2=qG5TiEl2sCJI8V5GJ1cz52RpXx2hVXMi=1Zl5CHpX8Adr9VMbj$Co
[ERROR] Response received: 401 UnAuthorized
`,
want: nil,
},
}
@@ -46,22 +68,15 @@ func TestAiven_Pattern(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
if len(matchedDetectors) == 0 {
t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
return
}
results, err := d.FromData(context.Background(), false, []byte(test.input))
if err != nil {
t.Errorf("error = %v", err)
return
}
require.NoError(t, err)
if len(results) != len(test.want) {
if len(results) == 0 {
t.Errorf("did not receive result")
} else {
t.Errorf("expected %d results, only received %d", len(test.want), len(results))
}
t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
return
}
@@ -73,6 +88,7 @@ func TestAiven_Pattern(t *testing.T) {
actual[string(r.Raw)] = struct{}{}
}
}
expected := make(map[string]struct{}, len(test.want))
for _, v := range test.want {
expected[v] = struct{}{}
+2 -2
View File
@@ -29,7 +29,7 @@ var (
// Keywords are used for efficiently pre-filtering chunks.
// Use identifiers in the secret preferably, or the provider name.
func (s Scanner) Keywords() []string {
return []string{"alchemy","alcht_"}
return []string{"alchemy", "alcht_"}
}
// FromData will find and optionally verify Alchemy secrets in a given set of bytes.
@@ -68,7 +68,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
func verifyMatch(ctx context.Context, client *http.Client, token string) (bool, map[string]string, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://eth-mainnet.g.alchemy.com/v2/"+token+"/getNFTs/?owner=vitalik.eth", nil)
if err != nil {
return false, nil, nil
return false, nil, err
}
res, err := client.Do(req)
+44 -20
View File
@@ -2,10 +2,13 @@ package alchemy
import (
"context"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
"testing"
)
func TestAlchemy_Pattern(t *testing.T) {
@@ -17,20 +20,47 @@ func TestAlchemy_Pattern(t *testing.T) {
want []string
}{
{
name: "typical pattern",
input: "alchemy_token = '3aBcDFE5678901234567890_1a2b3c4d'",
want: []string{"3aBcDFE5678901234567890_1a2b3c4d"},
name: "valid pattern",
input: `
[INFO] Sending request to the alchemy API
[DEBUG] Using Key=alcht_2Cy8xCLyvrAf7lZKfhQhyCr4RAID9D
[INFO] Response received: 200 OK
`,
want: []string{"alcht_2Cy8xCLyvrAf7lZKfhQhyCr4RAID9D"},
},
{
name: "valid pattern - xml",
input: `
<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
<scope>GLOBAL</scope>
<id>{alchemy}</id>
<secret>{alchemy AQAAABAAA 5iqW7gKQVXvwnykF9xAVfenemmnUJznI}</secret>
<description>configuration for production</description>
<creationDate>2023-05-18T14:32:10Z</creationDate>
<owner>jenkins-admin</owner>
</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
`,
want: []string{"5iqW7gKQVXvwnykF9xAVfenemmnUJznI"},
},
{
name: "finds all matches",
input: `alchemy_token1 = '3aBcDFE5678901234567890_1a2b3c4d'
alchemy_token2 = '3aDcDFE56789012245678a0_1a2b3c2d'`,
want: []string{"3aBcDFE5678901234567890_1a2b3c4d", "3aDcDFE56789012245678a0_1a2b3c2d"},
input: `
[INFO] Sending request to the alchemy API
[DEBUG] Using Key=alcht_2Cy8xCLyvrAf7lZKfhQhyCr4RAID9D
[ERROR] Response received 401 UnAuthorized
[DEBUG] Using alchemy Key=xuQIeWFVEp8k8Uu9FwPx6X5C8IViOe1o
[INFO] Response received: 200 OK
`,
want: []string{"alcht_2Cy8xCLyvrAf7lZKfhQhyCr4RAID9D", "xuQIeWFVEp8k8Uu9FwPx6X5C8IViOe1o"},
},
{
name: "invalid pattern",
input: "alchemy_token = '1a2b3c4d'",
want: []string{},
name: "invalid pattern",
input: `
[INFO] Sending request to the alchemy API
[DEBUG] Using Key=alcht_a2Cy8xCLyvrAf7lZKfhQhyCr4RAID9D
[ERROR] Response received: 401 UnAuthorized
`,
want: []string{},
},
}
@@ -38,22 +68,15 @@ alchemy_token2 = '3aDcDFE56789012245678a0_1a2b3c2d'`,
t.Run(test.name, func(t *testing.T) {
matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
if len(matchedDetectors) == 0 {
t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
return
}
results, err := d.FromData(context.Background(), false, []byte(test.input))
if err != nil {
t.Errorf("error = %v", err)
return
}
require.NoError(t, err)
if len(results) != len(test.want) {
if len(results) == 0 {
t.Errorf("did not receive result")
} else {
t.Errorf("expected %d results, only received %d", len(test.want), len(results))
}
t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
return
}
@@ -65,6 +88,7 @@ alchemy_token2 = '3aDcDFE56789012245678a0_1a2b3c2d'`,
actual[string(r.Raw)] = struct{}{}
}
}
expected := make(map[string]struct{}, len(test.want))
for _, v := range test.want {
expected[v] = struct{}{}
+41 -25
View File
@@ -2,20 +2,15 @@ package alconost
import (
"context"
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
)
var (
validPattern = "wdvnousa87acfxp9ioasrea4tbeasrfa"
invalidPattern = "wdvnousa87acfxp9ioasr$a4tBeasrfa"
)
func TestAlconost_Pattern(t *testing.T) {
d := Scanner{}
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
@@ -26,19 +21,46 @@ func TestAlconost_Pattern(t *testing.T) {
want []string
}{
{
name: "valid pattern",
input: fmt.Sprintf("alconost: '%s'", validPattern),
want: []string{validPattern},
name: "valid pattern",
input: `
[INFO] Sending request to the alconost API
[DEBUG] Using Key=wdvnousa87acfxp9ioasrea4tbeasrfa
[INFO] Response received: 200 OK
`,
want: []string{"wdvnousa87acfxp9ioasrea4tbeasrfa"},
},
{
name: "valid pattern - key out of prefix range",
input: fmt.Sprintf("alconost keyword is not close to the real key in the data = '%s'", validPattern),
want: nil,
name: "valid pattern - xml",
input: `
<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
<scope>GLOBAL</scope>
<id>{alconost}</id>
<secret>{alconost AQAAABAAA Awxzhkwff46dtkt5pnvdlss6t2kA44a7}</secret>
<description>configuration for production</description>
<creationDate>2023-05-18T14:32:10Z</creationDate>
<owner>jenkins-admin</owner>
</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
`,
want: []string{"Awxzhkwff46dtkt5pnvdlss6t2kA44a7"},
},
{
name: "invalid pattern",
input: fmt.Sprintf("alconost: '%s'", invalidPattern),
want: nil,
name: "valid pattern - key out of prefix range",
input: `
[DEBUG] alconost api processing
[INFO] Sending request to the API
[DEBUG] Using Key=wdvnousa87acfxp9ioasrea4tbeasrfa
[INFO] Response received: 200 OK
`,
want: nil,
},
{
name: "invalid pattern",
input: `
[INFO] Sending request to the alconost API
[DEBUG] Using Key=wdvnousa87acfxp9ioasra4tBeasrfa
[INFO] Response received: 401 UnAuthorized
`,
want: nil,
},
}
@@ -46,22 +68,15 @@ func TestAlconost_Pattern(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
if len(matchedDetectors) == 0 {
t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
return
}
results, err := d.FromData(context.Background(), false, []byte(test.input))
if err != nil {
t.Errorf("error = %v", err)
return
}
require.NoError(t, err)
if len(results) != len(test.want) {
if len(results) == 0 {
t.Errorf("did not receive result")
} else {
t.Errorf("expected %d results, only received %d", len(test.want), len(results))
}
t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
return
}
@@ -73,6 +88,7 @@ func TestAlconost_Pattern(t *testing.T) {
actual[string(r.Raw)] = struct{}{}
}
}
expected := make(map[string]struct{}, len(test.want))
for _, v := range test.want {
expected[v] = struct{}{}
+43 -26
View File
@@ -2,21 +2,15 @@ package alegra
import (
"context"
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
)
var (
validPattern = "wdvn-usa87a-fxp9ioas/[email protected]"
validSpecialCharPattern = "wdvn-usa87a-fxp9ioas / [email protected]"
invalidPattern = "wdvn-usa87a-fxp9ioasQQsstestUsQQ@example"
)
func TestAlegra_Pattern(t *testing.T) {
d := Scanner{}
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
@@ -27,43 +21,65 @@ func TestAlegra_Pattern(t *testing.T) {
want []string
}{
{
name: "valid pattern",
input: fmt.Sprintf("alegra: %s", validPattern),
want: []string{"wdvn-usa87a-fxp9ioas:wdvn-usa87a-fxp9ioas/[email protected]"},
name: "valid pattern",
input: `
[INFO] Sending request to the API
[DEBUG] Using alegra Key=wdvn-usa87a-fxp9ioas
[DEBUG] Using alegra Email = testUser.1005@example.com
[INFO] Response received: 200 OK
`,
want: []string{"wdvn-usa87a-fxp9ioas:[email protected]"},
},
{
name: "valid pattern - with special characters",
input: fmt.Sprintf("alegra: %s", validSpecialCharPattern),
want: []string{"wdvn-usa87a-fxp9ioas:[email protected]"},
name: "valid pattern - xml",
input: `
<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
<scope>GLOBAL</scope>
<id>{alegra kk18@example.com}</id>
<secret>{alegra AQAAABAAA buihlmkfnh5m1lk5z6do}</secret>
<description>configuration for production</description>
<creationDate>2023-05-18T14:32:10Z</creationDate>
<owner>jenkins-admin</owner>
</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
`,
want: []string{"buihlmkfnh5m1lk5z6do:[email protected]"},
},
{
name: "valid pattern - key out of prefix range",
input: fmt.Sprintf("alegra keyword is not close to the real key and id = %s", validPattern),
want: nil,
name: "valid pattern - key out of prefix range",
input: `
[DEBUG] alegra api processing
[INFO] Sending request to the API
[DEBUG] Using Key=wdvn-usa87a-fxp9ioas
[DEBUG] Using Email=testUser.1005@example.com
[INFO] Response received: 200 OK
`,
want: nil,
},
{
name: "invalid pattern",
input: fmt.Sprintf("alegra: %s", invalidPattern),
want: nil,
name: "invalid pattern",
input: `
[INFO] Sending request to the API
[DEBUG] Using alegra Key=wdvn_usa87a-fxp9ioas
[DEBUG] Using alegra Email=testUser.1005@example.com
[INFO] Response received: 401 UnAuthorized
`,
want: nil,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
if len(matchedDetectors) == 0 && test.want != nil {
t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
if len(matchedDetectors) == 0 {
t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
return
}
results, err := d.FromData(context.Background(), false, []byte(test.input))
if err != nil {
t.Errorf("error = %v", err)
return
}
require.NoError(t, err)
if len(results) != len(test.want) {
t.Errorf("expected %d results, got %d", len(test.want), len(results))
t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
return
}
@@ -75,6 +91,7 @@ func TestAlegra_Pattern(t *testing.T) {
actual[string(r.Raw)] = struct{}{}
}
}
expected := make(map[string]struct{}, len(test.want))
for _, v := range test.want {
expected[v] = struct{}{}
+41 -25
View File
@@ -2,20 +2,15 @@ package aletheiaapi
import (
"context"
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
)
var (
validPattern = "TEST01KEYPATTERN2024ALETHEIAAPIN"
invalidPattern = "TEST01kEYPATTERN2024ALETHEIAAPIN"
)
func TestAleTheIaAPI_Pattern(t *testing.T) {
d := Scanner{}
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
@@ -26,19 +21,46 @@ func TestAleTheIaAPI_Pattern(t *testing.T) {
want []string
}{
{
name: "valid pattern",
input: fmt.Sprintf("aletheiaapi: '%s'", validPattern),
want: []string{validPattern},
name: "valid pattern",
input: `
[INFO] Sending request to the aletheiaapi
[DEBUG] Using Key=LY027C40U2KNNZLFO1WEU3XQZ13LW515
[INFO] Response received: 200 OK
`,
want: []string{"LY027C40U2KNNZLFO1WEU3XQZ13LW515"},
},
{
name: "valid pattern - key out of prefix range",
input: fmt.Sprintf("aletheiaapi keyword is not close to the real key and secret = '%s'", validPattern),
want: nil,
name: "valid pattern - xml",
input: `
<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
<scope>GLOBAL</scope>
<id>{aletheiaapi}</id>
<secret>{aletheiaapi AQAAABAAA K7SOW2B8QH9QE435NLH07PH22XL4YOPG}</secret>
<description>configuration for production</description>
<creationDate>2023-05-18T14:32:10Z</creationDate>
<owner>jenkins-admin</owner>
</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
`,
want: []string{"K7SOW2B8QH9QE435NLH07PH22XL4YOPG"},
},
{
name: "invalid pattern",
input: fmt.Sprintf("aletheiaapi: '%s'", invalidPattern),
want: nil,
name: "valid pattern - key out of prefix range",
input: `
[DEBUG] aletheiaapi api processing
[INFO] Sending request to the API
[DEBUG] Using Key=LY027C40U2KNNZLFO1WEU3XQZ13LW515
[INFO] Response received: 200 OK
`,
want: nil,
},
{
name: "invalid pattern",
input: `
[INFO] Sending request to the aletheiaapi
[DEBUG] Using Key=LY027c40U2KNNZLFO1WEU3XQZ13LW515
[INFO] Response received: 200 OK
`,
want: nil,
},
}
@@ -46,22 +68,15 @@ func TestAleTheIaAPI_Pattern(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
if len(matchedDetectors) == 0 {
t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
return
}
results, err := d.FromData(context.Background(), false, []byte(test.input))
if err != nil {
t.Errorf("error = %v", err)
return
}
require.NoError(t, err)
if len(results) != len(test.want) {
if len(results) == 0 {
t.Errorf("did not receive result")
} else {
t.Errorf("expected %d results, only received %d", len(test.want), len(results))
}
t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
return
}
@@ -73,6 +88,7 @@ func TestAleTheIaAPI_Pattern(t *testing.T) {
actual[string(r.Raw)] = struct{}{}
}
}
expected := make(map[string]struct{}, len(test.want))
for _, v := range test.want {
expected[v] = struct{}{}
@@ -3,14 +3,18 @@ package algoliaadminkey
import (
"context"
"encoding/json"
"errors"
"fmt"
regexp "github.com/wasilibs/go-re2"
"io"
"net/http"
"slices"
"strings"
regexp "github.com/wasilibs/go-re2"
"github.com/trufflesecurity/trufflehog/v3/pkg/cache/simple"
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
logContext "github.com/trufflesecurity/trufflehog/v3/pkg/context"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
)
@@ -28,6 +32,10 @@ var (
// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"algolia", "docsearch", "appId"}) + `\b([A-Z0-9]{10})\b`)
keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"algolia", "docsearch", "apiKey"}) + `\b([a-zA-Z0-9]{32})\b`)
invalidHosts = simple.NewCache[struct{}]()
errNoHost = errors.New("no such host")
)
// Keywords are used for efficiently pre-filtering chunks.
@@ -38,6 +46,7 @@ func (s Scanner) Keywords() []string {
// FromData will find and optionally verify AlgoliaAdminKey secrets in a given set of bytes.
func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
logger := logContext.AddLogger(ctx).Logger().WithName("algoliaadminkey")
dataStr := string(data)
// Deduplicate matches.
@@ -59,6 +68,12 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
// Test matches.
for key := range keyMatches {
for id := range idMatches {
if invalidHosts.Exists(id) {
logger.V(3).Info("Skipping application id: no such host", "host", id)
delete(idMatches, id)
continue
}
r := detectors.Result{
DetectorType: detectorspb.DetectorType_AlgoliaAdminKey,
Raw: []byte(key),
@@ -70,7 +85,14 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
isVerified, extraData, verificationErr := verifyMatch(ctx, id, key)
r.Verified = isVerified
r.ExtraData = extraData
r.SetVerificationError(verificationErr, key)
if verificationErr != nil {
if errors.Is(verificationErr, errNoHost) {
invalidHosts.Set(id, struct{}{})
continue
}
r.SetVerificationError(verificationErr, key)
}
}
results = append(results, r)
@@ -101,6 +123,11 @@ func verifyMatch(ctx context.Context, appId, apiKey string) (bool, map[string]st
res, err := client.Do(req)
if err != nil {
// lookup xyz.algolia.net: no such host
if strings.Contains(err.Error(), "no such host") {
return false, nil, errNoHost
}
return false, nil, err
}
defer func() {
@@ -2,20 +2,15 @@ package algoliaadminkey
import (
"context"
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
)
var (
validPattern = "AlgoliaAdminKey010TESTINGPlmiOPC/ADMIN01KEY"
invalidPattern = "AlgoliaAdminN$TKey010TESTINGPlmiOPC/NonAdmin01ID"
)
func TestAlgoliaAdminKey_Pattern(t *testing.T) {
d := Scanner{}
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
@@ -26,19 +21,48 @@ func TestAlgoliaAdminKey_Pattern(t *testing.T) {
want []string
}{
{
name: "valid pattern",
input: fmt.Sprintf("algolia: '%s'", validPattern),
want: []string{"ADMIN01KEY:AlgoliaAdminKey010TESTINGPlmiOPC"},
name: "valid pattern",
input: `
[INFO] Sending request to the API
[DEBUG] Using algolia Key=BsDaN7ZU7kFiUX5CpN8CUf3nkMaSeZYn
[DEBUG] Using docsearch ID=844XQV5SUA
[INFO] Response received: 200 OK
`,
want: []string{"844XQV5SUA:BsDaN7ZU7kFiUX5CpN8CUf3nkMaSeZYn"},
},
{
name: "valid pattern - key out of prefix range",
input: fmt.Sprintf("algolia keyword is not close to the real key and secret = '%s'", validPattern),
want: nil,
name: "valid pattern - xml",
input: `
<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
<scope>GLOBAL</scope>
<id>{appId 0VJ9I1WV78}</id>
<secret>{algolia AQAAABAAA 4AYm3wz7nfnX7Bqtw5e5Qo3Z5vfBe0eS}</secret>
<description>configuration for production</description>
<creationDate>2023-05-18T14:32:10Z</creationDate>
<owner>jenkins-admin</owner>
</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
`,
want: []string{"0VJ9I1WV78:4AYm3wz7nfnX7Bqtw5e5Qo3Z5vfBe0eS"},
},
{
name: "invalid pattern",
input: fmt.Sprintf("algolia: '%s'", invalidPattern),
want: nil,
name: "valid pattern - key out of prefix range",
input: `
[INFO] Sending request to the algolia API
[DEBUG] Using Key=BsDaN7ZU7kFiUX5CpN8CUf3nkMaSeZYn
[DEBUG] Using ID=844XQV5SUA
[INFO] Response received: 200 OK
`,
want: nil,
},
{
name: "invalid pattern",
input: `
[INFO] Sending request to the API
[DEBUG] Using algolia Key=BsD-N7ZU7kFiUX5CpN8CUf3nkMaSeZYn
[DEBUG] Using docsearch ID=844XqV5SUA
[ERROR] Response received: 401 UnAuthorized
`,
want: nil,
},
}
@@ -46,22 +70,15 @@ func TestAlgoliaAdminKey_Pattern(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
if len(matchedDetectors) == 0 {
t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
return
}
results, err := d.FromData(context.Background(), false, []byte(test.input))
if err != nil {
t.Errorf("error = %v", err)
return
}
require.NoError(t, err)
if len(results) != len(test.want) {
if len(results) == 0 {
t.Errorf("did not receive result")
} else {
t.Errorf("expected %d results, only received %d", len(test.want), len(results))
}
t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
return
}
@@ -73,6 +90,7 @@ func TestAlgoliaAdminKey_Pattern(t *testing.T) {
actual[string(r.Raw)] = struct{}{}
}
}
expected := make(map[string]struct{}, len(test.want))
for _, v := range test.want {
expected[v] = struct{}{}
+43 -25
View File
@@ -2,20 +2,15 @@ package alibaba
import (
"context"
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
)
var (
validPattern = "abcDEF123ghiJKL456mnoPQR789std/LTAI123ABCdef456ghijkLMN"
invalidPattern = "abcDEF123ghiJKL456m$oPQR789/123ABCdef456ghijkLMN;"
)
func TestAliBaba_Pattern(t *testing.T) {
d := Scanner{}
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
@@ -26,19 +21,48 @@ func TestAliBaba_Pattern(t *testing.T) {
want []string
}{
{
name: "valid pattern",
input: fmt.Sprintf("LTAI: '%s'", validPattern),
want: []string{"abcDEF123ghiJKL456mnoPQR789stdLTAI123ABCdef456ghijkLMN"},
name: "valid pattern",
input: `
[INFO] Sending request to the API
[DEBUG] Using Key=CwgR2UwgaWd7hgUdQkwFnK9vvEeO4R
[DEBUG] Using ID=LTAIXgRPqwF1DhBf6Q1uZ5DrM
[INFO] Response received: 200 OK
`,
want: []string{"CwgR2UwgaWd7hgUdQkwFnK9vvEeO4RLTAIXgRPqwF1DhBf6Q1uZ5DrM"},
},
{
name: "valid pattern - ignore special characters at end",
input: fmt.Sprintf("LTAI: '%s\"''", validPattern),
want: []string{"abcDEF123ghiJKL456mnoPQR789stdLTAI123ABCdef456ghijkLMN"},
name: "valid pattern - xml",
input: `
<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
<scope>GLOBAL</scope>
<id>{WX6OtM8pbcrXWMIGc5evYousFWBlBm}</id>
<secret>{AQAAABAAA LTAImg3ZeAPatbAtEDS9HVZ}</secret>
<description>configuration for production</description>
<creationDate>2023-05-18T14:32:10Z</creationDate>
<owner>jenkins-admin</owner>
</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
`,
want: []string{"WX6OtM8pbcrXWMIGc5evYousFWBlBmLTAImg3ZeAPatbAtEDS9HVZ"},
},
{
name: "invalid pattern",
input: fmt.Sprintf("LTAI: '%s'", invalidPattern),
want: nil,
name: "valid pattern - ignore special characters at end",
input: `
[INFO] Sending request to the API
[DEBUG] Using Key=CwgR2UwgaWd7hgUdQkwFnK9vvEeO4R
[DEBUG] Using ID=LTAIXgRPqwF1DhBf6Q1uZ5DrM;
[INFO] Response received: 200 OK
`,
want: []string{"CwgR2UwgaWd7hgUdQkwFnK9vvEeO4RLTAIXgRPqwF1DhBf6Q1uZ5DrM"},
},
{
name: "invalid pattern",
input: `
[INFO] Sending request to the API
[DEBUG] Using Key=CwgR2UwgaWd7hgUdQkwFnK9vvEeO4
[DEBUG] Using ID=LTAIXgRPqwF1DhBf6Q1uZ5DrMYPW
[ERROR] Response received: 401 UnAuthorized
`,
want: nil,
},
}
@@ -46,22 +70,15 @@ func TestAliBaba_Pattern(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
if len(matchedDetectors) == 0 {
t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
return
}
results, err := d.FromData(context.Background(), false, []byte(test.input))
if err != nil {
t.Errorf("error = %v", err)
return
}
require.NoError(t, err)
if len(results) != len(test.want) {
if len(results) == 0 {
t.Errorf("did not receive result")
} else {
t.Errorf("expected %d results, only received %d", len(test.want), len(results))
}
t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
return
}
@@ -73,6 +90,7 @@ func TestAliBaba_Pattern(t *testing.T) {
actual[string(r.Raw)] = struct{}{}
}
}
expected := make(map[string]struct{}, len(test.want))
for _, v := range test.want {
expected[v] = struct{}{}
+41 -25
View File
@@ -2,20 +2,15 @@ package alienvault
import (
"context"
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
)
var (
validPattern = "a3b9f8c1e2d4f5b6c7d8e9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0"
invalidPattern = "A$3b9f8c1e2d4f5b6c7d8e9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d"
)
func TestAlienVault_Pattern(t *testing.T) {
d := Scanner{}
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
@@ -26,19 +21,46 @@ func TestAlienVault_Pattern(t *testing.T) {
want []string
}{
{
name: "valid pattern",
input: fmt.Sprintf("alienvault: '%s'", validPattern),
want: []string{validPattern},
name: "valid pattern",
input: `
[INFO] Sending request to the alienvault API
[DEBUG] Using Key=3em7p52ec9ut4k9ccqha19rz3oyeqnij3mn3ivml577f8pb2179yz9totr648hmy
[INFO] Response received: 200 OK
`,
want: []string{"3em7p52ec9ut4k9ccqha19rz3oyeqnij3mn3ivml577f8pb2179yz9totr648hmy"},
},
{
name: "valid pattern - key out of prefix range",
input: fmt.Sprintf("alienvault keyword is not close to the real key in the data\n = '%s'", validPattern),
want: nil,
name: "valid pattern - xml",
input: `
<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
<scope>GLOBAL</scope>
<id>{alienvault}</id>
<secret>{AQAAABAAA xyi7bj56t5b0hkinw4vz8qgffqhfb2ypemdnt407bke6s0ouuswvcdf5c1qpvse0}</secret>
<description>configuration for production</description>
<creationDate>2023-05-18T14:32:10Z</creationDate>
<owner>jenkins-admin</owner>
</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
`,
want: []string{"xyi7bj56t5b0hkinw4vz8qgffqhfb2ypemdnt407bke6s0ouuswvcdf5c1qpvse0"},
},
{
name: "invalid pattern",
input: fmt.Sprintf("alienvault: '%s'", invalidPattern),
want: nil,
name: "valid pattern - key out of prefix range",
input: `
[INFO] Fetching data from alienvault
[INFO] Sending request to the API
[DEBUG] Using Key=3em7p52ec9ut4k9ccqha19rz3oyeqnij3mn3ivml577f8pb2179yz9totr648hmy
[INFO] Response received: 200 OK
`,
want: nil,
},
{
name: "invalid pattern",
input: `
[INFO] Sending request to the alienvault API
[DEBUG] Using Key=3em7p52ec9ut4k9ccqha19rz3o_eqnij3mn3ivml577f8pb2179yz9totr648hmy
[ERROR] Response received: 401 UnAuthorized
`,
want: nil,
},
}
@@ -46,22 +68,15 @@ func TestAlienVault_Pattern(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
if len(matchedDetectors) == 0 {
t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
return
}
results, err := d.FromData(context.Background(), false, []byte(test.input))
if err != nil {
t.Errorf("error = %v", err)
return
}
require.NoError(t, err)
if len(results) != len(test.want) {
if len(results) == 0 {
t.Errorf("did not receive result")
} else {
t.Errorf("expected %d results, only received %d", len(test.want), len(results))
}
t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
return
}
@@ -73,6 +88,7 @@ func TestAlienVault_Pattern(t *testing.T) {
actual[string(r.Raw)] = struct{}{}
}
}
expected := make(map[string]struct{}, len(test.want))
for _, v := range test.want {
expected[v] = struct{}{}
+41 -25
View File
@@ -2,20 +2,15 @@ package allsports
import (
"context"
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
)
var (
validPattern = "d1f2e3c4b5a6d7e8f9g0h1i2j3k4l5m6n7o8p9q0r1s2t3u4v5w6x7y8z9a0b1ce"
invalidPattern = "Ad1f2e3c4b5a6d7e8f9g0h1i2j3k4l5m6n7o8p9q0r1sRt3u4v5w6x7y8z9a0b1cE"
)
func TestAllSports_Pattern(t *testing.T) {
d := Scanner{}
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
@@ -26,19 +21,46 @@ func TestAllSports_Pattern(t *testing.T) {
want []string
}{
{
name: "valid pattern",
input: fmt.Sprintf("allsports: '%s'", validPattern),
want: []string{validPattern},
name: "valid pattern",
input: `
[INFO] Sending request to the allsports API
[DEBUG] Using Key=cq73u5azj3p3shfvzz3lw1typfqu6uduq7bophtq4veta7cnvd4s5htkb8lgk4vr
[INFO] Response received: 200 OK
`,
want: []string{"cq73u5azj3p3shfvzz3lw1typfqu6uduq7bophtq4veta7cnvd4s5htkb8lgk4vr"},
},
{
name: "valid pattern - key out of prefix range",
input: fmt.Sprintf("allsports keyword is not close to the real key in the data\n = '%s'", validPattern),
want: nil,
name: "valid pattern - xml",
input: `
<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
<scope>GLOBAL</scope>
<id>{allsports}</id>
<secret>{AQAAABAAA bj8yzu3awie5akwiwcb7esqygqx14gt65j9lrcpec0v28ckkswtyza1x9747gap5}</secret>
<description>configuration for production</description>
<creationDate>2023-05-18T14:32:10Z</creationDate>
<owner>jenkins-admin</owner>
</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
`,
want: []string{"bj8yzu3awie5akwiwcb7esqygqx14gt65j9lrcpec0v28ckkswtyza1x9747gap5"},
},
{
name: "invalid pattern",
input: fmt.Sprintf("allsports: '%s'", invalidPattern),
want: nil,
name: "valid pattern - key out of prefix range",
input: `
[DEBUG] allsports api processing
[INFO] Sending request to the API
[DEBUG] Using Key=cq73u5azj3p3shfvzz3lw1typfqu6uduq7bophtq4veta7cnvd4s5htkb8lgk4vr
[INFO] Response received: 200 OK
`,
want: nil,
},
{
name: "invalid pattern",
input: `
[INFO] Sending request to the allsports API
[DEBUG] Using Key=d1f2e3c4b5a6d7e8f9G0h1i2j3k4l5m6n7o8p9q0r1s2t3u4v5w6x7y8z9a0b1ce
[INFO] Response received: 200 OK
`,
want: nil,
},
}
@@ -46,22 +68,15 @@ func TestAllSports_Pattern(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
if len(matchedDetectors) == 0 {
t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
return
}
results, err := d.FromData(context.Background(), false, []byte(test.input))
if err != nil {
t.Errorf("error = %v", err)
return
}
require.NoError(t, err)
if len(results) != len(test.want) {
if len(results) == 0 {
t.Errorf("did not receive result")
} else {
t.Errorf("expected %d results, only received %d", len(test.want), len(results))
}
t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
return
}
@@ -73,6 +88,7 @@ func TestAllSports_Pattern(t *testing.T) {
actual[string(r.Raw)] = struct{}{}
}
}
expected := make(map[string]struct{}, len(test.want))
for _, v := range test.want {
expected[v] = struct{}{}
+43 -25
View File
@@ -2,20 +2,15 @@ package amadeus
import (
"context"
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
)
var (
validPattern = "A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6 / A1b2C3d4E5f6G7ho"
invalidPattern = "A1b2$3d4E5f6G7h8I9j(K1l2M3n4O5)6 / A1b2C3d4E^f6G7#8"
)
func TestAmadeus_Pattern(t *testing.T) {
d := Scanner{}
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
@@ -26,19 +21,48 @@ func TestAmadeus_Pattern(t *testing.T) {
want []string
}{
{
name: "valid pattern",
input: fmt.Sprintf("amadeus: '%s'", validPattern),
want: []string{"A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6A1b2C3d4E5f6G7ho"},
name: "valid pattern",
input: `
[INFO] Sending request to the API
[DEBUG] Using amadeus Key=ttdveNai3Gj6Zrjvgz4fyBEWRLARCG6a
[DEBUG] Using amadeus Secret=9wqrSr2qveaqgQns
[INFO] Response received: 200 OK
`,
want: []string{"ttdveNai3Gj6Zrjvgz4fyBEWRLARCG6a9wqrSr2qveaqgQns"},
},
{
name: "valid pattern - key out of prefix range",
input: fmt.Sprintf("amadeus keyword is not close to the real key in the data\n = '%s'", validPattern),
want: nil,
name: "valid pattern - xml",
input: `
<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
<scope>GLOBAL</scope>
<id>{amadeus ey6U46qCx26dqzMVWAGiibt6m65mM5w9}</id>
<secret>{amadeus AQAAABAAA Ew3TfmLHYaRjPnYO}</secret>
<description>configuration for production</description>
<creationDate>2023-05-18T14:32:10Z</creationDate>
<owner>jenkins-admin</owner>
</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
`,
want: []string{"ey6U46qCx26dqzMVWAGiibt6m65mM5w9Ew3TfmLHYaRjPnYO"},
},
{
name: "invalid pattern",
input: fmt.Sprintf("amadeus: '%s'", invalidPattern),
want: nil,
name: "valid pattern - key out of prefix range",
input: `
[INFO] Sending request to the amadeus API
[DEBUG] Using Key=ttdveNai3Gj6Zrjvgz4fyBEWRLARCG6a
[DEBUG] Using Secret=9wqrSr2qveaqgQns
[INFO] Response received: 200 OK
`,
want: nil,
},
{
name: "invalid pattern",
input: `
[INFO] Sending request to the amadeus API
[DEBUG] Using amadeus Key=tthdveNai3Gj6Zrjvgz4fyBEWRLARCG6a
[DEBUG] Using amadeus Secret=9wqrSr2qveacqgQns
[INFO] Response received: 401 UnAuthorized
`,
want: nil,
},
}
@@ -46,22 +70,15 @@ func TestAmadeus_Pattern(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
if len(matchedDetectors) == 0 {
t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
return
}
results, err := d.FromData(context.Background(), false, []byte(test.input))
if err != nil {
t.Errorf("error = %v", err)
return
}
require.NoError(t, err)
if len(results) != len(test.want) {
if len(results) == 0 {
t.Errorf("did not receive result")
} else {
t.Errorf("expected %d results, only received %d", len(test.want), len(results))
}
t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
return
}
@@ -73,6 +90,7 @@ func TestAmadeus_Pattern(t *testing.T) {
actual[string(r.Raw)] = struct{}{}
}
}
expected := make(map[string]struct{}, len(test.want))
for _, v := range test.want {
expected[v] = struct{}{}
+41 -25
View File
@@ -2,20 +2,15 @@ package ambee
import (
"context"
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
)
var (
validPattern = "a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4"
invalidPattern = "X3b4c5d6e7f8a9b0c1d2e3f4a5b6c7Z8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4"
)
func TestAmbee_Pattern(t *testing.T) {
d := Scanner{}
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
@@ -26,19 +21,46 @@ func TestAmbee_Pattern(t *testing.T) {
want []string
}{
{
name: "valid pattern",
input: fmt.Sprintf("ambee: '%s'", validPattern),
want: []string{"a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4"},
name: "valid pattern",
input: `
[INFO] Sending request to the ambee API
[DEBUG] Using Key=eccb41cc2d4dab96b748ed040e9b308161279820447ef4553ba6e6d20ecb9962
[INFO] Response received: 200 OK
`,
want: []string{"eccb41cc2d4dab96b748ed040e9b308161279820447ef4553ba6e6d20ecb9962"},
},
{
name: "valid pattern - key out of prefix range",
input: fmt.Sprintf("ambee keyword is not close to the real key in the data\n = '%s'", validPattern),
want: nil,
name: "valid pattern - xml",
input: `
<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
<scope>GLOBAL</scope>
<id>{ambee}</id>
<secret>{ambee AQAAABAAA b91280c63e1571ad928d52947cc31a14ad1bf5a83088d0346b94f6683cf22138}</secret>
<description>configuration for production</description>
<creationDate>2023-05-18T14:32:10Z</creationDate>
<owner>jenkins-admin</owner>
</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
`,
want: []string{"b91280c63e1571ad928d52947cc31a14ad1bf5a83088d0346b94f6683cf22138"},
},
{
name: "invalid pattern",
input: fmt.Sprintf("ambee: '%s'", invalidPattern),
want: nil,
name: "valid pattern - key out of prefix range",
input: `
[INFO] Fetching data from ambee
[INFO] Sending request to the API
[DEBUG] Using Key=eccb41cc2d4dab96b748ed040e9b308161279820447ef4553ba6e6d20ecb9962
[INFO] Response received: 200 OK
`,
want: nil,
},
{
name: "invalid pattern",
input: `
[INFO] Sending request to the ambee API
[DEBUG] Using Key=eccb41cc2d4dab96y748ed040e9b308161279820447ef4553ba6e6d20ecb9962
[INFO] Response received: 200 OK
`,
want: nil,
},
}
@@ -46,22 +68,15 @@ func TestAmbee_Pattern(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
if len(matchedDetectors) == 0 {
t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
return
}
results, err := d.FromData(context.Background(), false, []byte(test.input))
if err != nil {
t.Errorf("error = %v", err)
return
}
require.NoError(t, err)
if len(results) != len(test.want) {
if len(results) == 0 {
t.Errorf("did not receive result")
} else {
t.Errorf("expected %d results, only received %d", len(test.want), len(results))
}
t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
return
}
@@ -73,6 +88,7 @@ func TestAmbee_Pattern(t *testing.T) {
actual[string(r.Raw)] = struct{}{}
}
}
expected := make(map[string]struct{}, len(test.want))
for _, v := range test.want {
expected[v] = struct{}{}
@@ -2,26 +2,15 @@ package amplitudeapikey
import (
"context"
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
)
var (
validPattern = `
amplitude key = 1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d
amplitude secret = 5b3c4d5e5f7a8b9c0d1e2f2f3a4b4c6e
`
invalidPattern = `
amplitude key = 1a2b3c4d5e6f7a8g9c0d1e2f3a4b5c6d
amplitude secret = 5b3c4d5e5f7a8b9c0d1r2f2f3a4b4c6e
`
)
func TestAmplitudeAPIKey_Pattern(t *testing.T) {
d := Scanner{}
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
@@ -32,17 +21,44 @@ func TestAmplitudeAPIKey_Pattern(t *testing.T) {
want []string
}{
{
name: "valid pattern",
input: fmt.Sprintf("amplitude: '%s'", validPattern),
name: "valid pattern",
input: `
[INFO] Sending request to the API
[DEBUG] Using amplitude Key=c2167730016af34b89e200ecf55710e8
[DEBUG] Using amplitude Secret=5488620aa9073c09f1a16e2b1dc357b6
[INFO] Response received: 200 OK
`,
want: []string{
"1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d5b3c4d5e5f7a8b9c0d1e2f2f3a4b4c6e",
"5b3c4d5e5f7a8b9c0d1e2f2f3a4b4c6e1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d",
"c2167730016af34b89e200ecf55710e85488620aa9073c09f1a16e2b1dc357b6",
"5488620aa9073c09f1a16e2b1dc357b6c2167730016af34b89e200ecf55710e8",
},
},
{
name: "invalid pattern",
input: fmt.Sprintf("amplitude: '%s'", invalidPattern),
want: nil,
name: "valid pattern - xml",
input: `
<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
<scope>GLOBAL</scope>
<id>{amplitude aac639f65d80ec2eec96e775f598ce13}</id>
<secret>{amplitude AQAAABAAA 8ac62041353622f9c5e4657807ff1eac}</secret>
<description>configuration for production</description>
<creationDate>2023-05-18T14:32:10Z</creationDate>
<owner>jenkins-admin</owner>
</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
`,
want: []string{
"aac639f65d80ec2eec96e775f598ce138ac62041353622f9c5e4657807ff1eac",
"8ac62041353622f9c5e4657807ff1eacaac639f65d80ec2eec96e775f598ce13",
},
},
{
name: "invalid pattern",
input: `
[INFO] Sending request to the API
[DEBUG] Using amplitude Key=c2167730016rf34b89e200ecf55710e8
[DEBUG] Using amplitude Secret=5488620aa9073q09f1a16e2b1dc357b6
[INFO] Response received: 401 UnAuthorized
`,
want: nil,
},
}
@@ -50,22 +66,15 @@ func TestAmplitudeAPIKey_Pattern(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
if len(matchedDetectors) == 0 {
t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
return
}
results, err := d.FromData(context.Background(), false, []byte(test.input))
if err != nil {
t.Errorf("error = %v", err)
return
}
require.NoError(t, err)
if len(results) != len(test.want) {
if len(results) == 0 {
t.Errorf("did not receive result")
} else {
t.Errorf("expected %d results, only received %d", len(test.want), len(results))
}
t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
return
}
@@ -77,6 +86,7 @@ func TestAmplitudeAPIKey_Pattern(t *testing.T) {
actual[string(r.Raw)] = struct{}{}
}
}
expected := make(map[string]struct{}, len(test.want))
for _, v := range test.want {
expected[v] = struct{}{}
+43 -30
View File
@@ -5,26 +5,12 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
)
var (
validPattern = `
System Log - Authentication Token Issued
Date: 2025-02-04 14:32:10 UTC
Server: api-secure-03.internal
Service: Anthropic API Gateway
API Key: sk-ant-api03-abc123xyz-456def789ghij-klmnopqrstuvwx-3456yza789bcde-1234fghijklmnopby56aaaogaopaaaabc123xyzAA
Admin Key: sk-ant-admin01-abc12fake-456def789ghij-klmnopqrstuvwx-3456yza789bcde-12fakehijklmnopby56aaaogaopaaaabc123xyzAA
Log Entry:
A new API and Admin key has been generated for service authentication. Please ensure that this key remains confidential and is not exposed in any public repositories or logs.
`
invalidPattern = "sk-ant-api03-abc123xyz-456de-klMnopqrstuvwx-3456yza789bcde-1234fghijklmnopAA"
)
func TestAnthropic_Pattern(t *testing.T) {
d := Scanner{}
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
@@ -35,17 +21,50 @@ func TestAnthropic_Pattern(t *testing.T) {
want []string
}{
{
name: "valid pattern",
input: validPattern,
name: "valid pattern",
input: `
System Log - Authentication Token Issued
Date: 2025-02-04 14:32:10 UTC
Server: api-secure-03.internal
Service: Anthropic API Gateway
API Key: sk-ant-api03-abc123xyz-456def789ghij-klmnopqrstuvwx-3456yza789bcde-1234fghijklmnopby56aaaogaopaaaabc123xyzAA
Admin Key: sk-ant-admin01-abc12fake-456def789ghij-klmnopqrstuvwx-3456yza789bcde-12fakehijklmnopby56aaaogaopaaaabc123xyzAA
Log Entry:
A new API and Admin key has been generated for service authentication. Please ensure that this key remains confidential and is not exposed in any public repositories or logs.
`,
want: []string{
"sk-ant-api03-abc123xyz-456def789ghij-klmnopqrstuvwx-3456yza789bcde-1234fghijklmnopby56aaaogaopaaaabc123xyzAA",
"sk-ant-admin01-abc12fake-456def789ghij-klmnopqrstuvwx-3456yza789bcde-12fakehijklmnopby56aaaogaopaaaabc123xyzAA",
},
},
{
name: "invalid pattern",
input: invalidPattern,
want: nil,
name: "valid pattern - xml",
input: `
<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
<scope>GLOBAL</scope>
<id>{anthropic}</id>
<secret>{AQAAABAAA sk-ant-api03-Dtjm9IZ_rYhS_ihHLZmPXhjJ6PN8UPp7vNO7qO3735RRDpf8xbWGinsch0McONXznUm-4KWoA7WU2otvvwHBR5QRjiLakAA}</secret>
<description>configuration for production</description>
<creationDate>2023-05-18T14:32:10Z</creationDate>
<owner>jenkins-admin</owner>
</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
`,
want: []string{"sk-ant-api03-Dtjm9IZ_rYhS_ihHLZmPXhjJ6PN8UPp7vNO7qO3735RRDpf8xbWGinsch0McONXznUm-4KWoA7WU2otvvwHBR5QRjiLakAA"},
},
{
name: "invalid pattern",
input: `
System Log - Authentication Token Issued
Date: 2025-02-04 14:32:10 UTC
Server: api-secure-03.internal
Service: Anthropic API Gateway
API Key: sk-ant-api03-abc123xyz-456de-klMnopqrstuvwx-3456yza789bcde-1234fghijklmnopAA
Log Entry:
A new API key has been generated for service authentication. Please ensure that this key remains confidential and is not exposed in any public repositories or logs.
`,
want: nil,
},
}
@@ -53,22 +72,15 @@ func TestAnthropic_Pattern(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
if len(matchedDetectors) == 0 {
t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
return
}
results, err := d.FromData(context.Background(), false, []byte(test.input))
if err != nil {
t.Errorf("error = %v", err)
return
}
require.NoError(t, err)
if len(results) != len(test.want) {
if len(results) == 0 {
t.Errorf("did not receive result")
} else {
t.Errorf("expected %d results, only received %d", len(test.want), len(results))
}
t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
return
}
@@ -80,6 +92,7 @@ func TestAnthropic_Pattern(t *testing.T) {
actual[string(r.Raw)] = struct{}{}
}
}
expected := make(map[string]struct{}, len(test.want))
for _, v := range test.want {
expected[v] = struct{}{}
+53 -46
View File
@@ -2,41 +2,15 @@ package anypoint
import (
"context"
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
)
var (
validPattern = "1a2b3c4d-5e6f-7g8h-9i0j-k1l2m3n4o5p6 / org: abcd1234-ef56-gh78-ij90-klmn1234opqr"
complexPattern = `
# Secret Configuration File
# Organization details
ORG_NAME=my_organization
ORG_ID=abcd1234-ef56-gh78-ij90-klmn1234opqr
# Database credentials
DB_USERNAME=iamnotadmin
DB_PASSWORD=8f3b6d3e7c9a2f5e
# OAuth tokens
ACCESS_TOKEN=abcxyz123
REFRESH_TOKEN=zyxwvutsrqponmlkji9876543210abcd
# API keys
API_KEY=sk-ant-api03-nothing-just-some-random-api-key-1234fghijklmnopAA
SECRET_KEY=1a2b3c4d-5e6f-7g8h-9i0j-k1l2m3n4o5p6
# Endpoints
SERVICE_URL=https://api.example.com/v1/resource
`
invalidPattern = "1a2b3C4d-5E6f-7g8H-9i0J-k1l2M3n4o5p6 / abcd1234-eF56-gH78-ij90-klmn1234opqr"
)
func TestAnypoint_Pattern(t *testing.T) {
d := Scanner{}
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
@@ -47,19 +21,58 @@ func TestAnypoint_Pattern(t *testing.T) {
want []string
}{
{
name: "valid pattern",
input: fmt.Sprintf("anypoint credentials: %s", validPattern),
want: []string{"1a2b3c4d-5e6f-7g8h-9i0j-k1l2m3n4o5p6abcd1234-ef56-gh78-ij90-klmn1234opqr"},
name: "valid pattern",
input: `
# Anypoint Secret Configuration File
# Organization details
ORG_NAME=my_organization
ORG_ID=abcd1234-ef56-gh78-ij90-klmn1234opqr
# OAuth tokens
ACCESS_TOKEN=abcxyz123
REFRESH_TOKEN=zyxwvutsrqponmlkji9876543210abcd
# API keys
SECRET_KEY=1a2b3c4d-5e6f-7g8h-9i0j-k1l2m3n4o5p6
# Endpoints
SERVICE_URL=https://api.example.com/v1/resource
`,
want: []string{"1a2b3c4d-5e6f-7g8h-9i0j-k1l2m3n4o5p6abcd1234-ef56-gh78-ij90-klmn1234opqr"},
},
{
name: "valid pattern - complex",
input: fmt.Sprintf("anypoint credentials: %s", complexPattern),
want: []string{"1a2b3c4d-5e6f-7g8h-9i0j-k1l2m3n4o5p6abcd1234-ef56-gh78-ij90-klmn1234opqr"},
name: "valid pattern - xml",
input: `
<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
<scope>GLOBAL</scope>
<id>{anypoint org rdogw4dd-6x3l-2nm3-jvl5-qi8dyheccgj7}</id>
<secret>{AQAAABAAA 7jhlugw8-3tfb-7ju2-0i0y-7un6qxvknbvz}</secret>
<description>configuration for production</description>
<creationDate>2023-05-18T14:32:10Z</creationDate>
<owner>jenkins-admin</owner>
</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
`,
want: []string{"7jhlugw8-3tfb-7ju2-0i0y-7un6qxvknbvzrdogw4dd-6x3l-2nm3-jvl5-qi8dyheccgj7"},
},
{
name: "invalid pattern",
input: fmt.Sprintf("anypoint credentials: %s", invalidPattern),
want: nil,
name: "invalid pattern",
input: `
# Anypoint Secret Configuration File
# Organization details
ORG_NAME=my_organization
ORG_ID=abcd1234-ef56-gh78-ij90-klmn1234opqr
# OAuth tokens
ACCESS_TOKEN=abcxyz123
REFRESH_TOKEN=zyxwvutsrqponmlkji9876543210abcd
# API keys
SECRET_KEY=1a2b3C4d-5E6f-7g8H-9i0J-k1l2M3n4o5p6
# Endpoints
SERVICE_URL=https://api.example.com/v1/resource
`,
want: nil,
},
}
@@ -67,22 +80,15 @@ func TestAnypoint_Pattern(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
if len(matchedDetectors) == 0 {
t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
return
}
results, err := d.FromData(context.Background(), false, []byte(test.input))
if err != nil {
t.Errorf("error = %v", err)
return
}
require.NoError(t, err)
if len(results) != len(test.want) {
if len(results) == 0 {
t.Errorf("did not receive result")
} else {
t.Errorf("expected %d results, only received %d", len(test.want), len(results))
}
t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
return
}
@@ -94,6 +100,7 @@ func TestAnypoint_Pattern(t *testing.T) {
actual[string(r.Raw)] = struct{}{}
}
}
expected := make(map[string]struct{}, len(test.want))
for _, v := range test.want {
expected[v] = struct{}{}
@@ -0,0 +1,131 @@
package anypointoauth2
import (
"context"
"fmt"
"io"
"net/http"
"strings"
regexp "github.com/wasilibs/go-re2"
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
)
type Scanner struct {
client *http.Client
detectors.DefaultMultiPartCredentialProvider
}
// Ensure the Scanner satisfies the interface at compile time.
var _ detectors.Detector = (*Scanner)(nil)
var (
defaultClient = common.SaneHttpClient()
// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"anypoint", "id"}) + `\b([0-9a-f]{32})\b`)
secretPat = regexp.MustCompile(detectors.PrefixRegex([]string{"anypoint", "secret"}) + `\b([0-9a-fA-F]{32})\b`)
verificationUrl = "https://anypoint.mulesoft.com/accounts/oauth2/token"
)
// Keywords are used for efficiently pre-filtering chunks.
// Use identifiers in the secret preferably, or the provider name.
func (s Scanner) Keywords() []string {
return []string{"anypoint"}
}
func (s Scanner) getClient() *http.Client {
if s.client != nil {
return s.client
}
return defaultClient
}
// FromData will find and optionally verify Anypoint secrets in a given set of bytes.
func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
dataStr := string(data)
var uniqueIDs, uniqueSecrets = make(map[string]struct{}), make(map[string]struct{})
for _, matches := range idPat.FindAllStringSubmatch(dataStr, -1) {
uniqueIDs[matches[1]] = struct{}{}
}
for _, matches := range secretPat.FindAllStringSubmatch(dataStr, -1) {
uniqueSecrets[matches[1]] = struct{}{}
}
for id := range uniqueIDs {
for secret := range uniqueSecrets {
if id == secret {
// Avoid processing the same string for both id and secret.
continue
}
s1 := detectors.Result{
DetectorType: detectorspb.DetectorType_AnypointOAuth2,
Raw: []byte(secret),
RawV2: []byte(fmt.Sprintf("%s:%s", id, secret)),
}
if verify {
client := s.getClient()
isVerified, verificationErr := verifyMatch(ctx, client, id, secret)
s1.Verified = isVerified
s1.SetVerificationError(verificationErr)
}
results = append(results, s1)
if s1.Verified {
// Anypoint client IDs and secrets are mapped one-to-one, so if a pair
// is verified, we can remove that secret from the uniqueSecrets map.
delete(uniqueSecrets, secret)
break
}
}
}
return
}
func verifyMatch(ctx context.Context, client *http.Client, id, secret string) (bool, error) {
payload := strings.NewReader(`{"grant_type":"client_credentials","client_id":"` + id + `","client_secret":"` + secret + `"}`)
req, err := http.NewRequestWithContext(ctx, http.MethodPost, verificationUrl, payload)
if err != nil {
return false, err
}
req.Header.Add("Content-Type", "application/json")
res, err := client.Do(req)
if err != nil {
return false, err
}
defer func() {
_, _ = io.Copy(io.Discard, res.Body)
_ = res.Body.Close()
}()
switch res.StatusCode {
// The endpoint responds with status 200 for valid Organization credentials and 422 for Client credentials.
case http.StatusOK, http.StatusUnprocessableEntity:
return true, nil
case http.StatusUnauthorized:
return false, nil
default:
return false, fmt.Errorf("unexpected status code: %d", res.StatusCode)
}
}
func (s Scanner) Type() detectorspb.DetectorType {
return detectorspb.DetectorType_AnypointOAuth2
}
func (s Scanner) Description() string {
return "Anypoint is a unified platform that allows organizations to build and manage APIs and integrations. Anypoint credentials can be used to access and manipulate these integrations and API data."
}
@@ -0,0 +1,133 @@
//go:build detectors
// +build detectors
package anypointoauth2
import (
"context"
"fmt"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
)
func TestAnypointOAuth2_FromChunk(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors6")
if err != nil {
t.Fatalf("could not get test secrets from GCP: %s", err)
}
clientID := testSecrets.MustGetField("ANYPOINT_CLIENT_ID")
clientSecret := testSecrets.MustGetField("ANYPOINT_CLIENT_SECRET")
inactiveSecret := testSecrets.MustGetField("ANYPOINT_INACTIVE_SECRET")
type args struct {
ctx context.Context
data []byte
verify bool
}
tests := []struct {
name string
s Scanner
args args
want []detectors.Result
wantErr bool
}{
{
name: "found, verified",
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf("You can find an anypoint secret %s within anypoint organization id %s", clientSecret, clientID)),
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_AnypointOAuth2,
Verified: true,
},
},
wantErr: false,
},
{
name: "found, unverified",
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf("You can find an anypoint secret %s within anypoint organization id %s but not valid", inactiveSecret, clientID)), // the secret would satisfy the regex but not pass validation
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_AnypointOAuth2,
Verified: false,
},
},
wantErr: false,
},
{
name: "not found",
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte("You cannot find the secret within"),
verify: true,
},
want: nil,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := Scanner{}
got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
if (err != nil) != tt.wantErr {
t.Errorf("Anypoint.FromData() error = %v, wantErr %v", err, tt.wantErr)
return
}
for i := range got {
if len(got[i].Raw) == 0 {
t.Fatalf("no raw secret present: \n %+v", got[i])
}
gotErr := ""
if got[i].VerificationError() != nil {
gotErr = got[i].VerificationError().Error()
}
wantErr := ""
if tt.want[i].VerificationError() != nil {
wantErr = tt.want[i].VerificationError().Error()
}
if gotErr != wantErr {
t.Fatalf("wantVerificationError = %v, verification error = %v", tt.want[i].VerificationError(), got[i].VerificationError())
}
}
ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "RawV2", "verificationError", "primarySecret")
if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
t.Errorf("AnypointOAuth2.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
}
})
}
}
func BenchmarkFromData(benchmark *testing.B) {
ctx := context.Background()
s := Scanner{}
for name, data := range detectors.MustGetBenchmarkData() {
benchmark.Run(name, func(b *testing.B) {
b.ResetTimer()
for n := 0; n < b.N; n++ {
_, err := s.FromData(ctx, false, data)
if err != nil {
b.Fatal(err)
}
}
})
}
}
@@ -0,0 +1,114 @@
package anypointoauth2
import (
"context"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
)
func TestAnypoint_Pattern(t *testing.T) {
d := Scanner{}
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
tests := []struct {
name string
input string
want []string
}{
{
name: "valid pattern",
input: `
# Anypoint Secret Configuration File
# Organization details
ORG_NAME=my_organization
ORG_ID=abcd1234-ef56-gh78-ij90-klmn1234opqr
# OAuth tokens
CLIENT_ID=e3cd10a87f53b2dfa4b5fd606e7d9eca
CLIENT_SECRET=ACE9d7E606Df5B4AFD2B35f78A01DC3E
# API keys
SECRET_KEY=1a2b3c4d-5e6f-7g8h-9i0j-k1l2m3n4o5p6
# Endpoints
SERVICE_URL=https://api.example.com/v1/resource
`,
want: []string{"e3cd10a87f53b2dfa4b5fd606e7d9eca:ACE9d7E606Df5B4AFD2B35f78A01DC3E"},
},
{
name: "valid pattern - xml",
input: `
<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
<scope>GLOBAL</scope>
<id>{anypoint id 17c55fba5c93de5646b10507c36fbc23}</id>
<secret>{AQAAABAAA 8E6Ef8F8d5De05d8BF1491e1ecC37b31}</secret>
<description>configuration for production</description>
<creationDate>2023-05-18T14:32:10Z</creationDate>
<owner>jenkins-admin</owner>
</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
`,
want: []string{"17c55fba5c93de5646b10507c36fbc23:8E6Ef8F8d5De05d8BF1491e1ecC37b31"},
},
{
name: "invalid pattern",
input: `
# Anypoint Secret Configuration File
# Organization details
ORG_NAME=my_organization
ORG_ID=abcd1234-ef56-gh78-ij90-klmn1234opqr
# OAuth tokens
CLIENT_ID=k4lzc5ty98tnfu3a11y8gnv5vb1281as
CLIENT_SECRET=ACE9d7E606Df5B4AFD2B35f78A01DC3E
# API keys
SECRET_KEY=1a2b3c4d-5e6f-7g8h-9i0j-k1l2m3n4o5p6
# Endpoints
SERVICE_URL=https://api.example.com/v1/resource
`,
want: nil,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
if len(matchedDetectors) == 0 {
t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
return
}
results, err := d.FromData(context.Background(), false, []byte(test.input))
require.NoError(t, err)
if len(results) != len(test.want) {
t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
return
}
actual := make(map[string]struct{}, len(results))
for _, r := range results {
if len(r.RawV2) > 0 {
actual[string(r.RawV2)] = struct{}{}
} else {
actual[string(r.Raw)] = struct{}{}
}
}
expected := make(map[string]struct{}, len(test.want))
for _, v := range test.want {
expected[v] = struct{}{}
}
if diff := cmp.Diff(expected, actual); diff != "" {
t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
}
})
}
}
+57 -52
View File
@@ -2,47 +2,15 @@ package apacta
import (
"context"
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
)
var (
validPattern = "abcd1234-ef56-gh78-ij90-klmn1234opqr"
complexPattern = `
func main() {
url := "https://api.example.com/v1/resource"
// Create a new request with the secret as a header
req, err := http.NewRequest("POST", url, bytes.NewBuffer([]byte("{}")))
if err != nil {
fmt.Println("Error creating request:", err)
return
}
apactaSecret := "Bearer abcd1234-ef56-gh78-ij90-klmn1234opqr"
req.Header.Set("Authorization", apactaSecret)
// Perform the request
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
// Check response status
if resp.StatusCode == http.StatusOK {
fmt.Println("Request successful!")
} else {
fmt.Println("Request failed with status:", resp.Status)
}
}
`
invalidPattern = "abcD$1234-ef56-gH78-ij90-klmn1234opqr"
)
func TestApacta_Pattern(t *testing.T) {
d := Scanner{}
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
@@ -53,19 +21,62 @@ func TestApacta_Pattern(t *testing.T) {
want []string
}{
{
name: "valid pattern",
input: fmt.Sprintf("apacta credentials: %s", validPattern),
want: []string{"abcd1234-ef56-gh78-ij90-klmn1234opqr"},
name: "valid pattern",
input: `
func main() {
// Create a new request with the secret as a header
req, err := http.NewRequest("POST", "https://api.example.com/v1/resource", bytes.NewBuffer([]byte("{}")))
if err != nil {
fmt.Println("Error creating request:", err)
return
}
apactaSecret := "Bearer abcd1234-ef56-gh78-ij90-klmn1234opqr"
req.Header.Set("Authorization", apactaSecret)
// Perform the request
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
}
`,
want: []string{"abcd1234-ef56-gh78-ij90-klmn1234opqr"},
},
{
name: "valid pattern - complex",
input: complexPattern,
want: []string{"abcd1234-ef56-gh78-ij90-klmn1234opqr"},
name: "valid pattern - xml",
input: `
<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
<scope>GLOBAL</scope>
<id>{apacta}</id>
<secret>{AQAAABAAA w8-p59rc70q0unyupknadu5sr8bf5us04mpt}</secret>
<description>configuration for production</description>
<creationDate>2023-05-18T14:32:10Z</creationDate>
<owner>jenkins-admin</owner>
</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
`,
want: []string{"w8-p59rc70q0unyupknadu5sr8bf5us04mpt"},
},
{
name: "invalid pattern",
input: fmt.Sprintf("apacta credentials: %s", invalidPattern),
want: nil,
name: "invalid pattern",
input: `
func main() {
// Create a new request with the secret as a header
req, err := http.NewRequest("POST", "https://api.example.com/v1/resource", bytes.NewBuffer([]byte("{}")))
if err != nil {
fmt.Println("Error creating request:", err)
return
}
apactaSecret := "Bearer abcD$1234-ef56-gH78-ij90-klmn1234opqr"
req.Header.Set("Authorization", apactaSecret)
// Perform the request
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
}
`,
want: nil,
},
}
@@ -73,22 +84,15 @@ func TestApacta_Pattern(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
if len(matchedDetectors) == 0 {
t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
return
}
results, err := d.FromData(context.Background(), false, []byte(test.input))
if err != nil {
t.Errorf("error = %v", err)
return
}
require.NoError(t, err)
if len(results) != len(test.want) {
if len(results) == 0 {
t.Errorf("did not receive result")
} else {
t.Errorf("expected %d results, only received %d", len(test.want), len(results))
}
t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
return
}
@@ -100,6 +104,7 @@ func TestApacta_Pattern(t *testing.T) {
actual[string(r.Raw)] = struct{}{}
}
}
expected := make(map[string]struct{}, len(test.want))
for _, v := range test.want {
expected[v] = struct{}{}
+37 -32
View File
@@ -2,27 +2,15 @@ package api2cart
import (
"context"
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
)
var (
validPattern = "0123456789abcdef0123456789abcdef"
complexPattern = `
To integrate with API2Cart, ensure you have the following credentials in your configuration file. Your API2CART key is 1234567890abcdef1234567890abcdef, which you will need to access the API securely.
The following endpoints are available for your use:
- Get Products: https://api.api2cart.com/v1.0/products/get
- Add Product: https://api.api2cart.com/v1.0/products/add
`
invalidPattern = "0123456789Gbcde^0123456789abcdef"
)
func TestApi2Cart_Pattern(t *testing.T) {
d := Scanner{}
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
@@ -33,19 +21,42 @@ func TestApi2Cart_Pattern(t *testing.T) {
want []string
}{
{
name: "valid pattern",
input: fmt.Sprintf("api2cart credentials: %s", validPattern),
want: []string{"0123456789abcdef0123456789abcdef"},
name: "valid pattern",
input: `
To integrate with API2Cart, ensure you have the following credentials in your configuration file.
Your API2CART key is 2afddb813193eb9d3b5bd99bf5d834cd, which you will need to access the API securely.
The following endpoints are available for your use:
- Get Products: https://api.api2cart.com/v1.0/products/get
- Add Product: https://api.api2cart.com/v1.0/products/add
`,
want: []string{"2afddb813193eb9d3b5bd99bf5d834cd"},
},
{
name: "valid pattern -complex",
input: complexPattern,
want: []string{"1234567890abcdef1234567890abcdef"},
name: "valid pattern - xml",
input: `
<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
<scope>GLOBAL</scope>
<id>{api2cart}</id>
<secret>{AQAAABAAA b36c17e9dc0dba67480e864cf69879c3}</secret>
<description>configuration for production</description>
<creationDate>2023-05-18T14:32:10Z</creationDate>
<owner>jenkins-admin</owner>
</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
`,
want: []string{"b36c17e9dc0dba67480e864cf69879c3"},
},
{
name: "invalid pattern",
input: fmt.Sprintf("api2cart credentials: %s", invalidPattern),
want: nil,
name: "invalid pattern",
input: `
To integrate with API2Cart, ensure you have the following credentials in your configuration file.
Your API2CART key is 68d746609J4240840734c22836725d76, which you will need to access the API securely.
The following endpoints are available for your use:
- Get Products: https://api.api2cart.com/v1.0/products/get
- Add Product: https://api.api2cart.com/v1.0/products/add
`,
want: nil,
},
}
@@ -53,22 +64,15 @@ func TestApi2Cart_Pattern(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
if len(matchedDetectors) == 0 {
t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
return
}
results, err := d.FromData(context.Background(), false, []byte(test.input))
if err != nil {
t.Errorf("error = %v", err)
return
}
require.NoError(t, err)
if len(results) != len(test.want) {
if len(results) == 0 {
t.Errorf("did not receive result")
} else {
t.Errorf("expected %d results, only received %d", len(test.want), len(results))
}
t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
return
}
@@ -80,6 +84,7 @@ func TestApi2Cart_Pattern(t *testing.T) {
actual[string(r.Raw)] = struct{}{}
}
}
expected := make(map[string]struct{}, len(test.want))
for _, v := range test.want {
expected[v] = struct{}{}
+35 -24
View File
@@ -2,22 +2,15 @@ package apideck
import (
"context"
"fmt"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
)
var (
validKeyPattern = "sk_live_abc123def456ghi789jklmno123456pqrstuvwxyz-1234567890123456789012345678901234567890AAui09pU8as"
validIdPattern = "apideck_id: 1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p7q8r9s0t"
invalidPattern = strings.ReplaceAll(validKeyPattern, "sk_live", "sk_inactive") + strings.ReplaceAll(validIdPattern, "1", "$")
)
func TestApiDeck_Pattern(t *testing.T) {
d := Scanner{}
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
@@ -28,16 +21,40 @@ func TestApiDeck_Pattern(t *testing.T) {
want []string
}{
{
name: "valid pattern",
input: fmt.Sprintf("credentials: %s \n %s", validKeyPattern, validIdPattern),
name: "valid pattern",
input: `
[INFO] Sending request to the apideck API
[DEBUG] Using Key=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
[DEBUG] Using apideck ID=VfKE9Zh2ZatnqmrloqDu3PCnkNBR6Io4TlSbsG1P
[INFO] Response received: 200 OK
`,
want: []string{
validKeyPattern + "1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p7q8r9s0t",
"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",
},
},
{
name: "invalid pattern",
input: fmt.Sprintf("credentials: %s", invalidPattern),
want: nil,
name: "valid pattern - xml",
input: `
<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
<scope>GLOBAL</scope>
<id>{apideck id J6rYP2lzThxp9JeGg74TDgAXvfQsvzonsHpYHDsG}</id>
<secret>{apideck AQAAABAAA sk_live_R5S2B88smT6QfTsUc3o3DedI2hbbcnZwvQKjyudQ41V0T38L8qUDPUTlBDcVE2NwRp1PowPYqnmAHlZ-W1Yr7AWGvpCvT}</secret>
<description>configuration for production</description>
<creationDate>2023-05-18T14:32:10Z</creationDate>
<owner>jenkins-admin</owner>
</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
`,
want: []string{"sk_live_R5S2B88smT6QfTsUc3o3DedI2hbbcnZwvQKjyudQ41V0T38L8qUDPUTlBDcVE2NwRp1PowPYqnmAHlZ-W1Yr7AWGvpCvTJ6rYP2lzThxp9JeGg74TDgAXvfQsvzonsHpYHDsG"},
},
{
name: "invalid pattern",
input: `
[INFO] Sending request to the apideck API
[DEBUG] Using Key=sk_live_GKE08ADdkDV1DQ4vDfaW4ejDHy-TkotfxDmHvQMLX0HRvhtfPwku6olGvsG2vXBg869A0hsOPHHOw48SAF2GO7jBMs6Rt
[DEBUG] Using apideck ID=VfKE9Zh2ZatnqmrloqDu3PC_kNBR6Io4TlSbsG1P
[ERROR] Response received: 401 UnAuthorized
`,
want: nil,
},
}
@@ -45,22 +62,15 @@ func TestApiDeck_Pattern(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
if len(matchedDetectors) == 0 {
t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
return
}
results, err := d.FromData(context.Background(), false, []byte(test.input))
if err != nil {
t.Errorf("error = %v", err)
return
}
require.NoError(t, err)
if len(results) != len(test.want) {
if len(results) == 0 {
t.Errorf("did not receive result")
} else {
t.Errorf("expected %d results, only received %d", len(test.want), len(results))
}
t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
return
}
@@ -72,6 +82,7 @@ func TestApiDeck_Pattern(t *testing.T) {
actual[string(r.Raw)] = struct{}{}
}
}
expected := make(map[string]struct{}, len(test.want))
for _, v := range test.want {
expected[v] = struct{}{}
+33 -24
View File
@@ -2,22 +2,15 @@ package apiflash
import (
"context"
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
)
var (
validPattern = `
apiflash_key: a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
`
invalidPattern = "0123456789Gbcde^0123456789abcdef"
)
func TestApiFlash_Pattern(t *testing.T) {
d := Scanner{}
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
@@ -28,14 +21,36 @@ func TestApiFlash_Pattern(t *testing.T) {
want []string
}{
{
name: "valid pattern",
input: fmt.Sprintf("apiflash credentials: %s", validPattern),
want: []string{"a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"},
name: "valid pattern",
input: `
[INFO] Sending request to the apiflash API
[DEBUG] Using Key=grevetn5owrs1ybhxtcen0ibvg2mi85x
[INFO] Response received: 200 OK
`,
want: []string{"grevetn5owrs1ybhxtcen0ibvg2mi85x"},
},
{
name: "invalid pattern",
input: fmt.Sprintf("apiflash credentials: %s", invalidPattern),
want: nil,
name: "valid pattern - xml",
input: `
<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
<scope>GLOBAL</scope>
<id>{apiflash}</id>
<secret>{AQAAABAAA axlzvcf9m7jyyts833f9gmtcpqe5b26o}</secret>
<description>configuration for production</description>
<creationDate>2023-05-18T14:32:10Z</creationDate>
<owner>jenkins-admin</owner>
</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
`,
want: []string{"axlzvcf9m7jyyts833f9gmtcpqe5b26o"},
},
{
name: "invalid pattern",
input: `
[INFO] Sending request to the apiflash API
[DEBUG] Using Key=grevetn5owRs1ybhxtcen0ibvg2mi85x
[ERROR] Response received: 401 UnAuthorized
`,
want: nil,
},
}
@@ -43,22 +58,15 @@ func TestApiFlash_Pattern(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
if len(matchedDetectors) == 0 {
t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
return
}
results, err := d.FromData(context.Background(), false, []byte(test.input))
if err != nil {
t.Errorf("error = %v", err)
return
}
require.NoError(t, err)
if len(results) != len(test.want) {
if len(results) == 0 {
t.Errorf("did not receive result")
} else {
t.Errorf("expected %d results, only received %d", len(test.want), len(results))
}
t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
return
}
@@ -70,6 +78,7 @@ func TestApiFlash_Pattern(t *testing.T) {
actual[string(r.Raw)] = struct{}{}
}
}
expected := make(map[string]struct{}, len(test.want))
for _, v := range test.want {
expected[v] = struct{}{}
+33 -22
View File
@@ -2,20 +2,15 @@ package apifonica
import (
"context"
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
)
var (
validPattern = "abc123def45-gh67-ij89-kl01-mn234op56789"
invalidPattern = "0123456789Gbcde^0123456789abcdef"
)
func TestApiFonica_Pattern(t *testing.T) {
d := Scanner{}
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
@@ -26,14 +21,36 @@ func TestApiFonica_Pattern(t *testing.T) {
want []string
}{
{
name: "valid pattern",
input: fmt.Sprintf("apifonica credentials: %s", validPattern),
want: []string{"abc123def45-gh67-ij89-kl01-mn234op56789"},
name: "valid pattern",
input: `
[INFO] Sending request to the apifonica API
[DEBUG] Using Key=4rv0hdx5188-3q48-2luk-e8v5-dyuuf8l44ib7
[INFO] Response received: 200 OK
`,
want: []string{"4rv0hdx5188-3q48-2luk-e8v5-dyuuf8l44ib7"},
},
{
name: "invalid pattern",
input: fmt.Sprintf("apifonica credentials: %s", invalidPattern),
want: nil,
name: "valid pattern - xml",
input: `
<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
<scope>GLOBAL</scope>
<id>{apifonica}</id>
<secret>{AQAAABAAA fvzlzj17xzz-lwon-842u-46bs-5spcl2g7u9eb}</secret>
<description>configuration for production</description>
<creationDate>2023-05-18T14:32:10Z</creationDate>
<owner>jenkins-admin</owner>
</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
`,
want: []string{"fvzlzj17xzz-lwon-842u-46bs-5spcl2g7u9eb"},
},
{
name: "invalid pattern",
input: `
[INFO] Sending request to the apifonica API
[DEBUG] Using Key=4rv0hdx51889-3q48-2luk-e8wv5-dyuuf8l44ib7
[ERROR] Response received: 401 UnAuthorized
`,
want: nil,
},
}
@@ -41,22 +58,15 @@ func TestApiFonica_Pattern(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
if len(matchedDetectors) == 0 {
t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
return
}
results, err := d.FromData(context.Background(), false, []byte(test.input))
if err != nil {
t.Errorf("error = %v", err)
return
}
require.NoError(t, err)
if len(results) != len(test.want) {
if len(results) == 0 {
t.Errorf("did not receive result")
} else {
t.Errorf("expected %d results, only received %d", len(test.want), len(results))
}
t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
return
}
@@ -68,6 +78,7 @@ func TestApiFonica_Pattern(t *testing.T) {
actual[string(r.Raw)] = struct{}{}
}
}
expected := make(map[string]struct{}, len(test.want))
for _, v := range test.want {
expected[v] = struct{}{}
+33 -21
View File
@@ -5,16 +5,12 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
)
var (
validPattern = "apify_api_ab12CD34ef56GH78ij90KL12mn34OP56qr78"
invalidPattern = "apify_api_ab12C#34ef56GH7$ij90KL12mn34OP56qr7)"
)
func TestApiFy_Pattern(t *testing.T) {
d := Scanner{}
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
@@ -25,14 +21,36 @@ func TestApiFy_Pattern(t *testing.T) {
want []string
}{
{
name: "valid pattern",
input: validPattern,
want: []string{validPattern},
name: "valid pattern",
input: `
[INFO] Sending request to the API
[DEBUG] Using Key=apify_api_dXB1vLsglgTexUYm3JTAx2BHTjVuDBbvPl8R
[INFO] Response received: 200 OK
`,
want: []string{"apify_api_dXB1vLsglgTexUYm3JTAx2BHTjVuDBbvPl8R"},
},
{
name: "invalid pattern",
input: invalidPattern,
want: nil,
name: "valid pattern - xml",
input: `
<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
<scope>GLOBAL</scope>
<id>{user-id}</id>
<secret>{AQAAABAAA apify_api_RpTLEX9U18xfGl90wDaT2V9R-YX0TlMpxIzi}</secret>
<description>configuration for production</description>
<creationDate>2023-05-18T14:32:10Z</creationDate>
<owner>jenkins-admin</owner>
</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
`,
want: []string{"apify_api_RpTLEX9U18xfGl90wDaT2V9R-YX0TlMpxIzi"},
},
{
name: "invalid pattern",
input: `
[INFO] Sending request to the API
[DEBUG] Using Key=apify_api_dXB1vLPglgTex_UYm3JTAx2BHTjVuDBbvPl8R
[INFO] Response received: 200 OK
`,
want: nil,
},
}
@@ -40,22 +58,15 @@ func TestApiFy_Pattern(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
if len(matchedDetectors) == 0 {
t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
return
}
results, err := d.FromData(context.Background(), false, []byte(test.input))
if err != nil {
t.Errorf("error = %v", err)
return
}
require.NoError(t, err)
if len(results) != len(test.want) {
if len(results) == 0 {
t.Errorf("did not receive result")
} else {
t.Errorf("expected %d results, only received %d", len(test.want), len(results))
}
t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
return
}
@@ -67,6 +78,7 @@ func TestApiFy_Pattern(t *testing.T) {
actual[string(r.Raw)] = struct{}{}
}
}
expected := make(map[string]struct{}, len(test.want))
for _, v := range test.want {
expected[v] = struct{}{}
+33 -21
View File
@@ -5,16 +5,12 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
)
var (
validPattern = "apilayerkey: abcdef1234567890abcdef1234567890"
invalidPattern = "apilayerkey: abcdef123$5678*0abcde()1234567890)"
)
func TestApiLayer_Pattern(t *testing.T) {
d := Scanner{}
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
@@ -25,14 +21,36 @@ func TestApiLayer_Pattern(t *testing.T) {
want []string
}{
{
name: "valid pattern",
input: validPattern,
want: []string{"abcdef1234567890abcdef1234567890"},
name: "valid pattern",
input: `
[INFO] Sending request to the apilayer API
[DEBUG] Using Key=qnHT110fihCn49wOm5b2h3ACTRmksbg0
[INFO] Response received: 200 OK
`,
want: []string{"qnHT110fihCn49wOm5b2h3ACTRmksbg0"},
},
{
name: "invalid pattern",
input: invalidPattern,
want: nil,
name: "valid pattern - xml",
input: `
<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
<scope>GLOBAL</scope>
<id>{apilayer}</id>
<secret>{AQAAABAAA HHTi3DYZIqt57j5WVHvXHHboXpCnm6CW}</secret>
<description>configuration for production</description>
<creationDate>2023-05-18T14:32:10Z</creationDate>
<owner>jenkins-admin</owner>
</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
`,
want: []string{"HHTi3DYZIqt57j5WVHvXHHboXpCnm6CW"},
},
{
name: "invalid pattern",
input: `
[INFO] Sending request to the apilayer API
[DEBUG] Using Key=qnHT110fiha-Cn49wOm5b2h3ACTRmksbg0
[INFO] Response received: 200 OK
`,
want: nil,
},
}
@@ -40,22 +58,15 @@ func TestApiLayer_Pattern(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
if len(matchedDetectors) == 0 {
t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
return
}
results, err := d.FromData(context.Background(), false, []byte(test.input))
if err != nil {
t.Errorf("error = %v", err)
return
}
require.NoError(t, err)
if len(results) != len(test.want) {
if len(results) == 0 {
t.Errorf("did not receive result")
} else {
t.Errorf("expected %d results, only received %d", len(test.want), len(results))
}
t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
return
}
@@ -67,6 +78,7 @@ func TestApiLayer_Pattern(t *testing.T) {
actual[string(r.Raw)] = struct{}{}
}
}
expected := make(map[string]struct{}, len(test.want))
for _, v := range test.want {
expected[v] = struct{}{}
+37 -27
View File
@@ -5,22 +5,12 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
)
var (
validPattern = `
func validateApiMatic() bool {
apiMaticKey := "rc6iLoUEFGGAWNLsuBJnmsh4tZB-oCxcDUmc45HIPcuiQvfUEuqo8wb9YrUd2LyB"
return isActive(apiMaticKey, apiMaticPass)
}
`
invalidPattern = "apiMaticKey: rc6iL$UEFGGAWNLsuBJnmsh4tZB)oCxcDUmc45HIPcuiQvfUEuqo8wb9YrUd2LyB"
)
func TestApiMatic_Pattern(t *testing.T) {
d := Scanner{}
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
@@ -31,14 +21,40 @@ func TestApiMatic_Pattern(t *testing.T) {
want []string
}{
{
name: "valid pattern",
input: validPattern,
want: []string{"rc6iLoUEFGGAWNLsuBJnmsh4tZB-oCxcDUmc45HIPcuiQvfUEuqo8wb9YrUd2LyB"},
name: "valid pattern",
input: `
func validateApiMatic() bool {
apiMaticKey := "rc6iLoUEFGGAWNLsuBJnmsh4tZB-oCxcDUmc45HIPcuiQvfUEuqo8wb9YrUd2LyB"
// isActive check if the key is active or not
return isActive(apiMaticKey)
}`,
want: []string{"rc6iLoUEFGGAWNLsuBJnmsh4tZB-oCxcDUmc45HIPcuiQvfUEuqo8wb9YrUd2LyB"},
},
{
name: "invalid pattern",
input: invalidPattern,
want: nil,
name: "valid pattern - xml",
input: `
<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
<scope>GLOBAL</scope>
<id>{apimatic}</id>
<secret>{AQAAABAAA 2eqQBh9HkE-5Mq5Ma_vOEvvyt-x9shcZ-T5B7hSY1C5xvTl7qLMwGL6QAoNYmMcF}</secret>
<description>configuration for production</description>
<creationDate>2023-05-18T14:32:10Z</creationDate>
<owner>jenkins-admin</owner>
</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
`,
want: []string{"2eqQBh9HkE-5Mq5Ma_vOEvvyt-x9shcZ-T5B7hSY1C5xvTl7qLMwGL6QAoNYmMcF"},
},
{
name: "invalid pattern",
input: `
func validateApiMatic() bool {
apiMaticKey := "rc6iLoUEFGGAWNLsuBJnmsh4tZB@oCxcDUmc45HIPcuiQvfUEuqo8wb9YrUd2LyB"
// isActive check if the key is active or not
return isActive(apiMaticKey)
}`,
want: nil,
},
}
@@ -46,22 +62,15 @@ func TestApiMatic_Pattern(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
if len(matchedDetectors) == 0 {
t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
return
}
results, err := d.FromData(context.Background(), false, []byte(test.input))
if err != nil {
t.Errorf("error = %v", err)
return
}
require.NoError(t, err)
if len(results) != len(test.want) {
if len(results) == 0 {
t.Errorf("did not receive result")
} else {
t.Errorf("expected %d results, only received %d", len(test.want), len(results))
}
t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
return
}
@@ -73,6 +82,7 @@ func TestApiMatic_Pattern(t *testing.T) {
actual[string(r.Raw)] = struct{}{}
}
}
expected := make(map[string]struct{}, len(test.want))
for _, v := range test.want {
expected[v] = struct{}{}
+37 -21
View File
@@ -5,16 +5,12 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
)
var (
validPattern = "apimetricskey: 5po8TFGawiYNCc1ct4ofWkBqzIfA6IeO"
invalidPattern = "apimetricskey: A1b2C3d4 E5f6G7h8I9 j0K1l2M3n4O5p6"
)
func TestApiMetrics_Pattern(t *testing.T) {
d := Scanner{}
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
@@ -25,14 +21,40 @@ func TestApiMetrics_Pattern(t *testing.T) {
want []string
}{
{
name: "valid pattern",
input: validPattern,
want: []string{"5po8TFGawiYNCc1ct4ofWkBqzIfA6IeO"},
name: "valid pattern",
input: `
func validateApiMetrics() bool {
apiMetrics := "5po8TFGawiYNCc1ct4ofWkBqzIfA6IeO"
// isActive check if the key is active or not
return isActive(apiMetrics)
}`,
want: []string{"5po8TFGawiYNCc1ct4ofWkBqzIfA6IeO"},
},
{
name: "invalid pattern",
input: invalidPattern,
want: nil,
name: "valid pattern - xml",
input: `
<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
<scope>GLOBAL</scope>
<id>{apimetrics}</id>
<secret>{AQAAABAAA XpLTBFZccOgbbtVht4OaZzsrgKdh42RX}</secret>
<description>configuration for production</description>
<creationDate>2023-05-18T14:32:10Z</creationDate>
<owner>jenkins-admin</owner>
</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
`,
want: []string{"XpLTBFZccOgbbtVht4OaZzsrgKdh42RX"},
},
{
name: "invalid pattern",
input: `
func validateApiMetrics() bool {
apiMetrics := "5po8TFGawiYNCc1c4ofWkBqzIfA6IeO"
// isActive check if the key is active or not
return isActive(apiMetrics)
}`,
want: nil,
},
}
@@ -40,22 +62,15 @@ func TestApiMetrics_Pattern(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
if len(matchedDetectors) == 0 {
t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
return
}
results, err := d.FromData(context.Background(), false, []byte(test.input))
if err != nil {
t.Errorf("error = %v", err)
return
}
require.NoError(t, err)
if len(results) != len(test.want) {
if len(results) == 0 {
t.Errorf("did not receive result")
} else {
t.Errorf("expected %d results, only received %d", len(test.want), len(results))
}
t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
return
}
@@ -67,6 +82,7 @@ func TestApiMetrics_Pattern(t *testing.T) {
actual[string(r.Raw)] = struct{}{}
}
}
expected := make(map[string]struct{}, len(test.want))
for _, v := range test.want {
expected[v] = struct{}{}
+37 -21
View File
@@ -5,16 +5,12 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
)
var (
validPattern = "apitemplatekey: a1B2c3D4e5F6g7H8i9J0k1L2m3N4o5P6q7R8s9T"
invalidPattern = "apitemplatekey: sk_0C2c3D4e5F6@7H8i9J$k1L2m3N4o5P6q7R8s9T"
)
func TestApiTemplate_Pattern(t *testing.T) {
d := Scanner{}
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
@@ -25,14 +21,40 @@ func TestApiTemplate_Pattern(t *testing.T) {
want []string
}{
{
name: "valid pattern",
input: validPattern,
want: []string{"a1B2c3D4e5F6g7H8i9J0k1L2m3N4o5P6q7R8s9T"},
name: "valid pattern",
input: `
func validateKey() bool {
apiTemplate := "EeOPHL7PyBlUk0qkJX72sDtdNL3WLdpxg1czllR"
// isActive check if the key is active or not
return isActive(apiTemplate)
}`,
want: []string{"EeOPHL7PyBlUk0qkJX72sDtdNL3WLdpxg1czllR"},
},
{
name: "invalid pattern",
input: invalidPattern,
want: nil,
name: "valid pattern - xml",
input: `
<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
<scope>GLOBAL</scope>
<id>{apitemplate}</id>
<secret>{AQAAABAAA oVqX8yfzlUtzudNnvlWKNI4pNKTKTwlaKmxlcX5}</secret>
<description>configuration for production</description>
<creationDate>2023-05-18T14:32:10Z</creationDate>
<owner>jenkins-admin</owner>
</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
`,
want: []string{"oVqX8yfzlUtzudNnvlWKNI4pNKTKTwlaKmxlcX5"},
},
{
name: "invalid pattern",
input: `
func validateKey() bool {
apiTemplate := "EeOPHL7PyBlUk0qkJAX72sDtdNL3WLdpxg1czllR"
// isActive check if the key is active or not
return isActive(apiTemplate)
}`,
want: nil,
},
}
@@ -40,22 +62,15 @@ func TestApiTemplate_Pattern(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
if len(matchedDetectors) == 0 {
t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
return
}
results, err := d.FromData(context.Background(), false, []byte(test.input))
if err != nil {
t.Errorf("error = %v", err)
return
}
require.NoError(t, err)
if len(results) != len(test.want) {
if len(results) == 0 {
t.Errorf("did not receive result")
} else {
t.Errorf("expected %d results, only received %d", len(test.want), len(results))
}
t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
return
}
@@ -67,6 +82,7 @@ func TestApiTemplate_Pattern(t *testing.T) {
actual[string(r.Raw)] = struct{}{}
}
}
expected := make(map[string]struct{}, len(test.want))
for _, v := range test.want {
expected[v] = struct{}{}
+49 -21
View File
@@ -5,16 +5,12 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
)
var (
validPattern = "apollo: A1b2C3d4E5f6G7h8I9j0K1"
invalidPattern = "apollo: A1b2C3d4-5f6G7h8I9j0K1@$"
)
func TestApollo_Pattern(t *testing.T) {
d := Scanner{}
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
@@ -25,14 +21,52 @@ func TestApollo_Pattern(t *testing.T) {
want []string
}{
{
name: "valid pattern",
input: validPattern,
want: []string{"A1b2C3d4E5f6G7h8I9j0K1"},
name: "valid pattern",
input: `
func validateApolloKey() bool {
apiKey := "897TJ1HevanW9Ye6nv6Ojj"
log.Println("Checking API key status...")
if !isActive(apiKey) {
log.Println("API key is inactive or invalid.")
return false
}
log.Println("API key is valid and active.")
return true
}`,
want: []string{"897TJ1HevanW9Ye6nv6Ojj"},
},
{
name: "invalid pattern",
input: invalidPattern,
want: nil,
name: "valid pattern - xml",
input: `
<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
<scope>GLOBAL</scope>
<id>{apollo}</id>
<secret>{AQAAABAAA S2wg2NMlgalg9AUsrXPd1O}</secret>
<description>configuration for production</description>
<creationDate>2023-05-18T14:32:10Z</creationDate>
<owner>jenkins-admin</owner>
</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
`,
want: []string{"S2wg2NMlgalg9AUsrXPd1O"},
},
{
name: "invalid pattern",
input: `
func validateApolloKey() bool {
apiKey := "897TJ1HevanW9Ye-nv6Ojj"
log.Println("Checking API key status...")
if !isActive(apiKey) {
log.Println("API key is inactive or invalid.")
return false
}
log.Println("API key is valid and active.")
return true
}`,
want: nil,
},
}
@@ -40,22 +74,15 @@ func TestApollo_Pattern(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
if len(matchedDetectors) == 0 {
t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
return
}
results, err := d.FromData(context.Background(), false, []byte(test.input))
if err != nil {
t.Errorf("error = %v", err)
return
}
require.NoError(t, err)
if len(results) != len(test.want) {
if len(results) == 0 {
t.Errorf("did not receive result")
} else {
t.Errorf("expected %d results, only received %d", len(test.want), len(results))
}
t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
return
}
@@ -67,6 +94,7 @@ func TestApollo_Pattern(t *testing.T) {
actual[string(r.Raw)] = struct{}{}
}
}
expected := make(map[string]struct{}, len(test.want))
for _, v := range test.want {
expected[v] = struct{}{}
+30 -13
View File
@@ -3,6 +3,7 @@ package appcues
import (
"context"
"fmt"
"io"
"net/http"
"strings"
@@ -13,7 +14,7 @@ import (
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
)
type Scanner struct{
type Scanner struct {
detectors.DefaultMultiPartCredentialProvider
}
@@ -60,18 +61,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
RawV2: []byte(resMatch + resUserMatch),
}
if verify {
req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://api.appcues.com/v2/accounts/%s/flows", resIdMatch), nil)
if err != nil {
continue
}
req.SetBasicAuth(resUserMatch, resMatch)
res, err := client.Do(req)
if err == nil {
defer res.Body.Close()
if res.StatusCode >= 200 && res.StatusCode < 300 {
s1.Verified = true
}
}
isVerified, err := verifyMatch(ctx, client, resUserMatch, resMatch, resIdMatch)
s1.Verified = isVerified
s1.SetVerificationError(err, resUserMatch, resMatch, resIdMatch)
}
results = append(results, s1)
@@ -82,6 +74,31 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
return results, nil
}
func verifyMatch(ctx context.Context, client *http.Client, resUserMatch, resMatch, resIdMatch string) (bool, error) {
// Reference: https://api.appcues.com/v2/docs?_gl=1#responses
req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("https://api.appcues.com/v2/accounts/%s/flows", resIdMatch), http.NoBody)
if err != nil {
return false, err
}
req.SetBasicAuth(resUserMatch, resMatch)
res, err := client.Do(req)
if err != nil {
return false, err
}
defer func() {
_, _ = io.Copy(io.Discard, res.Body)
_ = res.Body.Close()
}()
switch res.StatusCode {
case http.StatusOK:
return true, nil
case http.StatusUnauthorized, http.StatusForbidden, http.StatusBadRequest:
return false, nil
default:
return false, fmt.Errorf("unexpected status code: %d", res.StatusCode)
}
}
func (s Scanner) Type() detectorspb.DetectorType {
return detectorspb.DetectorType_Appcues
}
+38 -29
View File
@@ -5,24 +5,12 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
)
var (
validPattern = `
appcues abcdef12-3456-7890-1234-56789abcdef0
appcues abcdef123456789012345678901234567890123
appcues 12345
`
invalidPattern = `
appcues abcdef12#3456-7890-1234-56789abcdef0
appcues abcdef12345678901&345678901234567890123
appcues 123456
`
)
func TestAppCues_Pattern(t *testing.T) {
d := Scanner{}
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
@@ -33,14 +21,41 @@ func TestAppCues_Pattern(t *testing.T) {
want []string
}{
{
name: "valid pattern",
input: validPattern,
want: []string{"abcdef12-3456-7890-1234-56789abcdef0abcdef123456789012345678901234567890123"},
name: "valid pattern",
input: `
[INFO] Sending request to the appcues API
[DEBUG] Using appcues Key=5g5n4yazu-dpqp3g6qt3gn59wrxhqf2mqipm
[DEBUG] Using appcues User=truffle-security-lrv10a8l4u23xp5gkvg819
[INFO] Response received: 200 OK
[INFO] APPCUES_ID=57843
`,
want: []string{"5g5n4yazu-dpqp3g6qt3gn59wrxhqf2mqipmtruffle-security-lrv10a8l4u23xp5gkvg819"},
},
{
name: "invalid pattern",
input: invalidPattern,
want: nil,
name: "valid pattern - xml",
input: `
<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
<scope>GLOBAL</scope>
<id>{appcues 91712}</id>
<username>{appcues ubdcpht45hlfdywxv89ympnvtcnydl3uv-0umfu}</username>
<secret>{appcues AQAAABAAA w9hyyfghqirj8uwcmtv05-n4fppzl-in223u}</secret>
<description>configuration for production</description>
<creationDate>2023-05-18T14:32:10Z</creationDate>
<owner>jenkins-admin</owner>
</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
`,
want: []string{"w9hyyfghqirj8uwcmtv05-n4fppzl-in223uubdcpht45hlfdywxv89ympnvtcnydl3uv-0umfu"},
},
{
name: "invalid pattern",
input: `
[INFO] Sending request to the appcues API
[DEBUG] Using appcues Key=5g5n4yazu-dpqp3g6qt3gn59wrxhqf2mqipm
[DEBUG] Using appcues User=truffle_security-lrv10a8l4u23xp5gkvg819
[ERROR] Response received: 401 UnAuthorized
[INFO] ID=57843
`,
want: nil,
},
}
@@ -48,22 +63,15 @@ func TestAppCues_Pattern(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
if len(matchedDetectors) == 0 {
t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
return
}
results, err := d.FromData(context.Background(), false, []byte(test.input))
if err != nil {
t.Errorf("error = %v", err)
return
}
require.NoError(t, err)
if len(results) != len(test.want) {
if len(results) == 0 {
t.Errorf("did not receive result")
} else {
t.Errorf("expected %d results, only received %d", len(test.want), len(results))
}
t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
return
}
@@ -75,6 +83,7 @@ func TestAppCues_Pattern(t *testing.T) {
actual[string(r.Raw)] = struct{}{}
}
}
expected := make(map[string]struct{}, len(test.want))
for _, v := range test.want {
expected[v] = struct{}{}
+30 -12
View File
@@ -2,6 +2,8 @@ package appfollow
import (
"context"
"fmt"
"io"
"net/http"
"strings"
@@ -45,18 +47,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
}
if verify {
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.appfollow.io/api/v2/account/users", nil)
if err != nil {
continue
}
req.Header.Add("X-AppFollow-API-Token", resMatch)
res, err := client.Do(req)
if err == nil {
defer res.Body.Close()
if res.StatusCode >= 200 && res.StatusCode < 300 {
s1.Verified = true
}
}
isVerified, err := verifyMatch(ctx, client, resMatch)
s1.Verified = isVerified
s1.SetVerificationError(err, resMatch)
}
results = append(results, s1)
@@ -65,6 +58,31 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
return results, nil
}
func verifyMatch(ctx context.Context, client *http.Client, token string) (bool, error) {
// Reference: https://docs.api.appfollow.io/reference/users_list_api_v2_account_users_get-1
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.appfollow.io/api/v2/account/users", http.NoBody)
if err != nil {
return false, err
}
req.Header.Add("X-AppFollow-API-Token", token)
res, err := client.Do(req)
if err != nil {
return false, err
}
defer func() {
_, _ = io.Copy(io.Discard, res.Body)
_ = res.Body.Close()
}()
switch res.StatusCode {
case http.StatusOK, http.StatusPaymentRequired, http.StatusUnprocessableEntity:
return true, nil
case http.StatusUnauthorized:
return false, nil
default:
return false, fmt.Errorf("unexpected status code: %d", res.StatusCode)
}
}
func (s Scanner) Type() detectorspb.DetectorType {
return detectorspb.DetectorType_Appfollow
}
+43 -22
View File
@@ -2,20 +2,15 @@ package appfollow
import (
"context"
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
)
var (
validPattern = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.333HbjEo1oxVUFcASR0sQ8cMuIJRLcMd5H9iJWDbovCw6ESjNtuoEMQQGPN9aSoxponxrTPvn1.btADhgNetsaUBuwoyHo5ip0Jab6N6MEBnSaT6CHiO6z"
invalidPattern = "eyQ0eXAiOiJMM1QiLDJhbGciOiJIUXI1NiK7.abcdefgh1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcd1234efgh5678ijkl9012.qwerty12345-ASDFG67890_zxcvb_ABCDE"
)
func TestAppFollow_Pattern(t *testing.T) {
d := Scanner{}
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
@@ -26,14 +21,46 @@ func TestAppFollow_Pattern(t *testing.T) {
want []string
}{
{
name: "valid pattern",
input: fmt.Sprintf("appfollow credential: %s", validPattern),
want: []string{"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.333HbjEo1oxVUFcASR0sQ8cMuIJRLcMd5H9iJWDbovCw6ESjNtuoEMQQGPN9aSoxponxrTPvn1.btADhgNetsaUBuwoyHo5ip0Jab6N6MEBnSaT6CHiO6z"},
name: "valid pattern",
input: `
func validateAppFollowKey() bool {
key := "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.hdMLjiIayyb5cgbcVtjKywQwqeNKnsxZEhnJnX6wzhnblpmpjF4c2mbdmVVylTayE6M8ZE3h4V.fmnUM4cjvbe1JMFDuBSwWNEYQFHrD5AEm6p2Ir9w7K6"
// isActive check if the key is active or not
return isActive(key)
}`,
want: []string{"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.hdMLjiIayyb5cgbcVtjKywQwqeNKnsxZEhnJnX6wzhnblpmpjF4c2mbdmVVylTayE6M8ZE3h4V.fmnUM4cjvbe1JMFDuBSwWNEYQFHrD5AEm6p2Ir9w7K6"},
},
{
name: "invalid pattern",
input: fmt.Sprintf("appfollow credential: %s", invalidPattern),
want: nil,
name: "valid pattern - xml",
input: `
<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
<scope>GLOBAL</scope>
<id>{appfollow}</id>
<secret>{AQAAABAAA eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.YwK6gJ8sMVylaDNuXRiGFLRR1kgZaLF45EbJ0qHSRaW4CRtWaqWciTZZXxkk4a4wLh8f7cTTlb.wvTVCRC1RLCpd98q4WK3ef6M3TBrb08AkS9-jNOdA_r}</secret>
<description>configuration for production</description>
<creationDate>2023-05-18T14:32:10Z</creationDate>
<owner>jenkins-admin</owner>
</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
`,
want: []string{"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.YwK6gJ8sMVylaDNuXRiGFLRR1kgZaLF45EbJ0qHSRaW4CRtWaqWciTZZXxkk4a4wLh8f7cTTlb.wvTVCRC1RLCpd98q4WK3ef6M3TBrb08AkS9-jNOdA_r"},
},
{
name: "invalid pattern",
input: `
func validateAppFollowKey() bool {
apiKey := "eyJ0eXAiOiJKV1QiLCJhbGCiOiJIUzI1NiJ9.hdMLjiIayyb5cgbcVtjKywQwqeNKnsxZEhnJnX6wzhnblpmpjF4c2mbdVylTayE6M8ZE3h4V.fmnUM4cjvbe1JMFDuBSwWNEYQFHrDEm6p2Ir9w7K6"
log.Println("Checking API key status...")
if !isActive(apiKey) {
log.Println("API key is inactive or invalid.")
return false
}
log.Println("API key is valid and active.")
return true
}`,
want: nil,
},
}
@@ -41,22 +68,15 @@ func TestAppFollow_Pattern(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
if len(matchedDetectors) == 0 {
t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
return
}
results, err := d.FromData(context.Background(), false, []byte(test.input))
if err != nil {
t.Errorf("error = %v", err)
return
}
require.NoError(t, err)
if len(results) != len(test.want) {
if len(results) == 0 {
t.Errorf("did not receive result")
} else {
t.Errorf("expected %d results, only received %d", len(test.want), len(results))
}
t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
return
}
@@ -68,6 +88,7 @@ func TestAppFollow_Pattern(t *testing.T) {
actual[string(r.Raw)] = struct{}{}
}
}
expected := make(map[string]struct{}, len(test.want))
for _, v := range test.want {
expected[v] = struct{}{}
+35 -19
View File
@@ -2,11 +2,13 @@ package appointedd
import (
"context"
regexp "github.com/wasilibs/go-re2"
"fmt"
"io"
"net/http"
"strings"
regexp "github.com/wasilibs/go-re2"
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
@@ -44,24 +46,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
Raw: []byte(resMatch),
}
if verify {
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.appointedd.com/v1/availability/slots", nil)
if err != nil {
continue
}
req.Header.Add("X-API-KEY", resMatch)
res, err := client.Do(req)
if err == nil {
defer res.Body.Close()
bodyBytes, err := io.ReadAll(res.Body)
if err != nil {
continue
}
body := string(bodyBytes)
if strings.Contains(body, "total") {
s1.Verified = true
}
}
isVerified, err := verifyMatch(ctx, client, resMatch)
s1.Verified = isVerified
s1.SetVerificationError(err, resMatch)
}
results = append(results, s1)
@@ -70,6 +57,35 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
return results, nil
}
func verifyMatch(ctx context.Context, client *http.Client, secret string) (bool, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.appointedd.com/v1/availability/slots", http.NoBody)
if err != nil {
return false, err
}
req.Header.Add("X-API-KEY", secret)
res, err := client.Do(req)
if err != nil {
return false, err
}
defer func() {
_, _ = io.Copy(io.Discard, res.Body)
_ = res.Body.Close()
}()
switch res.StatusCode {
case http.StatusOK:
bodyBytes, err := io.ReadAll(res.Body)
if err != nil {
return false, err
}
return strings.Contains(string(bodyBytes), "total"), nil
case http.StatusUnauthorized, http.StatusForbidden:
return false, nil
default:
return false, fmt.Errorf("unexpected status code: %d", res.StatusCode)
}
}
func (s Scanner) Type() detectorspb.DetectorType {
return detectorspb.DetectorType_Appointedd
}
+49 -22
View File
@@ -2,20 +2,15 @@ package appointedd
import (
"context"
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
)
var (
validPattern = "Ci0a2bSpRyFcZyEXBEr9RHzf3xXllqO=XVoh+t0L0s8T2s3MFntfWhBlovqLaqEadtuJ9=Jy6yCOXmhbpEZPfY7Y"
invalidPattern = "Ci0a2bSpRyFcZyEXBEr9RHzf3xXllqO^XVoh+t0L0s8T2s3MFntfWhBlovqLaqEadtuJ9$Jy6yCOXmhbpEZPfY7Y"
)
func TestAppFollow_Pattern(t *testing.T) {
d := Scanner{}
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
@@ -26,14 +21,52 @@ func TestAppFollow_Pattern(t *testing.T) {
want []string
}{
{
name: "valid pattern",
input: fmt.Sprintf("appointedd credential: %s", validPattern),
want: []string{"Ci0a2bSpRyFcZyEXBEr9RHzf3xXllqO=XVoh+t0L0s8T2s3MFntfWhBlovqLaqEadtuJ9=Jy6yCOXmhbpEZPfY7Y"},
name: "valid pattern",
input: `
func validateAppointeddKey() bool {
appointeddKey := "Ci0a2bSpRyFcZyEXBEr9RHzf3xXllqO=XVoh+t0L0s8T2s3MFntfWhBlovqLaqEadtuJ9=Jy6yCOXmhbpEZPfY7Y"
log.Println("Checking API key status...")
if !isActive(appointeddKey) {
log.Println("API key is inactive or invalid.")
return false
}
log.Println("API key is valid and active.")
return true
}`,
want: []string{"Ci0a2bSpRyFcZyEXBEr9RHzf3xXllqO=XVoh+t0L0s8T2s3MFntfWhBlovqLaqEadtuJ9=Jy6yCOXmhbpEZPfY7Y"},
},
{
name: "invalid pattern",
input: fmt.Sprintf("appointedd credential: %s", invalidPattern),
want: nil,
name: "valid pattern - xml",
input: `
<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
<scope>GLOBAL</scope>
<id>{appointedd}</id>
<secret>{AQAAABAAA 2pRMKW=JrG9+xYmqlJMa4Omf9goqsSqsM3mIaqG8tG4lwnVrKIslbn=IpLIz7GTDEJUcQ0wlr6B+UjfvSY9XKXwu}</secret>
<description>configuration for production</description>
<creationDate>2023-05-18T14:32:10Z</creationDate>
<owner>jenkins-admin</owner>
</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
`,
want: []string{"2pRMKW=JrG9+xYmqlJMa4Omf9goqsSqsM3mIaqG8tG4lwnVrKIslbn=IpLIz7GTDEJUcQ0wlr6B+UjfvSY9XKXwu"},
},
{
name: "invalid pattern",
input: `
func validateAppointeddKey() bool {
appointeddKey := "Ci0a2bSpRyFcZyEXBEr9RHzf3xXllqO-XVoh+t0L0s8T2s3MFntfWhBlovqLaqEadtuJ9-Jy6yCOXmhbpEZPfY7Y"
log.Println("Checking API key status...")
if !isActive(appointeddKey) {
log.Println("API key is inactive or invalid.")
return false
}
log.Println("API key is valid and active.")
return true
}`,
want: nil,
},
}
@@ -41,22 +74,15 @@ func TestAppFollow_Pattern(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
if len(matchedDetectors) == 0 {
t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
return
}
results, err := d.FromData(context.Background(), false, []byte(test.input))
if err != nil {
t.Errorf("error = %v", err)
return
}
require.NoError(t, err)
if len(results) != len(test.want) {
if len(results) == 0 {
t.Errorf("did not receive result")
} else {
t.Errorf("expected %d results, only received %d", len(test.want), len(results))
}
t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
return
}
@@ -68,6 +94,7 @@ func TestAppFollow_Pattern(t *testing.T) {
actual[string(r.Raw)] = struct{}{}
}
}
expected := make(map[string]struct{}, len(test.want))
for _, v := range test.want {
expected[v] = struct{}{}
+49 -22
View File
@@ -2,20 +2,15 @@ package appoptics
import (
"context"
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
)
var (
validPattern = "IABJPR08RmvsGrebJhr1TUdo27-KtTn0mLCKkJJqj5lyba-otXPklygO9DK62o3QSPoIJ4E"
invalidPattern = "IABJPR08Rmvs.GrebJhr1TUdo27_KtTn0mLCKkJJqj5lyba&otXPklygO9DK62o3QSPoIJ4E"
)
func TestAppOptics_Pattern(t *testing.T) {
d := Scanner{}
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
@@ -26,14 +21,52 @@ func TestAppOptics_Pattern(t *testing.T) {
want []string
}{
{
name: "valid pattern",
input: fmt.Sprintf("appoptics credential: %s", validPattern),
want: []string{"IABJPR08RmvsGrebJhr1TUdo27-KtTn0mLCKkJJqj5lyba-otXPklygO9DK62o3QSPoIJ4E"},
name: "valid pattern",
input: `
func validateAppOpticsKey() bool {
appopticsKey := "Xwl4ViaAFDLrAmFX9g1blkUVC5dJj2he3a1tzkpJ4-PznQukQruRjqMFbEG73L92LJyBGMZ"
log.Println("Checking API key status...")
if !isActive(appopticsKey) {
log.Println("API key is inactive or invalid.")
return false
}
log.Println("API key is valid and active.")
return true
}`,
want: []string{"Xwl4ViaAFDLrAmFX9g1blkUVC5dJj2he3a1tzkpJ4-PznQukQruRjqMFbEG73L92LJyBGMZ"},
},
{
name: "invalid pattern",
input: fmt.Sprintf("appoptics credential: %s", invalidPattern),
want: nil,
name: "valid pattern - xml",
input: `
<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
<scope>GLOBAL</scope>
<id>{appoptics}</id>
<secret>{AQAAABAAA zxsb8yzT0RbIJ1TAalB87LOVUcT1b4uEgvT4tXCcSqv_gcmlrx5aQRleHPDFKePjpHFof5J}</secret>
<description>configuration for production</description>
<creationDate>2023-05-18T14:32:10Z</creationDate>
<owner>jenkins-admin</owner>
</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
`,
want: []string{"zxsb8yzT0RbIJ1TAalB87LOVUcT1b4uEgvT4tXCcSqv_gcmlrx5aQRleHPDFKePjpHFof5J"},
},
{
name: "invalid pattern",
input: `
func validateAppOpticsKey() bool {
appopticsKey := "Xwl4ViaAFDLrAmFX9g1blkUVC5dJj2h:3a1tzkpJ43PznQukQruRjqMFbEG73L92LJyBGMZ"
log.Println("Checking API key status...")
if !isActive(appopticsKey) {
log.Println("API key is inactive or invalid.")
return false
}
log.Println("API key is valid and active.")
return true
}`,
want: nil,
},
}
@@ -41,22 +74,15 @@ func TestAppOptics_Pattern(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
if len(matchedDetectors) == 0 {
t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
return
}
results, err := d.FromData(context.Background(), false, []byte(test.input))
if err != nil {
t.Errorf("error = %v", err)
return
}
require.NoError(t, err)
if len(results) != len(test.want) {
if len(results) == 0 {
t.Errorf("did not receive result")
} else {
t.Errorf("expected %d results, only received %d", len(test.want), len(results))
}
t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
return
}
@@ -68,6 +94,7 @@ func TestAppOptics_Pattern(t *testing.T) {
actual[string(r.Raw)] = struct{}{}
}
}
expected := make(map[string]struct{}, len(test.want))
for _, v := range test.want {
expected[v] = struct{}{}

Some files were not shown because too many files have changed in this diff Show More