Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1bbc35e5be |
@@ -6,7 +6,11 @@ labels: bug, needs triage
|
|||||||
assignees: trufflesecurity/product-eng
|
assignees: trufflesecurity/product-eng
|
||||||
---
|
---
|
||||||
|
|
||||||
Please review the [Community Note](https://github.com/trufflesecurity/trufflehog/blob/main/.github/community_note.md) before submitting
|
### Community Note
|
||||||
|
|
||||||
|
* Please vote on this issue by adding a 👍 [reaction](https://blog.github.com/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/) to the original issue to help the community and maintainers prioritize this request
|
||||||
|
* Please do not leave "+1" or other comments that do not add relevant new information or questions, they generate extra noise for issue followers and do not help prioritize the request
|
||||||
|
* If you are interested in working on this issue or have submitted a pull request, please leave a comment
|
||||||
|
|
||||||
### TruffleHog Version
|
### TruffleHog Version
|
||||||
<!--- Please run `trufflehog --version` to show the version. If you are not running the latest version, please upgrade because your issue may have already been fixed. --->
|
<!--- Please run `trufflehog --version` to show the version. If you are not running the latest version, please upgrade because your issue may have already been fixed. --->
|
||||||
|
|||||||
@@ -6,27 +6,33 @@ labels: enhancement, needs triage
|
|||||||
assignees: trufflesecurity/product-eng
|
assignees: trufflesecurity/product-eng
|
||||||
---
|
---
|
||||||
|
|
||||||
Please review the [Community Note](https://github.com/trufflesecurity/trufflehog/blob/main/.github/community_note.md) before submitting
|
### Community Note
|
||||||
|
|
||||||
|
* Please vote on this issue by adding a 👍 [reaction](https://blog.github.com/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/) to the original issue to help the community and maintainers prioritize this request
|
||||||
|
* Please do not leave "+1" or other comments that do not add relevant new information or questions, they generate extra noise for issue followers and do not help prioritize the request
|
||||||
|
* If you are interested in working on this issue or have submitted a pull request, please leave a comment
|
||||||
|
|
||||||
|
### Description
|
||||||
|
|
||||||
## Description
|
|
||||||
<!--- Please leave a helpful description of the feature request here. --->
|
<!--- Please leave a helpful description of the feature request here. --->
|
||||||
|
|
||||||
### Preferred Solution
|
## Problem to be Addressed
|
||||||
<!--- A clear and concise description of what you want to happen. What
|
<!--- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] --->
|
||||||
information may be required and what would be the preferred way to provide it?
|
|
||||||
|
## Description of the Preferred Solution
|
||||||
|
<!--- A clear and concise description of what you want to happen. What
|
||||||
|
information may be required and what would be the preferred way to provide it?
|
||||||
What should the output include? --->
|
What should the output include? --->
|
||||||
|
|
||||||
### Additional Context
|
## Additional Context
|
||||||
<!--- Add any other context or screenshots about the feature request here. --->
|
<!--- Add any other context or screenshots about the feature request here. --->
|
||||||
|
|
||||||
#### References
|
### References
|
||||||
|
|
||||||
<!---
|
<!---
|
||||||
Information about referencing Github Issues: https://help.github.com/articles/basic-writing-and-formatting-syntax/#referencing-issues-and-pull-requests
|
Information about referencing Github Issues: https://help.github.com/articles/basic-writing-and-formatting-syntax/#referencing-issues-and-pull-requests
|
||||||
|
|
||||||
Are there any other GitHub issues (open or closed) or pull requests that should be linked here? Vendor blog posts or documentation? For example:
|
Are there any other GitHub issues (open or closed) or pull requests that should be linked here? Vendor blog posts or documentation? For example:
|
||||||
|
|
||||||
* #0000
|
|
||||||
--->
|
--->
|
||||||
|
|
||||||
|
* #0000
|
||||||
|
|||||||
@@ -2,11 +2,3 @@
|
|||||||
Please create an issue to collect feedback prior to feature additions. Please also reference that issue in any PRs.
|
Please create an issue to collect feedback prior to feature additions. Please also reference that issue in any PRs.
|
||||||
If possible try to keep PRs scoped to one feature, and add tests for new features.
|
If possible try to keep PRs scoped to one feature, and add tests for new features.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
### Description:
|
|
||||||
Explain the purpose of the PR.
|
|
||||||
|
|
||||||
### Checklist:
|
|
||||||
* [ ] Tests passing (`make test-community`)?
|
|
||||||
* [ ] Lint passing (`make lint` this requires [golangci-lint](https://golangci-lint.run/usage/install/#local-installation))?
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
## Community Note
|
|
||||||
|
|
||||||
Please vote on this issue by adding a 👍 reaction to the original issue to help the community and maintainers prioritize this request.
|
|
||||||
|
|
||||||
Please do not leave "+1" or other comments that do not add relevant new information or questions, they generate extra noise for issue followers and do not help prioritize the request.
|
|
||||||
|
|
||||||
If you are interested in working on this issue or have submitted a pull request, please leave a comment.
|
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# To get started with Dependabot version updates, you'll need to specify which
|
||||||
|
# package ecosystems to update and where the package manifests are located.
|
||||||
|
# Please see the documentation for all configuration options:
|
||||||
|
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||||
|
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "gomod" # See documentation for possible values
|
||||||
|
directory: "/" # Location of package manifests
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
# Check for updates to GitHub Actions every weekday
|
||||||
|
interval: "weekly"
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
|
||||||
"extends": [
|
|
||||||
"config:base"
|
|
||||||
],
|
|
||||||
"prConcurrentLimit": 3,
|
|
||||||
"prHourlyLimit": 2
|
|
||||||
}
|
|
||||||
@@ -38,7 +38,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
name: detector test aggregation
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
schedule:
|
|
||||||
- cron: "0 8 * * *"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
test-detectors:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
actions: "read"
|
|
||||||
contents: "read"
|
|
||||||
id-token: "write"
|
|
||||||
steps:
|
|
||||||
- name: Install Go
|
|
||||||
uses: actions/setup-go@v4
|
|
||||||
with:
|
|
||||||
go-version: "1.21"
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- id: "auth"
|
|
||||||
uses: "google-github-actions/auth@v2"
|
|
||||||
with:
|
|
||||||
workload_identity_provider: "projects/811013774421/locations/global/workloadIdentityPools/github-pool/providers/github-provider"
|
|
||||||
service_account: "github-ci-external@trufflehog-testing.iam.gserviceaccount.com"
|
|
||||||
- name: Test
|
|
||||||
run: make test-integration
|
|
||||||
- name: Set up gotestsum
|
|
||||||
run: |
|
|
||||||
go install gotest.tools/gotestsum@latest
|
|
||||||
mkdir -p tmp/test-results
|
|
||||||
- name: Test
|
|
||||||
run: |
|
|
||||||
CGO_ENABLED=1 gotestsum --junitfile tmp/test-results/test.xml --raw-command -- go test -json -tags=detectors -timeout=15m $(go list ./... | grep pkg/detectors)
|
|
||||||
- name: Upload test results to BuildPulse for flaky test detection
|
|
||||||
if: ${{ !cancelled() }} # Run this step even when the tests fail. Skip if the workflow is cancelled.
|
|
||||||
uses: buildpulse/buildpulse-action@main
|
|
||||||
with:
|
|
||||||
account: 79229934
|
|
||||||
repository: 694446374
|
|
||||||
path: |
|
|
||||||
tmp/test-results/*.xml
|
|
||||||
key: ${{ secrets.BUILDPULSE_DETECTORS_ACCESS_KEY_ID }}
|
|
||||||
secret: ${{ secrets.BUILDPULSE_DETECTORS_SECRET_ACCESS_KEY }}
|
|
||||||
tags: detectors
|
|
||||||
@@ -4,6 +4,8 @@ on:
|
|||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- v*
|
- v*
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
@@ -15,10 +17,10 @@ jobs:
|
|||||||
name: lint
|
name: lint
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/setup-go@v4
|
- uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: '1.21'
|
go-version: '1.18'
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v3
|
uses: golangci/golangci-lint-action@v3
|
||||||
with:
|
with:
|
||||||
@@ -28,10 +30,13 @@ jobs:
|
|||||||
# working-directory: somedir
|
# working-directory: somedir
|
||||||
|
|
||||||
# Optional: golangci-lint command line arguments.
|
# Optional: golangci-lint command line arguments.
|
||||||
args: --enable bodyclose --enable exportloopref --timeout 10m
|
args: --enable bodyclose --timeout 10m
|
||||||
|
|
||||||
|
# Optional: show only new issues if it's a pull request. The default value is `false`.
|
||||||
|
only-new-issues: true
|
||||||
|
|
||||||
# Optional: if set to true then the action don't cache or restore ~/go/pkg.
|
# Optional: if set to true then the action don't cache or restore ~/go/pkg.
|
||||||
# skip-pkg-cache: true
|
# skip-pkg-cache: true
|
||||||
|
|
||||||
# Optional: if set to true then the action don't cache or restore ~/.cache/go-build.
|
# Optional: if set to true then the action don't cache or restore ~/.cache/go-build.
|
||||||
# skip-build-cache: true
|
# skip-build-cache: true
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
name: Performance Test
|
|
||||||
|
|
||||||
on: [pull_request]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
speed:
|
|
||||||
# skip if PR is from a fork.
|
|
||||||
# TODO: this could probabaly be refactored a bit so that it runs on forks
|
|
||||||
if: ${{ ! github.event.pull_request.head.repo.fork }}
|
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Install Go
|
|
||||||
uses: actions/setup-go@v4
|
|
||||||
with:
|
|
||||||
go-version: '1.21'
|
|
||||||
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
ref: ${{ github.head_ref }}
|
|
||||||
|
|
||||||
- name: Run Head
|
|
||||||
run: |
|
|
||||||
go build -o current .
|
|
||||||
repo_tmp=$(mktemp -d)
|
|
||||||
git clone https://github.com/trufflesecurity/trufflehog.git $repo_tmp
|
|
||||||
|
|
||||||
user_time_sum=0
|
|
||||||
|
|
||||||
for i in {1..5}
|
|
||||||
do
|
|
||||||
tmpfile=$(mktemp)
|
|
||||||
/usr/bin/time -o $tmpfile $GITHUB_WORKSPACE/current git "file://$repo_tmp" --no-verification --no-update > out.txt
|
|
||||||
cat $tmpfile
|
|
||||||
time_output=$(cat $tmpfile)
|
|
||||||
rm $tmpfile
|
|
||||||
user_time=$(echo $time_output | awk '{print $1}' | sed 's/user//')
|
|
||||||
|
|
||||||
# Add the user time to the sum
|
|
||||||
user_time_sum=$(echo "$user_time_sum + $user_time" | bc)
|
|
||||||
done
|
|
||||||
|
|
||||||
average_user_time=$(echo "scale=3; $user_time_sum / 5" | bc)
|
|
||||||
echo HEAD_TIME=$average_user_time >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Figure out previous tag
|
|
||||||
run: |
|
|
||||||
git fetch --tags
|
|
||||||
git tag -l --sort=-v:refname | head -n 1 > previous_tag.txt
|
|
||||||
echo PREVIOUS_TAG=$(cat previous_tag.txt) >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
ref: ${{ env.PREVIOUS_TAG }}
|
|
||||||
|
|
||||||
- name: Run Previous
|
|
||||||
run: |
|
|
||||||
go build -o previous .
|
|
||||||
repo_tmp=$(mktemp -d)
|
|
||||||
git clone https://github.com/trufflesecurity/trufflehog.git $repo_tmp
|
|
||||||
|
|
||||||
user_time_sum=0
|
|
||||||
|
|
||||||
for i in {1..5}
|
|
||||||
do
|
|
||||||
tmpfile=$(mktemp)
|
|
||||||
/usr/bin/time -o $tmpfile $GITHUB_WORKSPACE/previous git "file://$repo_tmp" --no-verification --no-update > out.txt
|
|
||||||
cat $tmpfile
|
|
||||||
time_output=$(cat $tmpfile)
|
|
||||||
rm $tmpfile
|
|
||||||
user_time=$(echo $time_output | awk '{print $1}' | sed 's/user//')
|
|
||||||
|
|
||||||
# Add the user time to the sum
|
|
||||||
user_time_sum=$(echo "$user_time_sum + $user_time" | bc)
|
|
||||||
done
|
|
||||||
|
|
||||||
average_user_time=$(echo "scale=3; $user_time_sum / 5" | bc)
|
|
||||||
echo PREVIOUS_TIME=$average_user_time >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Compare Results
|
|
||||||
run: |
|
|
||||||
echo "head ($GITHUB_SHA) avg time (n=5): $HEAD_TIME"
|
|
||||||
echo "$PREVIOUS_TAG avg time (n=5): $PREVIOUS_TIME"
|
|
||||||
if [ $(echo "$HEAD_TIME > $PREVIOUS_TIME * 1.1" | bc) -eq 1 ]
|
|
||||||
then
|
|
||||||
echo "HEAD run time is at least 10% slower than PREVIOUS run time"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
@@ -8,7 +8,6 @@ on:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
packages: write
|
packages: write
|
||||||
id-token: write
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
Release:
|
Release:
|
||||||
@@ -17,30 +16,28 @@ jobs:
|
|||||||
DOCKER_CLI_EXPERIMENTAL: "enabled"
|
DOCKER_CLI_EXPERIMENTAL: "enabled"
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v2
|
||||||
- name: Docker Login to DockerHub
|
- name: Docker Login to DockerHub
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
- name: Docker Login to GitHub Container Registry
|
- name: Docker Login to GitHub Container Registry
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: '1.21'
|
go-version: '1.18'
|
||||||
- name: Cosign install
|
|
||||||
uses: sigstore/cosign-installer@1fc5bd396d372bee37d608f955b336615edf79c8 # v3.2.0
|
|
||||||
- name: Run GoReleaser
|
- name: Run GoReleaser
|
||||||
uses: goreleaser/goreleaser-action@v5
|
uses: goreleaser/goreleaser-action@v4
|
||||||
with:
|
with:
|
||||||
distribution: goreleaser
|
distribution: goreleaser
|
||||||
version: latest
|
version: latest
|
||||||
|
|||||||
@@ -7,18 +7,18 @@ on:
|
|||||||
- v*
|
- v*
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: '1.21'
|
go-version: '1.18'
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
ref: ${{ github.head_ref }}
|
ref: ${{ github.head_ref }}
|
||||||
@@ -28,5 +28,4 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
path: ./
|
path: ./
|
||||||
base: ${{ github.event.repository.default_branch }}
|
base: ${{ github.event.repository.default_branch }}
|
||||||
head: HEAD
|
head: HEAD
|
||||||
extra_args: --only-verified
|
|
||||||
@@ -9,11 +9,14 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: '1.21'
|
go-version: '1.18'
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
ref: ${{ github.head_ref }}
|
||||||
- name: Smoke
|
- name: Smoke
|
||||||
run: |
|
run: |
|
||||||
set -e
|
set -e
|
||||||
|
|||||||
@@ -16,9 +16,9 @@ jobs:
|
|||||||
name: Run Snifftest
|
name: Run Snifftest
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/setup-go@v4
|
- uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: '1.21'
|
go-version: '1.18'
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
- name: Run Snifftest
|
- name: Run Snifftest
|
||||||
run: make snifftest
|
run: make snifftest
|
||||||
+34
-51
@@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
name: Test
|
name: Test
|
||||||
|
|
||||||
on:
|
on:
|
||||||
@@ -10,60 +11,42 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
if: ${{ ! github.event.pull_request.head.repo.fork }}
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
actions: "read"
|
contents: 'read'
|
||||||
contents: "read"
|
id-token: 'write'
|
||||||
id-token: "write"
|
|
||||||
steps:
|
steps:
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: "1.21"
|
go-version: '1.18'
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v3
|
||||||
- id: "auth"
|
- id: 'auth'
|
||||||
uses: "google-github-actions/auth@v2"
|
uses: 'google-github-actions/auth@v1'
|
||||||
with:
|
with:
|
||||||
workload_identity_provider: "projects/811013774421/locations/global/workloadIdentityPools/github-pool/providers/github-provider"
|
workload_identity_provider: 'projects/811013774421/locations/global/workloadIdentityPools/github-pool/providers/github-provider'
|
||||||
service_account: "github-ci-external@trufflehog-testing.iam.gserviceaccount.com"
|
service_account: 'github-ci-external@trufflehog-testing.iam.gserviceaccount.com'
|
||||||
- name: Set up gotestsum
|
- name: Test
|
||||||
run: |
|
run: make test-integration
|
||||||
go install gotest.tools/gotestsum@latest
|
test-detectors:
|
||||||
mkdir -p tmp/test-results
|
|
||||||
- name: Test
|
|
||||||
run: |
|
|
||||||
CGO_ENABLED=1 gotestsum --junitfile tmp/test-results/test.xml --raw-command -- go test -json -tags=sources $(go list ./... | grep -v /vendor/ | grep -v pkg/detectors)
|
|
||||||
if: ${{ success() || failure() }} # always run this step, even if there were previous errors
|
|
||||||
- name: Upload test results to BuildPulse for flaky test detection
|
|
||||||
if: ${{ !cancelled() }} # Run this step even when the tests fail. Skip if the workflow is cancelled.
|
|
||||||
uses: buildpulse/buildpulse-action@main
|
|
||||||
with:
|
|
||||||
account: 79229934
|
|
||||||
repository: 77726177
|
|
||||||
path: |
|
|
||||||
tmp/test-results/*.xml
|
|
||||||
key: ${{ secrets.BUILDPULSE_ACCESS_KEY_ID }}
|
|
||||||
secret: ${{ secrets.BUILDPULSE_SECRET_ACCESS_KEY }}
|
|
||||||
tags: integration
|
|
||||||
- name: Annotate test results
|
|
||||||
uses: mikepenz/action-junit-report@v4
|
|
||||||
if: success() || failure() # always run even if the previous step fails
|
|
||||||
with:
|
|
||||||
report_paths: "tmp/test-results/*.xml"
|
|
||||||
test-community:
|
|
||||||
if: ${{ github.event.pull_request.head.repo.fork }}
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
actions: "read"
|
contents: 'read'
|
||||||
contents: "read"
|
id-token: 'write'
|
||||||
steps:
|
steps:
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: "1.21"
|
go-version: '1.18'
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v3
|
||||||
- name: Test
|
- id: 'auth'
|
||||||
run: make test-community
|
uses: 'google-github-actions/auth@v1'
|
||||||
|
with:
|
||||||
|
workload_identity_provider: 'projects/811013774421/locations/global/workloadIdentityPools/github-pool/providers/github-provider'
|
||||||
|
service_account: 'github-ci-external@trufflehog-testing.iam.gserviceaccount.com'
|
||||||
|
- name: Test
|
||||||
|
run: make test-detectors
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
@@ -96,15 +96,3 @@ brews:
|
|||||||
homepage: "https://github.com/trufflesecurity/trufflehog"
|
homepage: "https://github.com/trufflesecurity/trufflehog"
|
||||||
install: |
|
install: |
|
||||||
bin.install "trufflehog"
|
bin.install "trufflehog"
|
||||||
signs:
|
|
||||||
- cmd: cosign
|
|
||||||
signature: "${artifact}.sig"
|
|
||||||
certificate: "${artifact}.pem"
|
|
||||||
args:
|
|
||||||
- "sign-blob"
|
|
||||||
- "--oidc-issuer=https://token.actions.githubusercontent.com"
|
|
||||||
- "--output-certificate=${certificate}"
|
|
||||||
- "--output-signature=${signature}"
|
|
||||||
- "${artifact}"
|
|
||||||
- "--yes"
|
|
||||||
artifacts: checksum
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
repos:
|
|
||||||
- repo: https://github.com/rhysd/actionlint
|
|
||||||
rev: v1.6.24
|
|
||||||
hooks:
|
|
||||||
- id: actionlint
|
|
||||||
|
|
||||||
- repo: https://github.com/mpalmer/action-validator
|
|
||||||
rev: v0.5.1
|
|
||||||
hooks:
|
|
||||||
- id: action-validator
|
|
||||||
+4
-3
@@ -8,9 +8,10 @@ RUN --mount=type=cache,target=/go/pkg/mod \
|
|||||||
--mount=type=cache,target=/root/.cache/go-build \
|
--mount=type=cache,target=/root/.cache/go-build \
|
||||||
GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o trufflehog .
|
GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o trufflehog .
|
||||||
|
|
||||||
FROM alpine:3.18
|
FROM alpine:3.15
|
||||||
RUN apk add --no-cache bash git openssh-client ca-certificates rpm2cpio binutils cpio \
|
RUN apk add --no-cache bash git openssh-client ca-certificates \
|
||||||
&& rm -rf /var/cache/apk/* && update-ca-certificates
|
&& rm -rf /var/cache/apk/* && \
|
||||||
|
update-ca-certificates
|
||||||
COPY --from=builder /build/trufflehog /usr/bin/trufflehog
|
COPY --from=builder /build/trufflehog /usr/bin/trufflehog
|
||||||
COPY entrypoint.sh /etc/entrypoint.sh
|
COPY entrypoint.sh /etc/entrypoint.sh
|
||||||
RUN chmod +x /etc/entrypoint.sh
|
RUN chmod +x /etc/entrypoint.sh
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
FROM alpine:3.18
|
FROM alpine:3.15
|
||||||
|
|
||||||
RUN apk add --no-cache bash git openssh-client ca-certificates rpm2cpio binutils cpio \
|
RUN apk add --no-cache bash git openssh-client ca-certificates \
|
||||||
&& rm -rf /var/cache/apk/* && update-ca-certificates
|
&& rm -rf /var/cache/apk/* && \
|
||||||
|
update-ca-certificates
|
||||||
WORKDIR /usr/bin/
|
WORKDIR /usr/bin/
|
||||||
COPY trufflehog .
|
COPY trufflehog .
|
||||||
COPY entrypoint.sh /etc/entrypoint.sh
|
COPY entrypoint.sh /etc/entrypoint.sh
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
PROTOS_IMAGE ?= trufflesecurity/protos:1.21-0
|
PROTOS_IMAGE ?= trufflesecurity/protos:1.18-0
|
||||||
|
|
||||||
.PHONY: check
|
.PHONY: check
|
||||||
.PHONY: lint
|
.PHONY: lint
|
||||||
@@ -22,7 +22,7 @@ check:
|
|||||||
go vet $(shell go list ./... | grep -v /vendor/)
|
go vet $(shell go list ./... | grep -v /vendor/)
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
golangci-lint run --enable bodyclose --enable exportloopref --out-format=colored-line-number --timeout 10m
|
golangci-lint run --enable bodyclose --out-format=colored-line-number --timeout 10m
|
||||||
|
|
||||||
test-failing:
|
test-failing:
|
||||||
CGO_ENABLED=0 go test -timeout=5m $(shell go list ./... | grep -v /vendor/) | grep FAIL
|
CGO_ENABLED=0 go test -timeout=5m $(shell go list ./... | grep -v /vendor/) | grep FAIL
|
||||||
@@ -39,9 +39,6 @@ test-race:
|
|||||||
test-detectors:
|
test-detectors:
|
||||||
CGO_ENABLED=0 go test -tags=detectors -timeout=5m $(shell go list ./... | grep pkg/detectors)
|
CGO_ENABLED=0 go test -tags=detectors -timeout=5m $(shell go list ./... | grep pkg/detectors)
|
||||||
|
|
||||||
test-community:
|
|
||||||
CGO_ENABLED=0 go test -timeout=5m $(shell go list ./... | grep -v /vendor/ | grep -v pkg/detectors | grep -v pkg/sources)
|
|
||||||
|
|
||||||
bench:
|
bench:
|
||||||
CGO_ENABLED=0 go test $(shell go list ./pkg/secrets/... | grep -v /vendor/) -benchmem -run=xxx -bench .
|
CGO_ENABLED=0 go test $(shell go list ./pkg/secrets/... | grep -v /vendor/) -benchmem -run=xxx -bench .
|
||||||
|
|
||||||
@@ -52,17 +49,17 @@ run-debug:
|
|||||||
CGO_ENABLED=0 go run . git file://. --json --debug
|
CGO_ENABLED=0 go run . git file://. --json --debug
|
||||||
|
|
||||||
protos:
|
protos:
|
||||||
docker run --rm -u "$(shell id -u)" -v "$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))":/pwd "${PROTOS_IMAGE}" bash -c "cd /pwd; /pwd/scripts/gen_proto.sh"
|
docker run -u "$(shell id -u)" -v "$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))":/pwd "${PROTOS_IMAGE}" bash -c "cd /pwd; /pwd/scripts/gen_proto.sh"
|
||||||
|
|
||||||
protos-windows:
|
protos-windows:
|
||||||
docker run --rm -v "$(shell cygpath -w $(shell pwd))":/pwd "${PROTOS_IMAGE}" bash -c "cd /pwd; ./scripts/gen_proto.sh"
|
docker run -v "$(shell cygpath -w $(shell pwd))":/pwd "${PROTOS_IMAGE}" bash -c "cd /pwd; ./scripts/gen_proto.sh"
|
||||||
|
|
||||||
release-protos-image:
|
release-protos-image:
|
||||||
docker buildx build --push --platform=linux/amd64,linux/arm64 \
|
docker buildx build --push --platform=linux/amd64,linux/arm64 \
|
||||||
-t trufflesecurity/protos:1.21-0 -f hack/Dockerfile.protos .
|
-t trufflesecurity/protos:1.18-0 -f hack/Dockerfile.protos .
|
||||||
|
|
||||||
snifftest:
|
snifftest:
|
||||||
./hack/snifftest/snifftest.sh
|
./hack/snifftest/snifftest.sh
|
||||||
|
|
||||||
test-release:
|
test-release:
|
||||||
goreleaser release --rm-dist --skip-publish --snapshot
|
goreleaser release --rm-dist --skip-publish --snapshot
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
|
[](https://github.com/trufflesecurity/trufflehog/actions)
|
||||||
[](https://goreportcard.com/report/github.com/trufflesecurity/trufflehog/v3)
|
[](https://goreportcard.com/report/github.com/trufflesecurity/trufflehog/v3)
|
||||||
[](/LICENSE)
|
[](/LICENSE)
|
||||||
[](/pkg/detectors)
|
[](/pkg/detectors)
|
||||||
@@ -16,25 +17,13 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# :mag*right: \_Now Scanning*
|
## Join The Slack
|
||||||
|
Have questions? Feedback? Jump in slack and hang out with us
|
||||||
|
|
||||||
<div align="center">
|
https://join.slack.com/t/trufflehog-community/shared_invite/zt-pw2qbi43-Aa86hkiimstfdKH9UCpPzQ
|
||||||
|
|
||||||
<img src="assets/scanning_logos.svg">
|
|
||||||
|
|
||||||
**...and more**
|
## Demo
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
# :loudspeaker: Join Our Community
|
|
||||||
|
|
||||||
Have questions? Feedback? Jump in slack or discord and hang out with us
|
|
||||||
|
|
||||||
Join our [Slack Community](https://join.slack.com/t/trufflehog-community/shared_invite/zt-pw2qbi43-Aa86hkiimstfdKH9UCpPzQ)
|
|
||||||
|
|
||||||
Join the [Secret Scanning Discord](https://discord.gg/8Hzbrnkr7E)
|
|
||||||
|
|
||||||
# :tv: Demo
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -42,70 +31,9 @@ Join the [Secret Scanning Discord](https://discord.gg/8Hzbrnkr7E)
|
|||||||
docker run --rm -it -v "$PWD:/pwd" trufflesecurity/trufflehog:latest github --org=trufflesecurity
|
docker run --rm -it -v "$PWD:/pwd" trufflesecurity/trufflehog:latest github --org=trufflesecurity
|
||||||
```
|
```
|
||||||
|
|
||||||
# :floppy_disk: Installation
|
## Examples
|
||||||
|
|
||||||
Several options available for you:
|
### Example 1: Scan a repo for only verified secrets
|
||||||
|
|
||||||
```bash
|
|
||||||
# MacOS users
|
|
||||||
brew install trufflesecurity/trufflehog/trufflehog
|
|
||||||
|
|
||||||
# Docker
|
|
||||||
docker run --rm -it -v "$PWD:/pwd" trufflesecurity/trufflehog:latest github --repo https://github.com/trufflesecurity/test_keys
|
|
||||||
|
|
||||||
# Docker for M1 and M2 Mac
|
|
||||||
docker run --platform linux/arm64 --rm -it -v "$PWD:/pwd" trufflesecurity/trufflehog:latest github --repo https://github.com/trufflesecurity/test_keys
|
|
||||||
|
|
||||||
# Binary releases
|
|
||||||
Download and unpack from https://github.com/trufflesecurity/trufflehog/releases
|
|
||||||
|
|
||||||
# Compile from source
|
|
||||||
git clone https://github.com/trufflesecurity/trufflehog.git
|
|
||||||
cd trufflehog; go install
|
|
||||||
|
|
||||||
# Using installation script
|
|
||||||
curl -sSfL https://raw.githubusercontent.com/trufflesecurity/trufflehog/main/scripts/install.sh | sh -s -- -b /usr/local/bin
|
|
||||||
# Using installation script to install a specific version
|
|
||||||
curl -sSfL https://raw.githubusercontent.com/trufflesecurity/trufflehog/main/scripts/install.sh | sh -s -- -b /usr/local/bin <ReleaseTag like v3.56.0>
|
|
||||||
```
|
|
||||||
|
|
||||||
# :closed_lock_with_key: Verifying the artifacts
|
|
||||||
|
|
||||||
Checksums are applied to all artifacts, and the resulting checksum file is signed using cosign.
|
|
||||||
|
|
||||||
You need the following tool to verify signature:
|
|
||||||
|
|
||||||
- [Cosign](https://docs.sigstore.dev/cosign/installation/)
|
|
||||||
|
|
||||||
Verification steps are as follow:
|
|
||||||
|
|
||||||
1. Download the artifact files you want, and the following files from the [releases](https://github.com/trufflesecurity/trufflehog/releases) page.
|
|
||||||
|
|
||||||
- trufflehog\_{version}\_checksums.txt
|
|
||||||
- trufflehog\_{version}\_checksums.txt.pem
|
|
||||||
- trufflehog\_{version}\_checksums.txt.sig
|
|
||||||
|
|
||||||
2. Verify the signature:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
cosign verify-blob <path to trufflehog_{version}_checksums.txt> \
|
|
||||||
--certificate <path to trufflehog_{version}_checksums.txt.pem> \
|
|
||||||
--signature <path to trufflehog_{version}_checksums.txt.sig> \
|
|
||||||
--certificate-identity-regexp 'https://github\.com/trufflesecurity/trufflehog/\.github/workflows/.+' \
|
|
||||||
--certificate-oidc-issuer "https://token.actions.githubusercontent.com"
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Once the signature is confirmed as valid, you can proceed to validate that the SHA256 sums align with the downloaded artifact:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
sha256sum --ignore-missing -c trufflehog_{version}_checksums.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
Replace `{version}` with the downloaded files version
|
|
||||||
|
|
||||||
# :rocket: Quick Start
|
|
||||||
|
|
||||||
## 1: Scan a repo for only verified secrets
|
|
||||||
|
|
||||||
Command:
|
Command:
|
||||||
|
|
||||||
@@ -114,7 +42,6 @@ trufflehog git https://github.com/trufflesecurity/test_keys --only-verified
|
|||||||
```
|
```
|
||||||
|
|
||||||
Expected output:
|
Expected output:
|
||||||
|
|
||||||
```
|
```
|
||||||
🐷🔑🐷 TruffleHog. Unearth your secrets. 🐷🔑🐷
|
🐷🔑🐷 TruffleHog. Unearth your secrets. 🐷🔑🐷
|
||||||
|
|
||||||
@@ -131,13 +58,13 @@ Timestamp: 2022-06-16 10:17:40 -0700 PDT
|
|||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
## 2: Scan a GitHub Org for only verified secrets
|
### Example 2: Scan a GitHub Org for only verified secrets
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
trufflehog github --org=trufflesecurity --only-verified
|
trufflehog github --org=trufflesecurity --only-verified
|
||||||
```
|
```
|
||||||
|
|
||||||
## 3: Scan a GitHub Repo for only verified keys and get JSON output
|
### Example 3: Scan a GitHub Repo for only verified keys and get JSON output
|
||||||
|
|
||||||
Command:
|
Command:
|
||||||
|
|
||||||
@@ -146,103 +73,110 @@ trufflehog git https://github.com/trufflesecurity/test_keys --only-verified --js
|
|||||||
```
|
```
|
||||||
|
|
||||||
Expected output:
|
Expected output:
|
||||||
|
|
||||||
```
|
```
|
||||||
{"SourceMetadata":{"Data":{"Git":{"commit":"fbc14303ffbf8fb1c2c1914e8dda7d0121633aca","file":"keys","email":"counter \u003ccounter@counters-MacBook-Air.local\u003e","repository":"https://github.com/trufflesecurity/test_keys","timestamp":"2022-06-16 10:17:40 -0700 PDT","line":4}}},"SourceID":0,"SourceType":16,"SourceName":"trufflehog - git","DetectorType":2,"DetectorName":"AWS","DecoderName":"PLAIN","Verified":true,"Raw":"AKIAYVP4CIPPERUVIFXG","Redacted":"AKIAYVP4CIPPERUVIFXG","ExtraData":{"account":"595918472158","arn":"arn:aws:iam::595918472158:user/canarytokens.com@@mirux23ppyky6hx3l6vclmhnj","user_id":"AIDAYVP4CIPPJ5M54LRCY"},"StructuredData":null}
|
{"SourceMetadata":{"Data":{"Git":{"commit":"fbc14303ffbf8fb1c2c1914e8dda7d0121633aca","file":"keys","email":"counter \u003ccounter@counters-MacBook-Air.local\u003e","repository":"https://github.com/trufflesecurity/test_keys","timestamp":"2022-06-16 10:17:40 -0700 PDT","line":4}}},"SourceID":0,"SourceType":16,"SourceName":"trufflehog - git","DetectorType":2,"DetectorName":"AWS","DecoderName":"PLAIN","Verified":true,"Raw":"AKIAYVP4CIPPERUVIFXG","Redacted":"AKIAYVP4CIPPERUVIFXG","ExtraData":{"account":"595918472158","arn":"arn:aws:iam::595918472158:user/canarytokens.com@@mirux23ppyky6hx3l6vclmhnj","user_id":"AIDAYVP4CIPPJ5M54LRCY"},"StructuredData":null}
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
## 4: Scan a GitHub Repo + its Issues and Pull Requests.
|
### Example 4: Scan an S3 bucket for verified keys
|
||||||
|
|
||||||
```bash
|
|
||||||
trufflehog github --repo=https://github.com/trufflesecurity/test_keys --issue-comments --pr-comments
|
|
||||||
```
|
|
||||||
|
|
||||||
## 5: Scan an S3 bucket for verified keys
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
trufflehog s3 --bucket=<bucket name> --only-verified
|
trufflehog s3 --bucket=<bucket name> --only-verified
|
||||||
```
|
```
|
||||||
|
|
||||||
## 6: Scan S3 buckets using IAM Roles
|
### FAQ
|
||||||
|
|
||||||
```bash
|
+ All I see is `🐷🔑🐷 TruffleHog. Unearth your secrets. 🐷🔑🐷` and the program exits, what gives?
|
||||||
trufflehog s3 --role-arn=<iam role arn>
|
+ That means no secrets were detected
|
||||||
```
|
+ Why is the scan is 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/)
|
||||||
|
|
||||||
## 7: Scan a Github Repo using SSH authentication in docker
|
|
||||||
|
### Example 5: Scan a Github Repo using SSH authentication in docker
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run --rm -v "$HOME/.ssh:/root/.ssh:ro" trufflesecurity/trufflehog:latest git ssh://github.com/trufflesecurity/test_keys
|
docker run --rm -v "$HOME/.ssh:/root/.ssh:ro" trufflesecurity/trufflehog:latest git ssh://github.com/trufflesecurity/test_keys
|
||||||
```
|
```
|
||||||
|
|
||||||
## 8: Scan individual files or directories
|
### Example 6: Scan individual files or directories
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
trufflehog filesystem path/to/file1.txt path/to/file2.txt path/to/dir
|
trufflehog filesystem path/to/file1.txt path/to/file2.txt path/to/dir
|
||||||
```
|
```
|
||||||
|
|
||||||
## 9: Scan GCS buckets for verified secrets.
|
# What's new in v3?
|
||||||
|
|
||||||
```bash
|
|
||||||
trufflehog gcs --project-id=<project-ID> --cloud-environment --only-verified
|
|
||||||
```
|
|
||||||
|
|
||||||
## 10: Scan a Docker image for verified secrets.
|
|
||||||
|
|
||||||
Use the `--image` flag multiple times to scan multiple images.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
trufflehog docker --image trufflesecurity/secrets --only-verified
|
|
||||||
```
|
|
||||||
|
|
||||||
## 11: Scan in CI
|
|
||||||
|
|
||||||
Set the `--since-commit` flag to your default branch that people merge into (ex: "main"). Set the `--branch` flag to your PR's branch name (ex: "feature-1"). Depending on the CI/CD platform you use, this value can be pulled in dynamically (ex: [CIRCLE_BRANCH in Circle CI](https://circleci.com/docs/variables/) and [TRAVIS_PULL_REQUEST_BRANCH in Travis CI](https://docs.travis-ci.com/user/environment-variables/)). If the repo is cloned and the target branch is already checked out during the CI/CD workflow, then `--branch HEAD` should be sufficient. The `--fail` flag will return an 183 error code if valid credentials are found.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
trufflehog git file://. --since-commit main --branch feature-1 --only-verified --fail
|
|
||||||
```
|
|
||||||
|
|
||||||
# :question: FAQ
|
|
||||||
|
|
||||||
- All I see is `🐷🔑🐷 TruffleHog. Unearth your secrets. 🐷🔑🐷` and the program exits, what gives?
|
|
||||||
- That means no secrets were detected
|
|
||||||
- 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/)
|
|
||||||
- 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.
|
|
||||||
|
|
||||||
# :newspaper: What's new in v3?
|
|
||||||
|
|
||||||
TruffleHog v3 is a complete rewrite in Go with many new powerful features.
|
TruffleHog v3 is a complete rewrite in Go with many new powerful features.
|
||||||
|
|
||||||
- We've **added over 700 credential detectors that support active verification against their respective APIs**.
|
- We've **added over 700 credential detectors that support active verification against their respective APIs**.
|
||||||
- We've also added native **support for scanning GitHub, GitLab, Docker, filesystems, S3, GCS, Circle CI and Travis CI**.
|
- We've also added native **support for scanning GitHub, GitLab, filesystems, S3, and Circle CI**.
|
||||||
- **Instantly verify private keys** against millions of github users and **billions** of TLS certificates using our [Driftwood](https://trufflesecurity.com/blog/driftwood) technology.
|
- **Instantly verify private keys** against millions of github users and **billions** of TLS certificates using our [Driftwood](https://trufflesecurity.com/blog/driftwood) technology.
|
||||||
- Scan binaries, documents, and other file formats
|
|
||||||
- Available as a GitHub Action and a pre-commit hook
|
|
||||||
|
|
||||||
## What is credential verification?
|
## 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. 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
|
## Installation
|
||||||
|
|
||||||
|
Several options:
|
||||||
|
|
||||||
|
### 1. Go
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/trufflesecurity/trufflehog.git
|
||||||
|
|
||||||
|
cd trufflehog; go install
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. [Release binaries](https://github.com/trufflesecurity/trufflehog/releases)
|
||||||
|
|
||||||
|
### 3. Docker
|
||||||
|
|
||||||
|
|
||||||
|
> Note: Apple M1 hardware users should run with `docker run --rm --platform linux/arm64` for better performance.
|
||||||
|
|
||||||
|
#### **Most users**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run --rm -it -v "$PWD:/pwd" trufflesecurity/trufflehog:latest github --repo https://github.com/trufflesecurity/test_keys
|
||||||
|
```
|
||||||
|
|
||||||
|
#### **Apple M1 users**
|
||||||
|
|
||||||
|
The `linux/arm64` image is better to run on the M1 than the amd64 image.
|
||||||
|
Even better is running the native darwin binary available, but there is no container image for that.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run --rm --platform linux/arm64 -it -v "$PWD:/pwd" trufflesecurity/trufflehog:latest github --repo https://github.com/trufflesecurity/test_keys
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Pip (help wanted)
|
||||||
|
|
||||||
|
It's possible to distribute binaries in pip wheels.
|
||||||
|
|
||||||
|
Here is an example of a [project that does it](https://github.com/Yelp/dumb-init).
|
||||||
|
|
||||||
|
Help with setting up this packaging would be appreciated!
|
||||||
|
|
||||||
|
### 5. Brew
|
||||||
|
|
||||||
|
```bash
|
||||||
|
brew install trufflesecurity/trufflehog/trufflehog
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
TruffleHog has a sub-command for each source of data that you may want to scan:
|
TruffleHog has a sub-command for each source of data that you may want to scan:
|
||||||
|
|
||||||
- git
|
- git
|
||||||
- github
|
- github
|
||||||
- gitlab
|
- gitlab
|
||||||
- docker
|
|
||||||
- S3
|
- S3
|
||||||
- filesystem (files and directories)
|
- filesystem (files and directories)
|
||||||
- syslog
|
- syslog
|
||||||
- circleci
|
- circleci
|
||||||
- travisci
|
- stdin (coming soon)
|
||||||
- GCS (Google Cloud Storage)
|
|
||||||
|
|
||||||
Each subcommand can have options that you can see with the `--help` flag provided to the sub command:
|
Each subcommand can have options that you can see with the `--help` flag provided to the sub command:
|
||||||
|
|
||||||
@@ -253,91 +187,49 @@ usage: TruffleHog git [<flags>] <uri>
|
|||||||
Find credentials in git repositories.
|
Find credentials in git repositories.
|
||||||
|
|
||||||
Flags:
|
Flags:
|
||||||
-h, --help Show context-sensitive help (also try --help-long and --help-man).
|
--help Show context-sensitive help (also try --help-long and --help-man).
|
||||||
--debug Run in debug mode.
|
--debug Run in debug mode.
|
||||||
--trace Run in trace mode.
|
--trace Run in trace mode.
|
||||||
--profile Enables profiling and sets a pprof and fgprof server on :18066.
|
--profile Enables profiling and sets a pprof and fgprof server on :18066.
|
||||||
-j, --json Output in JSON format.
|
-j, --json Output in JSON format.
|
||||||
--json-legacy Use the pre-v3.0 JSON format. Only works with git, gitlab, and github sources.
|
--json-legacy Use the pre-v3.0 JSON format. Only works with git, gitlab, and github sources.
|
||||||
--github-actions Output in GitHub Actions format.
|
--concurrency=10 Number of concurrent workers.
|
||||||
--concurrency=8 Number of concurrent workers.
|
--no-verification Don't verify the results.
|
||||||
--no-verification Don't verify the results.
|
--only-verified Only output verified results.
|
||||||
--only-verified Only output verified results.
|
--filter-unverified Only output first unverified result per chunk per detector if there are more than one results.
|
||||||
--filter-unverified Only output first unverified result per chunk per detector if there are more than one results.
|
|
||||||
--filter-entropy=FILTER-ENTROPY
|
|
||||||
Filter unverified results with Shannon entropy. Start with 3.0.
|
|
||||||
--config=CONFIG Path to configuration file.
|
--config=CONFIG Path to configuration file.
|
||||||
--print-avg-detector-time
|
--print-avg-detector-time Print the average time spent on each detector.
|
||||||
Print the average time spent on each detector.
|
--no-update Don't check for updates.
|
||||||
--no-update Don't check for updates.
|
--fail Exit with code 183 if results are found.
|
||||||
--fail Exit with code 183 if results are found.
|
--version Show application version.
|
||||||
--verifier=VERIFIER ... Set custom verification endpoints.
|
|
||||||
--archive-max-size=ARCHIVE-MAX-SIZE
|
|
||||||
Maximum size of archive to scan. (Byte units eg. 512B, 2KB, 4MB)
|
|
||||||
--archive-max-depth=ARCHIVE-MAX-DEPTH
|
|
||||||
Maximum depth of archive to scan.
|
|
||||||
--archive-timeout=ARCHIVE-TIMEOUT
|
|
||||||
Maximum time to spend extracting an archive.
|
|
||||||
--include-detectors="all" Comma separated list of detector types to include. Protobuf name or IDs may be used, as well as ranges.
|
|
||||||
--exclude-detectors=EXCLUDE-DETECTORS
|
|
||||||
Comma separated list of detector types to exclude. Protobuf name or IDs may be used, as well as ranges. IDs defined here take precedence over the include list.
|
|
||||||
--version Show application version.
|
|
||||||
-i, --include-paths=INCLUDE-PATHS
|
|
||||||
Path to file with newline separated regexes for files to include in scan.
|
|
||||||
-x, --exclude-paths=EXCLUDE-PATHS
|
|
||||||
Path to file with newline separated regexes for files to exclude in scan.
|
|
||||||
--exclude-globs=EXCLUDE-GLOBS
|
|
||||||
Comma separated list of globs to exclude in scan. This option filters at the `git log` level, resulting in faster scans.
|
|
||||||
--since-commit=SINCE-COMMIT
|
|
||||||
Commit to start scan from.
|
|
||||||
--branch=BRANCH Branch to scan.
|
|
||||||
--max-depth=MAX-DEPTH Maximum depth of commits to scan.
|
|
||||||
--bare Scan bare repository (e.g. useful while using in pre-receive hooks)
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
<uri> Git repository URL. https://, file://, or ssh:// schema expected.
|
<uri> Git repository URL. https://, file://, or ssh:// schema expected.
|
||||||
```
|
```
|
||||||
|
|
||||||
For example, to scan a `git` repository, start with
|
For example, to scan a `git` repository, start with
|
||||||
|
|
||||||
```
|
```
|
||||||
$ trufflehog git https://github.com/trufflesecurity/trufflehog.git
|
$ trufflehog git https://github.com/trufflesecurity/trufflehog.git
|
||||||
```
|
```
|
||||||
|
|
||||||
## S3
|
|
||||||
|
|
||||||
The S3 source supports assuming IAM roles for scanning in addition to IAM users. This makes it easier for users to scan multiple AWS accounts without needing to rely on hardcoded credentials for each account.
|
|
||||||
|
|
||||||
The IAM identity that TruffleHog uses initially will need to have `AssumeRole` privileges as a principal in the [trust policy](https://aws.amazon.com/blogs/security/how-to-use-trust-policies-with-iam-roles/) of each IAM role to assume.
|
|
||||||
|
|
||||||
To scan a specific bucket using locally set credentials or instance metadata if on an EC2 instance:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
trufflehog s3 --bucket=<bucket-name>
|
|
||||||
```
|
|
||||||
|
|
||||||
To scan a specific bucket using an assumed role:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
trufflehog s3 --bucket=<bucket-name> --role-arn=<iam-role-arn>
|
|
||||||
```
|
|
||||||
|
|
||||||
Multiple roles can be passed as separate arguments. The following command will attempt to scan every bucket each role has permissions to list in the S3 API:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
trufflehog s3 --role-arn=<iam-role-arn-1> --role-arn=<iam-role-arn-2>
|
|
||||||
```
|
|
||||||
|
|
||||||
Exit Codes:
|
Exit Codes:
|
||||||
|
|
||||||
- 0: No errors and no results were found.
|
- 0: No errors and no results were found.
|
||||||
- 1: An error was encountered. Sources may not have completed scans.
|
- 1: An error was encountered. Sources may not have completed scans.
|
||||||
- 183: No errors were encountered, but results were found. Will only be returned if `--fail` flag is used.
|
- 183: No errors were encountered, but results were found. Will only be returned if `--fail` flag is used.
|
||||||
|
|
||||||
## :octocat: TruffleHog Github Action
|
#### Scanning an organization
|
||||||
|
|
||||||
|
Try scanning an entire GitHub organization with the following:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run --rm -it -v "$PWD:/pwd" trufflesecurity/trufflehog:latest github --org=trufflesecurity
|
||||||
|
```
|
||||||
|
|
||||||
|
### TruffleHog OSS Github Action
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- name: TruffleHog
|
- name: TruffleHog OSS
|
||||||
uses: trufflesecurity/trufflehog@main
|
uses: trufflesecurity/trufflehog@main
|
||||||
with:
|
with:
|
||||||
# Repository path
|
# Repository path
|
||||||
@@ -354,9 +246,8 @@ The TruffleHog OSS Github Action can be used to scan a range of commits for leak
|
|||||||
any results are found.
|
any results are found.
|
||||||
|
|
||||||
For example, to scan the contents of pull requests you could use the following workflow:
|
For example, to scan the contents of pull requests you could use the following workflow:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
name: TruffleHog Secrets Scan
|
name: Leaked Secrets Scan
|
||||||
on: [pull_request]
|
on: [pull_request]
|
||||||
jobs:
|
jobs:
|
||||||
TruffleHog:
|
TruffleHog:
|
||||||
@@ -375,23 +266,23 @@ jobs:
|
|||||||
extra_args: --debug --only-verified
|
extra_args: --debug --only-verified
|
||||||
```
|
```
|
||||||
|
|
||||||
## Pre-commit Hook
|
### Precommit Hook
|
||||||
|
|
||||||
Trufflehog can be used in a pre-commit hook to prevent credentials from leaking before they ever leave your computer.
|
Trufflehog can be used in a precommit hook to prevent credentials from leaking before they ever leave your computer.
|
||||||
An example `.pre-commit-config.yaml` is provided (see [pre-commit.com](https://pre-commit.com/) for installation).
|
An example `.pre-commit-config.yaml` is provided (see [pre-commit.com](https://pre-commit.com/) for installation).
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
repos:
|
repos:
|
||||||
- repo: local
|
- repo: local
|
||||||
hooks:
|
hooks:
|
||||||
- id: trufflehog
|
- id: trufflehog
|
||||||
name: TruffleHog
|
name: TruffleHog
|
||||||
description: Detect secrets in your data.
|
description: Detect secrets in your data.
|
||||||
entry: bash -c 'trufflehog git file://. --since-commit HEAD --only-verified --fail'
|
entry: bash -c 'trufflehog git file://. --since-commit HEAD --only-verified --fail'
|
||||||
# For running trufflehog in docker, use the following entry instead:
|
# For running trufflehog in docker, use the following entry instead:
|
||||||
# entry: bash -c 'docker run --rm -v "$(pwd):/workdir" -i --rm trufflesecurity/trufflehog:latest git file:///workdir --since-commit HEAD --only-verified --fail'
|
# entry: bash -c 'docker run --rm -v "$(pwd):/workdir" -i --rm trufflesecurity/trufflehog:latest git file:///workdir --since-commit HEAD --only-verified --fail'
|
||||||
language: system
|
language: system
|
||||||
stages: ["commit", "push"]
|
stages: ["commit", "push"]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Regex Detector (alpha)
|
## Regex Detector (alpha)
|
||||||
@@ -408,22 +299,22 @@ status code, the secret is considered verified.
|
|||||||
|
|
||||||
**NB:** This feature is alpha and subject to change.
|
**NB:** This feature is alpha and subject to change.
|
||||||
|
|
||||||
## Regex Detector Example
|
### Regex Detector Example
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
# config.yaml
|
# config.yaml
|
||||||
detectors:
|
detectors:
|
||||||
- name: hog detector
|
- name: hog detector
|
||||||
keywords:
|
keywords:
|
||||||
- hog
|
- hog
|
||||||
regex:
|
regex:
|
||||||
adjective: hogs are (\S+)
|
adjective: hogs are (\S+)
|
||||||
verify:
|
verify:
|
||||||
- endpoint: http://localhost:8000/
|
- endpoint: http://localhost:8000/
|
||||||
# unsafe must be set if the endpoint is HTTP
|
# unsafe must be set if the endpoint is HTTP
|
||||||
unsafe: true
|
unsafe: true
|
||||||
headers:
|
headers:
|
||||||
- "Authorization: super secret authorization header"
|
- 'Authorization: super secret authorization header'
|
||||||
```
|
```
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -437,7 +328,7 @@ Raw result: hogs are cool
|
|||||||
File: /tmp/hog-facts.txt
|
File: /tmp/hog-facts.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
## Verification Server Example (Python)
|
#### Verification Server Example (Python)
|
||||||
|
|
||||||
Unless you run a verification server, secrets found by the custom regex
|
Unless you run a verification server, secrets found by the custom regex
|
||||||
detector will be unverified. Here is an example Python implementation of a
|
detector will be unverified. Here is an example Python implementation of a
|
||||||
@@ -487,35 +378,38 @@ with HTTPServer(('', 8000), Verifier) as server:
|
|||||||
pass
|
pass
|
||||||
```
|
```
|
||||||
|
|
||||||
# :heart: Contributors
|
## Use as a library
|
||||||
|
|
||||||
|
Currently, trufflehog is in heavy development and no guarantees can be made on
|
||||||
|
the stability of the public APIs at this time.
|
||||||
|
|
||||||
|
## Contributors
|
||||||
|
|
||||||
This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)].
|
This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)].
|
||||||
|
|
||||||
|
|
||||||
<a href="https://github.com/trufflesecurity/trufflehog/graphs/contributors">
|
<a href="https://github.com/trufflesecurity/trufflehog/graphs/contributors">
|
||||||
<img src="https://contrib.rocks/image?repo=trufflesecurity/trufflehog" />
|
<img src="https://contrib.rocks/image?repo=trufflesecurity/trufflehog" />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
# :computer: Contributing
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
Contributions are very welcome! Please see our [contribution guidelines first](CONTRIBUTING.md).
|
Contributions are very welcome! Please see our [contribution guidelines first](CONTRIBUTING.md).
|
||||||
|
|
||||||
We no longer accept contributions to TruffleHog v2, but that code is available in the `v2` branch.
|
We no longer accept contributions to TruffleHog v2, but that code is available in the `v2` branch.
|
||||||
|
|
||||||
## Adding new secret detectors
|
### Adding new secret detectors
|
||||||
|
|
||||||
We have published some [documentation and tooling to get started on adding new secret detectors](hack/docs/Adding_Detectors_external.md). Let's improve detection together!
|
We have published some [documentation and tooling to get started on adding new secret detectors](hack/docs/Adding_Detectors_external.md). Let's improve detection together!
|
||||||
|
|
||||||
# Use as a library
|
## License Change
|
||||||
|
|
||||||
Currently, trufflehog is in heavy development and no guarantees can be made on
|
|
||||||
the stability of the public APIs at this time.
|
|
||||||
|
|
||||||
# License Change
|
|
||||||
|
|
||||||
Since v3.0, TruffleHog is released under a AGPL 3 license, included in [`LICENSE`](LICENSE). TruffleHog v3.0 uses none of the previous codebase, but care was taken to preserve backwards compatibility on the command line interface. The work previous to this release is still available licensed under GPL 2.0 in the history of this repository and the previous package releases and tags. A completed CLA is required for us to accept contributions going forward.
|
Since v3.0, TruffleHog is released under a AGPL 3 license, included in [`LICENSE`](LICENSE). TruffleHog v3.0 uses none of the previous codebase, but care was taken to preserve backwards compatibility on the command line interface. The work previous to this release is still available licensed under GPL 2.0 in the history of this repository and the previous package releases and tags. A completed CLA is required for us to accept contributions going forward.
|
||||||
|
|
||||||
# :money_with_wings: Enterprise product
|
|
||||||
|
|
||||||
Are you interested in continuously monitoring your Git, Jira, Slack, Confluence, etc.. for credentials? We have an enterprise product that can help. Reach out here to learn more https://trufflesecurity.com/contact/
|
## Enterprise product
|
||||||
|
|
||||||
|
Are you interested in continously monitoring your Git, Jira, Slack, Confluence, etc.. for credentials? We have an enterprise product that can help. Reach out here to learn more https://trufflesecurity.com/contact/
|
||||||
|
|
||||||
We take the revenue from the enterprise product to fund more awesome open source projects that the whole community can benefit from.
|
We take the revenue from the enterprise product to fund more awesome open source projects that the whole community can benefit from.
|
||||||
|
|||||||
@@ -32,5 +32,4 @@ runs:
|
|||||||
- ${{ inputs.head }}
|
- ${{ inputs.head }}
|
||||||
- --fail
|
- --fail
|
||||||
- --no-update
|
- --no-update
|
||||||
- --github-actions
|
|
||||||
- ${{ inputs.extra_args }}
|
- ${{ inputs.extra_args }}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 83 KiB |
+1
-1
@@ -3,4 +3,4 @@
|
|||||||
# Parse the last argument into an array of extra_args.
|
# Parse the last argument into an array of extra_args.
|
||||||
mapfile -t extra_args < <(bash -c "for arg in ${*: -1}; do echo \$arg; done")
|
mapfile -t extra_args < <(bash -c "for arg in ${*: -1}; do echo \$arg; done")
|
||||||
|
|
||||||
/usr/bin/trufflehog "${@: 1: $#-1}" "${extra_args[@]}"
|
/usr/bin/trufflehog "${@: 1: $#-1}" "${extra_args[@]}"
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
# Examples
|
|
||||||
This folder contains various examples like custom detectors, scripts, etc. Feel free to contribute!
|
|
||||||
|
|
||||||
### Generic Detector
|
|
||||||
An often requested feature for TruffleHog is a generic detector. By default, we do not support generic detection as it would result in lots of false positives. However, if you want to attempt detect generic secrets you can use a custom detector.
|
|
||||||
|
|
||||||
#### Try it out:
|
|
||||||
```
|
|
||||||
wget https://raw.githubusercontent.com/trufflesecurity/trufflehog/main/examples/generic.yml
|
|
||||||
trufflehog filesystem --config=$PWD/generic.yml $PWD
|
|
||||||
|
|
||||||
# to filter so that _only_ generic credentials are logged:
|
|
||||||
trufflehog filesystem --config=$PWD/generic.yml --json --no-verification $PWD | awk '/generic-api-key/{print $0}'
|
|
||||||
```
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
detectors:
|
|
||||||
- name: generic-api-key
|
|
||||||
keywords:
|
|
||||||
- key
|
|
||||||
- api
|
|
||||||
- token
|
|
||||||
- secret
|
|
||||||
- client
|
|
||||||
- passwd
|
|
||||||
- password
|
|
||||||
- auth
|
|
||||||
- access
|
|
||||||
regex:
|
|
||||||
# borrowing the gitleaks generic-api-key regex
|
|
||||||
generic-api-key: "(?i)(?:key|api|token|secret|client|passwd|password|auth|access)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:{1,3}=|\\|\\|:|<=|=>|:|\\?=)(?:'|\"|\\s|=|\\x60){0,5}([0-9a-z\\-_.=]{10,150})(?:['|\"|\\n|\\r|\\s|\\x60|;]|$)"
|
|
||||||
@@ -1,106 +1,72 @@
|
|||||||
module github.com/trufflesecurity/trufflehog/v3
|
module github.com/trufflesecurity/trufflehog/v3
|
||||||
|
|
||||||
go 1.21
|
go 1.18
|
||||||
|
|
||||||
replace github.com/jpillora/overseer => github.com/trufflesecurity/overseer v1.2.7
|
replace github.com/jpillora/overseer => github.com/trufflesecurity/overseer v1.1.7-custom5
|
||||||
|
|
||||||
|
replace github.com/zricethezav/gitleaks/v8 => github.com/trufflesecurity/gitleaks/v8 v8.6.1-custom10
|
||||||
|
|
||||||
|
replace github.com/gitleaks/go-gitdiff => github.com/trufflesecurity/go-gitdiff v0.7.6-zombies2
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go/secretmanager v1.11.4
|
cloud.google.com/go/secretmanager v1.10.0
|
||||||
cloud.google.com/go/storage v1.35.1
|
github.com/Azure/go-autorest/autorest/azure/auth v0.5.11
|
||||||
github.com/Azure/go-autorest/autorest/azure/auth v0.5.12
|
github.com/TheZeroSlave/zapsentry v1.14.0
|
||||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.0
|
github.com/aws/aws-sdk-go v1.44.83
|
||||||
github.com/BobuSumisu/aho-corasick v1.0.3
|
github.com/bill-rich/disk-buffer-reader v0.1.7
|
||||||
github.com/TheZeroSlave/zapsentry v1.19.0
|
|
||||||
github.com/alecthomas/kingpin/v2 v2.4.0
|
|
||||||
github.com/aws/aws-sdk-go v1.48.12
|
|
||||||
github.com/aymanbagabas/go-osc52 v1.2.2
|
|
||||||
github.com/bill-rich/go-syslog v0.0.0-20220413021637-49edb52a574c
|
github.com/bill-rich/go-syslog v0.0.0-20220413021637-49edb52a574c
|
||||||
github.com/bitfinexcom/bitfinex-api-go v0.0.0-20210608095005-9e0b26f200fb
|
github.com/bitfinexcom/bitfinex-api-go v0.0.0-20210608095005-9e0b26f200fb
|
||||||
github.com/bradleyfalzon/ghinstallation/v2 v2.8.0
|
github.com/bradleyfalzon/ghinstallation/v2 v2.1.0
|
||||||
github.com/charmbracelet/bubbles v0.16.1
|
|
||||||
github.com/charmbracelet/bubbletea v0.24.2
|
|
||||||
github.com/charmbracelet/glamour v0.6.0
|
|
||||||
github.com/charmbracelet/lipgloss v0.9.1
|
|
||||||
github.com/coinbase/waas-client-library-go v1.0.8
|
|
||||||
github.com/couchbase/gocb/v2 v2.7.0
|
|
||||||
github.com/crewjam/rfc5424 v0.1.0
|
github.com/crewjam/rfc5424 v0.1.0
|
||||||
github.com/denisenkom/go-mssqldb v0.12.3
|
github.com/denisenkom/go-mssqldb v0.12.3
|
||||||
github.com/envoyproxy/protoc-gen-validate v1.0.2
|
github.com/envoyproxy/protoc-gen-validate v0.9.1
|
||||||
github.com/fatih/color v1.16.0
|
github.com/fatih/color v1.13.0
|
||||||
github.com/felixge/fgprof v0.9.3
|
github.com/felixge/fgprof v0.9.3
|
||||||
github.com/getsentry/sentry-go v0.25.0
|
github.com/getsentry/sentry-go v0.18.0
|
||||||
github.com/go-errors/errors v1.5.1
|
github.com/go-errors/errors v1.4.2
|
||||||
github.com/go-git/go-git/v5 v5.10.1
|
github.com/go-git/go-git/v5 v5.5.2
|
||||||
github.com/go-ldap/ldap/v3 v3.4.6
|
github.com/go-ldap/ldap/v3 v3.4.4
|
||||||
github.com/go-logr/logr v1.3.0
|
github.com/go-logr/logr v1.2.3
|
||||||
github.com/go-logr/zapr v1.3.0
|
github.com/go-logr/zapr v1.2.3
|
||||||
github.com/go-redis/redis v6.15.9+incompatible
|
github.com/go-redis/redis v6.15.9+incompatible
|
||||||
github.com/go-sql-driver/mysql v1.7.1
|
github.com/go-sql-driver/mysql v1.7.0
|
||||||
github.com/gobwas/glob v0.2.3
|
github.com/gobwas/glob v0.2.3
|
||||||
github.com/golang-jwt/jwt v3.2.2+incompatible
|
github.com/golang-jwt/jwt v3.2.2+incompatible
|
||||||
github.com/google/go-cmp v0.6.0
|
github.com/google/go-cmp v0.5.9
|
||||||
github.com/google/go-containerregistry v0.17.0
|
|
||||||
github.com/google/go-github/v42 v42.0.0
|
github.com/google/go-github/v42 v42.0.0
|
||||||
github.com/google/go-github/v56 v56.0.0
|
github.com/gorilla/mux v1.8.0
|
||||||
github.com/google/go-github/v57 v57.0.0
|
github.com/hashicorp/go-retryablehttp v0.7.2
|
||||||
github.com/google/uuid v1.4.0
|
github.com/jlaffaye/ftp v0.1.0
|
||||||
github.com/googleapis/gax-go/v2 v2.12.0
|
|
||||||
github.com/h2non/filetype v1.1.3
|
|
||||||
github.com/hashicorp/go-retryablehttp v0.7.5
|
|
||||||
github.com/hashicorp/golang-lru v0.6.0
|
|
||||||
github.com/hashicorp/golang-lru/v2 v2.0.7
|
|
||||||
github.com/jlaffaye/ftp v0.2.0
|
|
||||||
github.com/joho/godotenv v1.5.1
|
github.com/joho/godotenv v1.5.1
|
||||||
github.com/jpillora/overseer v1.1.6
|
github.com/jpillora/overseer v1.1.6
|
||||||
github.com/kylelemons/godebug v1.1.0
|
github.com/kylelemons/godebug v1.1.0
|
||||||
github.com/launchdarkly/go-server-sdk/v6 v6.1.1
|
github.com/lib/pq v1.10.7
|
||||||
github.com/lib/pq v1.10.9
|
github.com/mattn/go-sqlite3 v1.14.16
|
||||||
github.com/lrstanley/bubblezone v0.0.0-20230911164824-e3824f1adde9
|
github.com/mholt/archiver/v4 v4.0.0-alpha.7
|
||||||
github.com/marusama/semaphore/v2 v2.5.0
|
|
||||||
github.com/mattn/go-isatty v0.0.20
|
|
||||||
github.com/mattn/go-sqlite3 v1.14.18
|
|
||||||
github.com/mholt/archiver/v4 v4.0.0-alpha.8
|
|
||||||
github.com/mitchellh/go-ps v1.0.0
|
|
||||||
github.com/muesli/reflow v0.3.0
|
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
|
||||||
github.com/paulbellamy/ratecounter v0.2.0
|
github.com/paulbellamy/ratecounter v0.2.0
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/prometheus/client_golang v1.17.0
|
github.com/rabbitmq/amqp091-go v1.7.0
|
||||||
github.com/rabbitmq/amqp091-go v1.9.0
|
|
||||||
github.com/sergi/go-diff v1.3.1
|
github.com/sergi/go-diff v1.3.1
|
||||||
github.com/shuheiktgw/go-travis v0.3.1
|
github.com/stretchr/testify v1.8.2
|
||||||
github.com/snowflakedb/gosnowflake v1.6.25
|
|
||||||
github.com/stretchr/testify v1.8.4
|
|
||||||
github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502
|
github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502
|
||||||
github.com/trufflesecurity/disk-buffer-reader v0.2.1
|
github.com/xanzy/go-gitlab v0.80.2
|
||||||
github.com/xanzy/go-gitlab v0.94.0
|
go.mongodb.org/mongo-driver v1.11.2
|
||||||
go.mongodb.org/mongo-driver v1.12.1
|
go.uber.org/zap v1.24.0
|
||||||
go.uber.org/mock v0.3.0
|
golang.org/x/crypto v0.6.0
|
||||||
go.uber.org/zap v1.26.0
|
golang.org/x/exp v0.0.0-20221018205818-5c77f4b2bbd7
|
||||||
golang.org/x/crypto v0.16.0
|
golang.org/x/oauth2 v0.5.0
|
||||||
golang.org/x/exp v0.0.0-20231127185646-65229373498e
|
golang.org/x/sync v0.1.0
|
||||||
golang.org/x/oauth2 v0.15.0
|
golang.org/x/text v0.7.0
|
||||||
golang.org/x/sync v0.5.0
|
google.golang.org/protobuf v1.28.1
|
||||||
golang.org/x/text v0.14.0
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6
|
||||||
google.golang.org/api v0.152.0
|
|
||||||
google.golang.org/protobuf v1.31.0
|
|
||||||
gopkg.in/h2non/gock.v1 v1.1.2
|
gopkg.in/h2non/gock.v1 v1.1.2
|
||||||
pgregory.net/rapid v1.1.0
|
sigs.k8s.io/yaml v1.3.0
|
||||||
sigs.k8s.io/yaml v1.4.0
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go v0.110.10 // indirect
|
cloud.google.com/go/compute v1.12.1 // indirect
|
||||||
cloud.google.com/go/compute v1.23.3 // indirect
|
cloud.google.com/go/compute/metadata v0.2.1 // indirect
|
||||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
cloud.google.com/go/iam v0.8.0 // indirect
|
||||||
cloud.google.com/go/iam v1.1.5 // indirect
|
|
||||||
cloud.google.com/go/longrunning v0.5.4 // indirect
|
|
||||||
dario.cat/mergo v1.0.0 // indirect
|
|
||||||
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
|
|
||||||
github.com/99designs/keyring v1.2.2 // indirect
|
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.4.0 // indirect
|
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.1.2 // indirect
|
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.0.0 // indirect
|
|
||||||
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||||
github.com/Azure/go-autorest/autorest v0.11.24 // indirect
|
github.com/Azure/go-autorest/autorest v0.11.24 // indirect
|
||||||
github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect
|
github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect
|
||||||
@@ -108,162 +74,79 @@ require (
|
|||||||
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
|
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
|
||||||
github.com/Azure/go-autorest/logger v0.2.1 // indirect
|
github.com/Azure/go-autorest/logger v0.2.1 // indirect
|
||||||
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
||||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
|
github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e // indirect
|
||||||
github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c // indirect
|
github.com/Microsoft/go-winio v0.5.2 // indirect
|
||||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 // indirect
|
||||||
github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect
|
github.com/acomagu/bufpipe v1.0.3 // indirect
|
||||||
github.com/acomagu/bufpipe v1.0.4 // indirect
|
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
|
||||||
github.com/alecthomas/chroma v0.10.0 // indirect
|
|
||||||
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect
|
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect
|
||||||
github.com/andybalholm/brotli v1.0.5 // indirect
|
github.com/andybalholm/brotli v1.0.4 // indirect
|
||||||
github.com/apache/arrow/go/v12 v12.0.1 // indirect
|
github.com/benbjohnson/clock v1.1.0 // indirect
|
||||||
github.com/apache/thrift v0.16.0 // indirect
|
github.com/cloudflare/circl v1.1.0 // indirect
|
||||||
github.com/atotto/clipboard v0.1.4 // indirect
|
|
||||||
github.com/aws/aws-sdk-go-v2 v1.17.7 // indirect
|
|
||||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 // indirect
|
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.13.18 // indirect
|
|
||||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.59 // indirect
|
|
||||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.31 // indirect
|
|
||||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.25 // indirect
|
|
||||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.23 // indirect
|
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11 // indirect
|
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.26 // indirect
|
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.25 // indirect
|
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.0 // indirect
|
|
||||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.31.0 // indirect
|
|
||||||
github.com/aws/smithy-go v1.13.5 // 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
|
|
||||||
github.com/bodgit/plumbing v1.3.0 // indirect
|
|
||||||
github.com/bodgit/sevenzip v1.4.3 // indirect
|
|
||||||
github.com/bodgit/windows v1.0.1 // indirect
|
|
||||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
|
||||||
github.com/cloudflare/circl v1.3.3 // indirect
|
|
||||||
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
|
|
||||||
github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect
|
|
||||||
github.com/couchbase/gocbcore/v10 v10.3.0 // indirect
|
|
||||||
github.com/couchbase/gocbcoreps v0.1.0 // indirect
|
|
||||||
github.com/couchbase/goprotostellar v1.0.0 // indirect
|
|
||||||
github.com/couchbaselabs/gocbconnstr/v2 v2.0.0-20230515165046-68b522a21131 // indirect
|
|
||||||
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
|
|
||||||
github.com/danieljoos/wincred v1.1.2 // indirect
|
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/dimchansky/utfbom v1.1.1 // indirect
|
github.com/dimchansky/utfbom v1.1.1 // indirect
|
||||||
github.com/dlclark/regexp2 v1.4.0 // indirect
|
|
||||||
github.com/docker/cli v24.0.0+incompatible // indirect
|
|
||||||
github.com/docker/distribution v2.8.2+incompatible // indirect
|
|
||||||
github.com/docker/docker v24.0.0+incompatible // indirect
|
|
||||||
github.com/docker/docker-credential-helpers v0.7.0 // indirect
|
|
||||||
github.com/dsnet/compress v0.0.1 // indirect
|
github.com/dsnet/compress v0.0.1 // indirect
|
||||||
github.com/dvsekhvalnov/jose2go v1.5.0 // indirect
|
|
||||||
github.com/emirpasic/gods v1.18.1 // indirect
|
github.com/emirpasic/gods v1.18.1 // indirect
|
||||||
github.com/form3tech-oss/jwt-go v3.2.5+incompatible // indirect
|
github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
github.com/go-git/gcfg v1.5.0 // indirect
|
||||||
github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect
|
github.com/go-git/go-billy/v5 v5.4.0 // indirect
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
|
||||||
github.com/go-git/go-billy/v5 v5.5.0 // indirect
|
|
||||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||||
github.com/goccy/go-json v0.10.0 // indirect
|
github.com/golang-jwt/jwt/v4 v4.4.1 // indirect
|
||||||
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
|
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
|
||||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe // indirect
|
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe // indirect
|
||||||
github.com/golang-sql/sqlexp v0.1.0 // indirect
|
github.com/golang-sql/sqlexp v0.1.0 // indirect
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
|
||||||
github.com/golang/protobuf v1.5.3 // indirect
|
github.com/golang/protobuf v1.5.2 // indirect
|
||||||
github.com/golang/snappy v0.0.4 // indirect
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
github.com/google/flatbuffers v23.1.21+incompatible // indirect
|
github.com/google/go-github/v45 v45.2.0 // indirect
|
||||||
github.com/google/go-querystring v1.1.0 // indirect
|
github.com/google/go-querystring v1.1.0 // indirect
|
||||||
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd // indirect
|
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd // indirect
|
||||||
github.com/google/s2a-go v0.1.7 // indirect
|
github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
github.com/googleapis/gax-go/v2 v2.7.0 // indirect
|
||||||
github.com/gorilla/css v1.0.0 // indirect
|
|
||||||
github.com/gregjones/httpcache v0.0.0-20171119193500-2bcd89a1743f // indirect
|
|
||||||
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
|
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2 // indirect
|
|
||||||
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect
|
|
||||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect
|
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect
|
||||||
github.com/hashicorp/errwrap v1.0.0 // indirect
|
github.com/hashicorp/errwrap v1.0.0 // indirect
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||||
|
github.com/imdario/mergo v0.3.13 // indirect
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||||
github.com/josharian/intern v1.0.0 // indirect
|
|
||||||
github.com/jpillora/s3 v1.1.4 // indirect
|
github.com/jpillora/s3 v1.1.4 // indirect
|
||||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||||
github.com/klauspost/asmfmt v1.3.2 // indirect
|
github.com/klauspost/compress v1.15.11 // indirect
|
||||||
github.com/klauspost/compress v1.16.6 // indirect
|
|
||||||
github.com/klauspost/cpuid/v2 v2.2.3 // indirect
|
|
||||||
github.com/klauspost/pgzip v1.2.5 // indirect
|
github.com/klauspost/pgzip v1.2.5 // indirect
|
||||||
github.com/launchdarkly/ccache v1.1.0 // indirect
|
|
||||||
github.com/launchdarkly/eventsource v1.6.2 // indirect
|
|
||||||
github.com/launchdarkly/go-jsonstream/v3 v3.0.0 // indirect
|
|
||||||
github.com/launchdarkly/go-sdk-common/v3 v3.0.1 // indirect
|
|
||||||
github.com/launchdarkly/go-sdk-events/v2 v2.0.2 // indirect
|
|
||||||
github.com/launchdarkly/go-semver v1.0.2 // indirect
|
|
||||||
github.com/launchdarkly/go-server-sdk-evaluation/v2 v2.0.2 // indirect
|
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
|
||||||
github.com/mailru/easyjson v0.7.7 // indirect
|
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-localereader v0.0.1 // indirect
|
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
|
||||||
github.com/microcosm-cc/bluemonday v1.0.23 // indirect
|
|
||||||
github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 // indirect
|
|
||||||
github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 // indirect
|
|
||||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||||
github.com/montanaflynn/stats v0.7.0 // indirect
|
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
|
||||||
github.com/mtibben/percent v0.2.1 // indirect
|
|
||||||
github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70 // indirect
|
|
||||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
|
||||||
github.com/muesli/termenv v0.15.2 // indirect
|
|
||||||
github.com/nwaples/rardecode/v2 v2.0.0-beta.2 // indirect
|
github.com/nwaples/rardecode/v2 v2.0.0-beta.2 // indirect
|
||||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
|
||||||
github.com/onsi/ginkgo v1.16.5 // indirect
|
github.com/onsi/ginkgo v1.16.5 // indirect
|
||||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
github.com/onsi/gomega v1.23.0 // indirect
|
||||||
github.com/opencontainers/image-spec v1.1.0-rc3 // indirect
|
github.com/petar-dambovaliev/aho-corasick v0.0.0-20211021192214-5ab2d9280aa9 // indirect
|
||||||
github.com/pierrec/lz4/v4 v4.1.18 // indirect
|
github.com/pierrec/lz4/v4 v4.1.14 // indirect
|
||||||
github.com/pjbgf/sha1cd v0.3.0 // indirect
|
github.com/pjbgf/sha1cd v0.2.3 // indirect
|
||||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
|
|
||||||
github.com/pkg/diff v0.0.0-20200914180035-5b29258ca4f7 // indirect
|
github.com/pkg/diff v0.0.0-20200914180035-5b29258ca4f7 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect
|
github.com/skeema/knownhosts v1.1.0 // indirect
|
||||||
github.com/prometheus/common v0.44.0 // indirect
|
|
||||||
github.com/prometheus/procfs v0.11.1 // indirect
|
|
||||||
github.com/rivo/uniseg v0.4.2 // indirect
|
|
||||||
github.com/sahilm/fuzzy v0.1.0 // indirect
|
|
||||||
github.com/sirupsen/logrus v1.9.1 // indirect
|
|
||||||
github.com/skeema/knownhosts v1.2.1 // indirect
|
|
||||||
github.com/therootcompany/xz v1.0.1 // indirect
|
github.com/therootcompany/xz v1.0.1 // indirect
|
||||||
github.com/ulikunitz/xz v0.5.11 // indirect
|
github.com/ulikunitz/xz v0.5.10 // indirect
|
||||||
github.com/vbatts/tar-split v0.11.3 // indirect
|
|
||||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||||
github.com/xdg-go/scram v1.1.2 // indirect
|
github.com/xdg-go/scram v1.1.1 // indirect
|
||||||
github.com/xdg-go/stringprep v1.0.4 // indirect
|
github.com/xdg-go/stringprep v1.0.3 // indirect
|
||||||
github.com/xhit/go-str2duration/v2 v2.1.0 // indirect
|
|
||||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
|
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
|
||||||
github.com/yuin/goldmark v1.5.2 // indirect
|
|
||||||
github.com/yuin/goldmark-emoji v1.0.1 // indirect
|
|
||||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||||
github.com/zeebo/xxh3 v1.0.2 // indirect
|
|
||||||
go.einride.tech/aip v0.60.0 // indirect
|
|
||||||
go.opencensus.io v0.24.0 // indirect
|
go.opencensus.io v0.24.0 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/atomic v1.7.0 // indirect
|
||||||
go4.org v0.0.0-20200411211856-f5505b9728dd // indirect
|
go.uber.org/multierr v1.6.0 // indirect
|
||||||
golang.org/x/mod v0.14.0 // indirect
|
golang.org/x/mod v0.7.0 // indirect
|
||||||
golang.org/x/net v0.19.0 // indirect
|
golang.org/x/net v0.7.0 // indirect
|
||||||
golang.org/x/sys v0.15.0 // indirect
|
golang.org/x/sys v0.5.0 // indirect
|
||||||
golang.org/x/term v0.15.0 // indirect
|
golang.org/x/time v0.3.0 // indirect
|
||||||
golang.org/x/time v0.5.0 // indirect
|
golang.org/x/tools v0.3.0 // indirect
|
||||||
golang.org/x/tools v0.16.0 // indirect
|
google.golang.org/api v0.103.0 // indirect
|
||||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
|
||||||
google.golang.org/appengine v1.6.7 // indirect
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 // indirect
|
google.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 // indirect
|
google.golang.org/grpc v1.51.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect
|
|
||||||
google.golang.org/grpc v1.59.0 // indirect
|
|
||||||
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
|
||||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# trufflesecurity/protos:1.18-0
|
# trufflesecurity/protos:1.18-0
|
||||||
|
|
||||||
FROM golang:1.21-bullseye
|
FROM golang:1.18-buster
|
||||||
|
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
ARG TARGETOS
|
ARG TARGETOS
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
set terminal png size 800,600
|
|
||||||
set output "hack/bench/versions.png"
|
|
||||||
|
|
||||||
set title "User Time vs. Version"
|
|
||||||
set xlabel "Version"
|
|
||||||
set ylabel "Average User Time (s)"
|
|
||||||
|
|
||||||
set xtics rotate by -45
|
|
||||||
|
|
||||||
plot "hack/bench/plot.txt" using 2:xtic(1) with linespoints linestyle 1 notitle
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
if [ $# -ne 2 ]; then
|
|
||||||
echo "Usage: $0 <repository to clone> <number_of_versions_back_to_test>"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Get the number of versions back to test from command line argument
|
|
||||||
num_versions="$2"
|
|
||||||
|
|
||||||
test_repo="$1"
|
|
||||||
|
|
||||||
bash hack/bench/versions.sh $test_repo $num_versions | tee hack/bench/plot.txt
|
|
||||||
|
|
||||||
gnuplot hack/bench/plot.gp
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
v3.33.0: 1.402
|
|
||||||
v3.32.2: 1.298
|
|
||||||
v3.32.1: 1.332
|
|
||||||
v3.32.0: 1.348
|
|
||||||
v3.31.6: 2.470
|
|
||||||
v3.31.5: 2.462
|
|
||||||
v3.31.4: 2.460
|
|
||||||
v3.31.3: 2.418
|
|
||||||
v3.31.2: 1.384
|
|
||||||
v3.31.1: 1.344
|
|
||||||
v3.31.0: 1.354
|
|
||||||
v3.30.0: 1.392
|
|
||||||
v3.29.1: 1.382
|
|
||||||
v3.29.0: 1.340
|
|
||||||
v3.28.7: 1.380
|
|
||||||
v3.28.6: 1.308
|
|
||||||
v3.28.5: 2.596
|
|
||||||
v3.28.4: 2.554
|
|
||||||
v3.28.3: 2.582
|
|
||||||
v3.28.1: 2.578
|
|
||||||
v3.28.2: 2.566
|
|
||||||
v3.28.0: 2.552
|
|
||||||
v3.27.1: 2.574
|
|
||||||
v3.26.0: 2.538
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 9.3 KiB |
@@ -1,80 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
if [ $# -ne 2 ]; then
|
|
||||||
echo "Usage: $0 <repository to clone> <number_of_versions_back_to_test>"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Get the number of versions back to test from command line argument
|
|
||||||
num_versions="$2"
|
|
||||||
|
|
||||||
test_repo="$1"
|
|
||||||
|
|
||||||
num_iterations=5
|
|
||||||
|
|
||||||
# Create a temporary folder to clone the repository
|
|
||||||
repo_tmp=$(mktemp -d)
|
|
||||||
# Set up a trap to remove the temporary folder on exit or failure
|
|
||||||
trap "rm -rf $repo_tmp" EXIT
|
|
||||||
# Clone the test repository to a temporary folder
|
|
||||||
git clone --quiet "$test_repo" $repo_tmp
|
|
||||||
|
|
||||||
|
|
||||||
# Get list of git tags, sorted from newest to oldest
|
|
||||||
tags=$(git tag --sort=-creatordate)
|
|
||||||
|
|
||||||
# Counter to keep track of number of tags checked out
|
|
||||||
count=0
|
|
||||||
|
|
||||||
|
|
||||||
# Loop over tags and checkout each one in turn, up to the specified number of versions
|
|
||||||
for tag in $tags
|
|
||||||
do
|
|
||||||
if [[ $count -eq $num_versions ]]; then
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Skip RC tags
|
|
||||||
if [[ $tag == *"rc"* ]]; then
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Skip alpha tags
|
|
||||||
if [[ $tag == *"alpha"* ]]; then
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Use git checkout with the quiet flag to suppress output
|
|
||||||
git checkout $tag --quiet
|
|
||||||
|
|
||||||
# Run make install with suppressed output
|
|
||||||
make install > /dev/null
|
|
||||||
|
|
||||||
# Initialize the variable to store the sum of user times
|
|
||||||
user_time_sum=0
|
|
||||||
|
|
||||||
# Run each iteration 5 times and calculate the average user time
|
|
||||||
for i in {1..$num_iterations}
|
|
||||||
do
|
|
||||||
# Run trufflehog with suppressed output and capture user time with /usr/bin/time
|
|
||||||
tmpfile=$(mktemp)
|
|
||||||
/usr/bin/time -o $tmpfile trufflehog git "file://$repo_tmp" --no-verification --no-update >/dev/null 2>&1
|
|
||||||
time_output=$(cat $tmpfile)
|
|
||||||
rm $tmpfile
|
|
||||||
|
|
||||||
# Extract the user time from the output
|
|
||||||
user_time=$(echo $time_output | awk '{print $3}')
|
|
||||||
|
|
||||||
# Add the user time to the sum
|
|
||||||
user_time_sum=$(echo "$user_time_sum + $user_time" | bc)
|
|
||||||
done
|
|
||||||
|
|
||||||
# Calculate the average user time
|
|
||||||
average_user_time=$(echo "scale=3; $user_time_sum / $num_iterations" | bc)
|
|
||||||
|
|
||||||
# Print the average user time output for this iteration in the specified format
|
|
||||||
echo "$tag: $average_user_time"
|
|
||||||
|
|
||||||
# Increment the counter
|
|
||||||
count=$((count+1))
|
|
||||||
done
|
|
||||||
@@ -44,18 +44,6 @@ If you think that something should be included outside of these guidelines, plea
|
|||||||
- Go 1.17+
|
- Go 1.17+
|
||||||
- Make
|
- Make
|
||||||
|
|
||||||
### Adding New Token Formats to an Existing Scanner
|
|
||||||
|
|
||||||
In some instances, services will update their token format, requiring a new regex to properly detect secrets in addition to supporting the previous token format. Accomodating this can be done without adding a net-new detector. [We provide a `Versioner` interface](https://github.com/trufflesecurity/trufflehog/blob/e18cfd5e0af1469a9f05b8d5732bcc94c39da49c/pkg/detectors/detectors.go#L30) that can be implemented.
|
|
||||||
|
|
||||||
1. Create a copy of the package and append `_v2` to the package and file names. Ex: `<packagename>/` -> `<packagename>_v2`, `<packagename>.go` -> `<packagename>_v2.go`
|
|
||||||
|
|
||||||
Note: Be sure to update the tests to reference the new secret values in GSM, or the tests will fail.
|
|
||||||
|
|
||||||
2. Implement the `Versioner` interface. [GitHub example implementation.](/pkg/detectors/github_old/github_old.go#L22)
|
|
||||||
|
|
||||||
3. Proceed from step 3 of [Creating a new Secret Scanner](#creating-a-new-secret-scanner)
|
|
||||||
|
|
||||||
### Creating a new Secret Scanner
|
### Creating a new Secret Scanner
|
||||||
|
|
||||||
1. Identify the Secret Detector name from the [/proto/detectors.proto](/proto/detectors.proto) `DetectorType` enum.
|
1. Identify the Secret Detector name from the [/proto/detectors.proto](/proto/detectors.proto) `DetectorType` enum.
|
||||||
@@ -74,31 +62,15 @@ Note: Be sure to update the tests to reference the new secret values in GSM, or
|
|||||||
1. Add the test secret to GCP Secrets. See [managing test secrets](#managing-test-secrets)
|
1. Add the test secret to GCP Secrets. See [managing test secrets](#managing-test-secrets)
|
||||||
2. Update the pattern regex and keywords. Try iterating with [regex101.com](http://regex101.com/).
|
2. Update the pattern regex and keywords. Try iterating with [regex101.com](http://regex101.com/).
|
||||||
3. Update the verifier code to use a non-destructive API call that can determine whether the secret is valid or not.
|
3. 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).
|
|
||||||
4. Update the tests with these test cases at minimum:
|
4. Update the tests with these test cases at minimum:
|
||||||
1. Found and verified (using a credential loaded from GCP Secrets)
|
1. Found and verified (using a credential loaded from GCP Secrets)
|
||||||
2. Found and unverified (determinately, i.e. the secret is invalid)
|
2. Found and unverified
|
||||||
3. Found and unverified (indeterminately due to timeout)
|
3. Not found
|
||||||
4. Found and unverified (indeterminately due to an unexpected API response)
|
4. Any false positive cases that you come across
|
||||||
5. Not found
|
|
||||||
6. Any false positive cases that you come across
|
|
||||||
5. Create a merge request for review. CI tests must be passing.
|
5. Create a merge request for review. CI tests must be passing.
|
||||||
|
|
||||||
## Addendum
|
## Addendum
|
||||||
|
|
||||||
### Verification indeterminacy
|
|
||||||
|
|
||||||
There are two types of reasons that secret verification can fail:
|
|
||||||
* The candidate secret is not actually a valid secret.
|
|
||||||
* Something went wrong in the process unrelated to the candidate secret, such as a transient network error or an unexpected API response.
|
|
||||||
|
|
||||||
In Trufflehog parlance, the first type of verification response is called _determinate_ and the second type is called _indeterminate_. Verification code should distinguish between the two by returning an error object in the result struct **only** for indeterminate failures. In general, a verifier should return an error (indicating an indeterminate failure) in all cases that haven't been explicitly identified as determinate failure states.
|
|
||||||
|
|
||||||
For example, consider a hypothetical authentication endpoint that returns `200 OK` for valid credentials and `403 Forbidden` for invalid credentials. The verifier for this endpoint could make an HTTP request and use the response status code to decide what to return:
|
|
||||||
* A `200` response would indicate that verification succeeded. (Or maybe any `2xx` response.)
|
|
||||||
* A `403` response would indicate that verification failed **determinately** and no error object should be returned.
|
|
||||||
* Any other response would indicate that verification failed **indeterminately** and an error object should be returned.
|
|
||||||
|
|
||||||
### Managing Test Secrets
|
### Managing Test Secrets
|
||||||
|
|
||||||
Do not embed test credentials in the test code. Instead, use GCP Secrets Manager.
|
Do not embed test credentials in the test code. Instead, use GCP Secrets Manager.
|
||||||
@@ -108,7 +80,7 @@ Do not embed test credentials in the test code. Instead, use GCP Secrets Manager
|
|||||||
Note: `/tmp/s` is a valid path on Linux. You will need to change that for Windows or OSX, otherwise you will see an error. On Windows you will also need to install [WSL](https://docs.microsoft.com/en-us/windows/wsl/install).
|
Note: `/tmp/s` is a valid path on Linux. You will need to change that for Windows or OSX, otherwise you will see an error. On Windows you will also need to install [WSL](https://docs.microsoft.com/en-us/windows/wsl/install).
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
gcloud secrets versions access --project trufflehog-testing --secret detectors5 latest > /tmp/s
|
gcloud secrets versions access --project trufflehog-testing --secret detectors3 latest > /tmp/s
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Add the secret that you need for testing.
|
2. Add the secret that you need for testing.
|
||||||
@@ -125,9 +97,8 @@ Do not embed test credentials in the test code. Instead, use GCP Secrets Manager
|
|||||||
3. Update the secret version with your modification.
|
3. Update the secret version with your modification.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
gcloud secrets versions add --project trufflehog-testing detectors5 --data-file /tmp/s
|
gcloud secrets versions add --project trufflehog-testing detectors3 --data-file /tmp/s
|
||||||
```
|
```
|
||||||
Note: We increment the detectors file name `detectors(n+1)` once the previous one exceeds the max size allowed by GSM (65kb).
|
|
||||||
|
|
||||||
4. Access the secret value as shown in the [example code](pkg/detectors/heroku/heroku_test.go).
|
4. Access the secret value as shown in the [example code](pkg/detectors/heroku/heroku_test.go).
|
||||||
|
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ The purpose of Secret Detectors is to discover secrets with exceptionally high s
|
|||||||
+ [Sourcing Guidelines](#sourcing-guidelines)
|
+ [Sourcing Guidelines](#sourcing-guidelines)
|
||||||
+ [Development Guidelines](#development-guidelines)
|
+ [Development Guidelines](#development-guidelines)
|
||||||
+ [Development Dependencies](#development-dependencies)
|
+ [Development Dependencies](#development-dependencies)
|
||||||
+ [Creating a new Secret Detector](#creating-a-new-secret-detector)
|
+ [Creating a new Secret Scanner](#creating-a-new-secret-scanner)
|
||||||
+ [Testing the Detector](#testing-the-detector)
|
|
||||||
* [Addendum](#addendum)
|
* [Addendum](#addendum)
|
||||||
|
+ [Using a test secret file](#using-a-test-secret-file)
|
||||||
+ [Adding Protos in Windows](#adding-protos-in-windows)
|
+ [Adding Protos in Windows](#adding-protos-in-windows)
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
@@ -40,48 +40,38 @@ If you think that something should be included outside of these guidelines, plea
|
|||||||
- Go 1.17+
|
- Go 1.17+
|
||||||
- Make
|
- Make
|
||||||
|
|
||||||
### Adding New Token Formats to an Existing Scanner
|
### Creating a new Secret Scanner
|
||||||
|
|
||||||
In some instances, services will update their token format, requiring a new regex to properly detect secrets in addition to supporting the previous token format. Accomodating this can be done without adding a net-new detector. [We provide a `Versioner` interface](https://github.com/trufflesecurity/trufflehog/blob/e18cfd5e0af1469a9f05b8d5732bcc94c39da49c/pkg/detectors/detectors.go#L30) that can be implemented.
|
1. Identify the Secret Detector name from the [/proto/detectors.proto](/proto/detectors.proto) `DetectorType` enum. If necessary, run `make protos` when adding new ones.
|
||||||
|
|
||||||
1. Create a copy of the package and append `_v2` to the package and file names. Ex: `<packagename>/` -> `<packagename>_v2`, `<packagename>.go` -> `<packagename>_v2.go`
|
2. Generate the Secret Detector
|
||||||
|
|
||||||
Note: Be sure to update the tests to reference the new secret values in GSM, or the tests will fail.
|
|
||||||
|
|
||||||
2. Implement the `Versioner` interface. [GitHub example implementation.](/pkg/detectors/github_old/github_old.go#L22)
|
|
||||||
|
|
||||||
3. Proceed from step 3 of [Creating a new Secret Scanner](#creating-a-new-secret-scanner)
|
|
||||||
|
|
||||||
### Creating a new Secret Detector
|
|
||||||
|
|
||||||
1. Add a new Secret Detector enum to the [`DetectorType` list here](/proto/detectors.proto).
|
|
||||||
|
|
||||||
2. Run `make protos` to update the `.pb` files.
|
|
||||||
|
|
||||||
3. Generate the Secret Detector
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
go run hack/generate/generate.go detector <DetectorType enum name>
|
go run hack/generate/generate.go detector <DetectorType enum name>
|
||||||
example: go run hack/generate/generate.go detector SampleAPI
|
|
||||||
```
|
```
|
||||||
4. Add the Secret Detector to TruffleHog's Default Detectors
|
3. Add Secret Scanner
|
||||||
|
|
||||||
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
|
Add the secret scanner to the `pkg/engine/defaults.go` file like `github.com/trufflesecurity/trufflehog/v3/pkg/detectors/<detector_name>` and
|
||||||
[`<detector_name>.Scanner{}`](https://github.com/trufflesecurity/trufflehog/blob/b71ea27a696bdf1c3141f637fda4ee4936c2f2d6/pkg/engine/defaults.go#L1546)
|
`<detector_name>.Scanner{},`
|
||||||
|
|
||||||
5. Complete the Secret Detector.
|
4. Complete the secret detector.
|
||||||
|
|
||||||
The previous step templated a boilerplate + some example code as a package in the `pkg/detectors` folder for you to work on.
|
The previous step templated a boilerplate + some example code as a package in the `pkg/detectors` folder for you to work on.
|
||||||
The Secret Detector can be completed with these general steps:
|
The secret detector can be completed with these general steps:
|
||||||
|
|
||||||
1. Update the pattern regex and keywords. Try iterating with [regex101.com](http://regex101.com/).
|
1. Create a [test secrets file, and export the variable](#using-a-test-secret-file)
|
||||||
2. Update the verifier code to use a non-destructive API call that can determine whether the secret is valid or not.
|
2. Update the pattern regex and keywords. Try iterating with [regex101.com](http://regex101.com/).
|
||||||
* Make sure you understand [verification indeterminacy](#verification-indeterminacy).
|
3. Update the verifier code to use a non-destructive API call that can determine whether the secret is valid or not.
|
||||||
3. Create a [test for the detector](#testing-the-detector)
|
4. Update the tests with these test cases at minimum:
|
||||||
4. Create a pull request for review.
|
1. Found and verified (using a credential loaded from GCP Secrets)
|
||||||
|
2. Found and unverified
|
||||||
|
3. Not found
|
||||||
|
4. Any false positive cases that you come across
|
||||||
|
5. Create a pull request for review.
|
||||||
|
|
||||||
### Testing the Detector
|
## Addendum
|
||||||
To ensure the quality of your PR, make sure your tests are passing with verified credentials.
|
|
||||||
|
### Using a test secret file
|
||||||
|
|
||||||
1. Create a file called `.env` with this env file format:
|
1. Create a file called `.env` with this env file format:
|
||||||
|
|
||||||
@@ -95,49 +85,8 @@ To ensure the quality of your PR, make sure your tests are passing with verified
|
|||||||
```bash
|
```bash
|
||||||
export TEST_SECRET_FILE=".env"
|
export TEST_SECRET_FILE=".env"
|
||||||
```
|
```
|
||||||
The `.env` file should be in the new detector's directory like this:
|
|
||||||
```
|
|
||||||
├── tailscale
|
|
||||||
│ ├── .env
|
|
||||||
│ ├── tailscale.go
|
|
||||||
│ └── tailscale_test.go
|
|
||||||
```
|
|
||||||
Now that a `.env` file is present, the test file can load secrets locally.
|
|
||||||
|
|
||||||
3. Next, update the tests as necessary. A test file has already been generated by the `go run hack/generate/generate.go` command from earlier. There are 5 cases that have been generated:
|
Now, the detector test should attempt to load the given env key from that file.
|
||||||
1. Found and verified (using a credential loaded from the .env file)
|
|
||||||
2. Found and unverified (determinately, i.e. the secret is invalid)
|
|
||||||
3. Found and unverified (indeterminately due to timeout)
|
|
||||||
4. Found and unverified (indeterminately due to an unexpected API response)
|
|
||||||
5. Not found
|
|
||||||
|
|
||||||
Make any necessary updates to the tests. Note there might not be any changes required as the tests generated by the `go run hack/generate/generate.go` command are pretty good.
|
|
||||||
[Here is an exemplary test file for a detector which covers all 5 test cases](https://github.com/trufflesecurity/trufflehog/blob/6f9065b0aae981133a7fa3431c17a5c6213be226/pkg/detectors/browserstack/browserstack_test.go).
|
|
||||||
|
|
||||||
4. Now run the tests and check to make sure they are passing ✔️!
|
|
||||||
```bash
|
|
||||||
go test ./pkg/detectors/<detector> -tags=detectors
|
|
||||||
```
|
|
||||||
|
|
||||||
If the tests are passing, feel free to open a PR!
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Addendum
|
|
||||||
|
|
||||||
### Verification indeterminacy
|
|
||||||
|
|
||||||
There are two types of reasons that secret verification can fail:
|
|
||||||
* The candidate secret is not actually a valid secret.
|
|
||||||
* Something went wrong in the process unrelated to the candidate secret, such as a transient network error or an unexpected API response.
|
|
||||||
|
|
||||||
In Trufflehog parlance, the first type of verification response is called _determinate_ and the second type is called _indeterminate_. Verification code should distinguish between the two by returning an error object in the result struct **only** for indeterminate failures. In general, a verifier should return an error (indicating an indeterminate failure) in all cases that haven't been explicitly identified as determinate failure states.
|
|
||||||
|
|
||||||
For example, consider a hypothetical authentication endpoint that returns `200 OK` for valid credentials and `403 Forbidden` for invalid credentials. The verifier for this endpoint could make an HTTP request and use the response status code to decide what to return:
|
|
||||||
* A `200` response would indicate that verification succeeded. (Or maybe any `2xx` response.)
|
|
||||||
* A `403` response would indicate that verification failed **determinately** and no error object should be returned.
|
|
||||||
* Any other response would indicate that verification failed **indeterminately** and an error object should be returned.
|
|
||||||
|
|
||||||
### Adding Protos in Windows
|
### Adding Protos in Windows
|
||||||
|
|
||||||
@@ -156,3 +105,7 @@ For example, consider a hypothetical authentication endpoint that returns `200 O
|
|||||||
make protos
|
make protos
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Testing a detector
|
||||||
|
```bash
|
||||||
|
go test ./pkg/detectors/<detector> -tags=detectors
|
||||||
|
```
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/alecthomas/kingpin/v2"
|
|
||||||
"github.com/go-errors/errors"
|
"github.com/go-errors/errors"
|
||||||
"golang.org/x/text/cases"
|
"golang.org/x/text/cases"
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
|
"gopkg.in/alecthomas/kingpin.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -34,14 +34,14 @@ func main() {
|
|||||||
case "detector":
|
case "detector":
|
||||||
mustWriteTemplates([]templateJob{
|
mustWriteTemplates([]templateJob{
|
||||||
{
|
{
|
||||||
TemplatePath: "pkg/detectors/alchemy/alchemy.go",
|
TemplatePath: "pkg/detectors/heroku/heroku.go",
|
||||||
WritePath: filepath.Join(folderPath(), nameLower+".go"),
|
WritePath: filepath.Join(folderPath(), nameLower+".go"),
|
||||||
ReplaceString: []string{"alchemy"},
|
ReplaceString: []string{"heroku"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
TemplatePath: "pkg/detectors/alchemy/alchemy_test.go",
|
TemplatePath: "pkg/detectors/heroku/heroku_test.go",
|
||||||
WritePath: filepath.Join(folderPath(), nameLower+"_test.go"),
|
WritePath: filepath.Join(folderPath(), nameLower+"_test.go"),
|
||||||
ReplaceString: []string{"alchemy"},
|
ReplaceString: []string{"heroku"},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
// case "source":
|
// case "source":
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ import (
|
|||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/alecthomas/kingpin/v2"
|
|
||||||
"github.com/paulbellamy/ratecounter"
|
"github.com/paulbellamy/ratecounter"
|
||||||
"golang.org/x/sync/semaphore"
|
"golang.org/x/sync/semaphore"
|
||||||
|
"gopkg.in/alecthomas/kingpin.v2"
|
||||||
|
|
||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
|
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
|
||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/decoders"
|
"github.com/trufflesecurity/trufflehog/v3/pkg/decoders"
|
||||||
@@ -74,7 +74,7 @@ func main() {
|
|||||||
selectedScanners := map[string]detectors.Detector{}
|
selectedScanners := map[string]detectors.Detector{}
|
||||||
allScanners := getAllScanners()
|
allScanners := getAllScanners()
|
||||||
|
|
||||||
allDecoders := decoders.DefaultDecoders()
|
decoders := decoders.DefaultDecoders()
|
||||||
|
|
||||||
input := strings.ToLower(*scanCmdDetector)
|
input := strings.ToLower(*scanCmdDetector)
|
||||||
if input == "all" {
|
if input == "all" {
|
||||||
@@ -121,7 +121,7 @@ func main() {
|
|||||||
|
|
||||||
for chunk := range chunksChan {
|
for chunk := range chunksChan {
|
||||||
for name, scanner := range selectedScanners {
|
for name, scanner := range selectedScanners {
|
||||||
for _, dec := range allDecoders {
|
for _, dec := range decoders {
|
||||||
decoded := dec.FromChunk(&sources.Chunk{Data: chunk.Data})
|
decoded := dec.FromChunk(&sources.Chunk{Data: chunk.Data})
|
||||||
if decoded != nil {
|
if decoded != nil {
|
||||||
foundKeyword := false
|
foundKeyword := false
|
||||||
@@ -204,7 +204,7 @@ func main() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
logger.Info("scanning repo", "repo", r)
|
logger.Info("scanning repo", "repo", r)
|
||||||
err = s.ScanRepo(ctx, repo, path, git.NewScanOptions(), sources.ChanReporter{Ch: chunksChan})
|
err = s.ScanRepo(ctx, repo, path, git.NewScanOptions(), chunksChan)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logFatal(err, "error scanning repo")
|
logFatal(err, "error scanning repo")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,15 +9,14 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/alecthomas/kingpin/v2"
|
|
||||||
"github.com/felixge/fgprof"
|
"github.com/felixge/fgprof"
|
||||||
"github.com/go-logr/logr"
|
"github.com/go-logr/logr"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
"github.com/jpillora/overseer"
|
"github.com/jpillora/overseer"
|
||||||
"github.com/mattn/go-isatty"
|
"gopkg.in/alecthomas/kingpin.v2"
|
||||||
"google.golang.org/protobuf/types/known/anypb"
|
|
||||||
|
|
||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/cleantemp"
|
|
||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
|
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
|
||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/config"
|
"github.com/trufflesecurity/trufflehog/v3/pkg/config"
|
||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
|
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
|
||||||
@@ -27,34 +26,30 @@ import (
|
|||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/handlers"
|
"github.com/trufflesecurity/trufflehog/v3/pkg/handlers"
|
||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/log"
|
"github.com/trufflesecurity/trufflehog/v3/pkg/log"
|
||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/output"
|
"github.com/trufflesecurity/trufflehog/v3/pkg/output"
|
||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb"
|
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
|
||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/sources"
|
"github.com/trufflesecurity/trufflehog/v3/pkg/sources"
|
||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui"
|
"github.com/trufflesecurity/trufflehog/v3/pkg/sources/git"
|
||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/updater"
|
"github.com/trufflesecurity/trufflehog/v3/pkg/updater"
|
||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/version"
|
"github.com/trufflesecurity/trufflehog/v3/pkg/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
cli = kingpin.New("TruffleHog", "TruffleHog is a tool for finding credentials.")
|
cli = kingpin.New("TruffleHog", "TruffleHog is a tool for finding credentials.")
|
||||||
cmd string
|
cmd string
|
||||||
debug = cli.Flag("debug", "Run in debug mode.").Bool()
|
debug = cli.Flag("debug", "Run in debug mode.").Bool()
|
||||||
trace = cli.Flag("trace", "Run in trace mode.").Bool()
|
trace = cli.Flag("trace", "Run in trace mode.").Bool()
|
||||||
profile = cli.Flag("profile", "Enables profiling and sets a pprof and fgprof server on :18066.").Bool()
|
profile = cli.Flag("profile", "Enables profiling and sets a pprof and fgprof server on :18066.").Bool()
|
||||||
localDev = cli.Flag("local-dev", "Hidden feature to disable overseer for local dev.").Hidden().Bool()
|
jsonOut = cli.Flag("json", "Output in JSON format.").Short('j').Bool()
|
||||||
jsonOut = cli.Flag("json", "Output in JSON format.").Short('j').Bool()
|
jsonLegacy = cli.Flag("json-legacy", "Use the pre-v3.0 JSON format. Only works with git, gitlab, and github sources.").Bool()
|
||||||
jsonLegacy = cli.Flag("json-legacy", "Use the pre-v3.0 JSON format. Only works with git, gitlab, and github sources.").Bool()
|
concurrency = cli.Flag("concurrency", "Number of concurrent workers.").Default(strconv.Itoa(runtime.NumCPU())).Int()
|
||||||
gitHubActionsFormat = cli.Flag("github-actions", "Output in GitHub Actions format.").Bool()
|
noVerification = cli.Flag("no-verification", "Don't verify the results.").Bool()
|
||||||
concurrency = cli.Flag("concurrency", "Number of concurrent workers.").Default(strconv.Itoa(runtime.NumCPU())).Int()
|
onlyVerified = cli.Flag("only-verified", "Only output verified results.").Bool()
|
||||||
noVerification = cli.Flag("no-verification", "Don't verify the results.").Bool()
|
filterUnverified = cli.Flag("filter-unverified", "Only output first unverified result per chunk per detector if there are more than one results.").Bool()
|
||||||
onlyVerified = cli.Flag("only-verified", "Only output verified results.").Bool()
|
configFilename = cli.Flag("config", "Path to configuration file.").ExistingFile()
|
||||||
filterUnverified = cli.Flag("filter-unverified", "Only output first unverified result per chunk per detector if there are more than one results.").Bool()
|
|
||||||
filterEntropy = cli.Flag("filter-entropy", "Filter unverified results with Shannon entropy. Start with 3.0.").Float64()
|
|
||||||
configFilename = cli.Flag("config", "Path to configuration file.").ExistingFile()
|
|
||||||
// rules = cli.Flag("rules", "Path to file with custom rules.").String()
|
// rules = cli.Flag("rules", "Path to file with custom rules.").String()
|
||||||
printAvgDetectorTime = cli.Flag("print-avg-detector-time", "Print the average time spent on each detector.").Bool()
|
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()
|
noUpdate = cli.Flag("no-update", "Don't check for updates.").Bool()
|
||||||
fail = cli.Flag("fail", "Exit with code 183 if results are found.").Bool()
|
fail = cli.Flag("fail", "Exit with code 183 if results are found.").Bool()
|
||||||
verifiers = cli.Flag("verifier", "Set custom verification endpoints.").StringMap()
|
|
||||||
archiveMaxSize = cli.Flag("archive-max-size", "Maximum size of archive to scan. (Byte units eg. 512B, 2KB, 4MB)").Bytes()
|
archiveMaxSize = cli.Flag("archive-max-size", "Maximum size of archive to scan. (Byte units eg. 512B, 2KB, 4MB)").Bytes()
|
||||||
archiveMaxDepth = cli.Flag("archive-max-depth", "Maximum depth of archive to scan.").Int()
|
archiveMaxDepth = cli.Flag("archive-max-depth", "Maximum depth of archive to scan.").Int()
|
||||||
archiveTimeout = cli.Flag("archive-timeout", "Maximum time to spend extracting an archive.").Duration()
|
archiveTimeout = cli.Flag("archive-timeout", "Maximum time to spend extracting an archive.").Duration()
|
||||||
@@ -65,29 +60,24 @@ var (
|
|||||||
gitScanURI = gitScan.Arg("uri", "Git repository URL. https://, file://, or ssh:// schema expected.").Required().String()
|
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()
|
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()
|
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()
|
gitScanSinceCommit = gitScan.Flag("since-commit", "Commit to start scan from.").String()
|
||||||
gitScanBranch = gitScan.Flag("branch", "Branch to scan.").String()
|
gitScanBranch = gitScan.Flag("branch", "Branch to scan.").String()
|
||||||
gitScanMaxDepth = gitScan.Flag("max-depth", "Maximum depth of commits to scan.").Int()
|
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("allow", "No-op flag for backwards compat.").Bool()
|
||||||
_ = gitScan.Flag("entropy", "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.Flag("regex", "No-op flag for backwards compat.").Bool()
|
||||||
|
|
||||||
githubScan = cli.Command("github", "Find credentials in GitHub repositories.")
|
githubScan = cli.Command("github", "Find credentials in GitHub repositories.")
|
||||||
githubScanEndpoint = githubScan.Flag("endpoint", "GitHub endpoint.").Default("https://api.github.com").String()
|
githubScanEndpoint = githubScan.Flag("endpoint", "GitHub endpoint.").Default("https://api.github.com").String()
|
||||||
githubScanRepos = githubScan.Flag("repo", `GitHub repository to scan. You can repeat this flag. Example: "https://github.com/dustin-decker/secretsandstuff"`).Strings()
|
githubScanRepos = githubScan.Flag("repo", `GitHub repository to scan. You can repeat this flag. Example: "https://github.com/dustin-decker/secretsandstuff"`).Strings()
|
||||||
githubScanOrgs = githubScan.Flag("org", `GitHub organization to scan. You can repeat this flag. Example: "trufflesecurity"`).Strings()
|
githubScanOrgs = githubScan.Flag("org", `GitHub organization to scan. You can repeat this flag. Example: "trufflesecurity"`).Strings()
|
||||||
githubScanToken = githubScan.Flag("token", "GitHub token. Can be provided with environment variable GITHUB_TOKEN.").Envar("GITHUB_TOKEN").String()
|
githubScanToken = githubScan.Flag("token", "GitHub token. Can be provided with environment variable GITHUB_TOKEN.").Envar("GITHUB_TOKEN").String()
|
||||||
githubIncludeForks = githubScan.Flag("include-forks", "Include forks in scan.").Bool()
|
githubIncludeForks = githubScan.Flag("include-forks", "Include forks in scan.").Bool()
|
||||||
githubIncludeMembers = githubScan.Flag("include-members", "Include organization member repositories in scan.").Bool()
|
githubIncludeMembers = githubScan.Flag("include-members", "Include organization member repositories in scan.").Bool()
|
||||||
githubIncludeRepos = githubScan.Flag("include-repos", `Repositories to include in an org scan. This can also be a glob pattern. You can repeat this flag. Must use Github repo full name. Example: "trufflesecurity/trufflehog", "trufflesecurity/t*"`).Strings()
|
githubIncludeRepos = githubScan.Flag("include-repos", `Repositories to include in an org scan. This can also be a glob pattern. You can repeat this flag. Must use Github repo full name. Example: "trufflesecurity/trufflehog", "trufflesecurity/t*"`).Strings()
|
||||||
githubExcludeRepos = githubScan.Flag("exclude-repos", `Repositories to exclude in an org scan. This can also be a glob pattern. You can repeat this flag. Must use Github repo full name. Example: "trufflesecurity/driftwood", "trufflesecurity/d*"`).Strings()
|
githubExcludeRepos = githubScan.Flag("exclude-repos", `Repositories to exclude in an org scan. This can also be a glob pattern. You can repeat this flag. Must use Github repo full name. Example: "trufflesecurity/driftwood", "trufflesecurity/d*"`).Strings()
|
||||||
githubScanIncludePaths = githubScan.Flag("include-paths", "Path to file with newline separated regexes for files to include in scan.").Short('i').String()
|
githubScanIncludePaths = githubScan.Flag("include-paths", "Path to file with newline separated regexes for files to include in scan.").Short('i').String()
|
||||||
githubScanExcludePaths = githubScan.Flag("exclude-paths", "Path to file with newline separated regexes for files to exclude in scan.").Short('x').String()
|
githubScanExcludePaths = githubScan.Flag("exclude-paths", "Path to file with newline separated regexes for files to exclude in scan.").Short('x').String()
|
||||||
githubScanIssueComments = githubScan.Flag("issue-comments", "Include issue descriptions and comments in scan.").Bool()
|
|
||||||
githubScanPRComments = githubScan.Flag("pr-comments", "Include pull request descriptions and comments in scan.").Bool()
|
|
||||||
githubScanGistComments = githubScan.Flag("gist-comments", "Include gist comments in scan.").Bool()
|
|
||||||
|
|
||||||
gitlabScan = cli.Command("gitlab", "Find credentials in GitLab repositories.")
|
gitlabScan = cli.Command("gitlab", "Find credentials in GitLab repositories.")
|
||||||
// TODO: Add more GitLab options
|
// TODO: Add more GitLab options
|
||||||
@@ -106,26 +96,11 @@ var (
|
|||||||
filesystemScanIncludePaths = filesystemScan.Flag("include-paths", "Path to file with newline separated regexes for files to include in scan.").Short('i').String()
|
filesystemScanIncludePaths = filesystemScan.Flag("include-paths", "Path to file with newline separated regexes for files to include in scan.").Short('i').String()
|
||||||
filesystemScanExcludePaths = filesystemScan.Flag("exclude-paths", "Path to file with newline separated regexes for files to exclude in scan.").Short('x').String()
|
filesystemScanExcludePaths = filesystemScan.Flag("exclude-paths", "Path to file with newline separated regexes for files to exclude in scan.").Short('x').String()
|
||||||
|
|
||||||
s3Scan = cli.Command("s3", "Find credentials in S3 buckets.")
|
s3Scan = cli.Command("s3", "Find credentials in S3 buckets.")
|
||||||
s3ScanKey = s3Scan.Flag("key", "S3 key used to authenticate. Can be provided with environment variable AWS_ACCESS_KEY_ID.").Envar("AWS_ACCESS_KEY_ID").String()
|
s3ScanKey = s3Scan.Flag("key", "S3 key used to authenticate. Can be provided with environment variable AWS_ACCESS_KEY_ID.").Envar("AWS_ACCESS_KEY_ID").String()
|
||||||
s3ScanRoleArns = s3Scan.Flag("role-arn", "Specify the ARN of an IAM role to assume for scanning. You can repeat this flag.").Strings()
|
s3ScanSecret = s3Scan.Flag("secret", "S3 secret used to authenticate. Can be provided with environment variable AWS_SECRET_ACCESS_KEY.").Envar("AWS_SECRET_ACCESS_KEY").String()
|
||||||
s3ScanSecret = s3Scan.Flag("secret", "S3 secret used to authenticate. Can be provided with environment variable AWS_SECRET_ACCESS_KEY.").Envar("AWS_SECRET_ACCESS_KEY").String()
|
s3ScanCloudEnv = s3Scan.Flag("cloud-environment", "Use IAM credentials in cloud environment.").Bool()
|
||||||
s3ScanSessionToken = s3Scan.Flag("session-token", "S3 session token used to authenticate temporary credentials. Can be provided with environment variable AWS_SESSION_TOKEN.").Envar("AWS_SESSION_TOKEN").String()
|
s3ScanBuckets = s3Scan.Flag("bucket", "Name of S3 bucket to scan. You can repeat this flag.").Strings()
|
||||||
s3ScanCloudEnv = s3Scan.Flag("cloud-environment", "Use IAM credentials in cloud environment.").Bool()
|
|
||||||
s3ScanBuckets = s3Scan.Flag("bucket", "Name of S3 bucket to scan. You can repeat this flag.").Strings()
|
|
||||||
s3ScanMaxObjectSize = s3Scan.Flag("max-object-size", "Maximum size of objects to scan. Objects larger than this will be skipped. (Byte units eg. 512B, 2KB, 4MB)").Default("250MB").Bytes()
|
|
||||||
|
|
||||||
gcsScan = cli.Command("gcs", "Find credentials in GCS buckets.")
|
|
||||||
gcsProjectID = gcsScan.Flag("project-id", "GCS project ID used to authenticate. Can NOT be used with unauth scan. Can be provided with environment variable GOOGLE_CLOUD_PROJECT.").Envar("GOOGLE_CLOUD_PROJECT").String()
|
|
||||||
gcsCloudEnv = gcsScan.Flag("cloud-environment", "Use Application Default Credentials, IAM credentials to authenticate.").Bool()
|
|
||||||
gcsServiceAccount = gcsScan.Flag("service-account", "Path to GCS service account JSON file.").ExistingFile()
|
|
||||||
gcsWithoutAuth = gcsScan.Flag("without-auth", "Scan GCS buckets without authentication. This will only work for public buckets").Bool()
|
|
||||||
gcsAPIKey = gcsScan.Flag("api-key", "GCS API key used to authenticate. Can be provided with environment variable GOOGLE_API_KEY.").Envar("GOOGLE_API_KEY").String()
|
|
||||||
gcsIncludeBuckets = gcsScan.Flag("include-buckets", "Buckets to scan. Comma separated list of buckets. You can repeat this flag. Globs are supported").Short('I').Strings()
|
|
||||||
gcsExcludeBuckets = gcsScan.Flag("exclude-buckets", "Buckets to exclude from scan. Comma separated list of buckets. Globs are supported").Short('X').Strings()
|
|
||||||
gcsIncludeObjects = gcsScan.Flag("include-objects", "Objects to scan. Comma separated list of objects. you can repeat this flag. Globs are supported").Short('i').Strings()
|
|
||||||
gcsExcludeObjects = gcsScan.Flag("exclude-objects", "Objects to exclude from scan. Comma separated list of objects. You can repeat this flag. Globs are supported").Short('x').Strings()
|
|
||||||
gcsMaxObjectSize = gcsScan.Flag("max-object-size", "Maximum size of objects to scan. Objects larger than this will be skipped. (Byte units eg. 512B, 2KB, 4MB)").Default("10MB").Bytes()
|
|
||||||
|
|
||||||
syslogScan = cli.Command("syslog", "Scan syslog")
|
syslogScan = cli.Command("syslog", "Scan syslog")
|
||||||
syslogAddress = syslogScan.Flag("address", "Address and port to listen on for syslog. Example: 127.0.0.1:514").String()
|
syslogAddress = syslogScan.Flag("address", "Address and port to listen on for syslog. Example: 127.0.0.1:514").String()
|
||||||
@@ -136,12 +111,6 @@ var (
|
|||||||
|
|
||||||
circleCiScan = cli.Command("circleci", "Scan CircleCI")
|
circleCiScan = cli.Command("circleci", "Scan CircleCI")
|
||||||
circleCiScanToken = circleCiScan.Flag("token", "CircleCI token. Can also be provided with environment variable").Envar("CIRCLECI_TOKEN").Required().String()
|
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()
|
|
||||||
|
|
||||||
travisCiScan = cli.Command("travisci", "Scan TravisCI")
|
|
||||||
travisCiScanToken = travisCiScan.Flag("token", "TravisCI token. Can also be provided with environment variable").Envar("TRAVISCI_TOKEN").Required().String()
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -154,21 +123,6 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cli.Version("trufflehog " + version.BuildVersion)
|
cli.Version("trufflehog " + version.BuildVersion)
|
||||||
|
|
||||||
// Support -h for help
|
|
||||||
cli.HelpFlag.Short('h')
|
|
||||||
|
|
||||||
if len(os.Args) <= 1 && isatty.IsTerminal(os.Stdout.Fd()) {
|
|
||||||
args := tui.Run()
|
|
||||||
if len(args) == 0 {
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Overwrite the Args slice so overseer works properly.
|
|
||||||
os.Args = os.Args[:1]
|
|
||||||
os.Args = append(os.Args, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd = kingpin.MustParse(cli.Parse(os.Args[1:]))
|
cmd = kingpin.MustParse(cli.Parse(os.Args[1:]))
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
@@ -188,12 +142,6 @@ func main() {
|
|||||||
logger, sync := log.New("trufflehog", logFormat(os.Stderr))
|
logger, sync := log.New("trufflehog", logFormat(os.Stderr))
|
||||||
// make it the default logger for contexts
|
// make it the default logger for contexts
|
||||||
context.SetDefaultLogger(logger)
|
context.SetDefaultLogger(logger)
|
||||||
|
|
||||||
if *localDev {
|
|
||||||
run(overseer.State{})
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() { _ = sync() }()
|
defer func() { _ = sync() }()
|
||||||
logFatal := logFatalFunc(logger)
|
logFatal := logFatalFunc(logger)
|
||||||
|
|
||||||
@@ -214,13 +162,8 @@ func main() {
|
|||||||
|
|
||||||
err := overseer.RunErr(updateCfg)
|
err := overseer.RunErr(updateCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logFatal(err, "error occurred with trufflehog updater 🐷")
|
logFatal(err, "error occured with trufflehog updater 🐷")
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
go cleantemp.RunCleanupLoop(ctx)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func run(state overseer.State) {
|
func run(state overseer.State) {
|
||||||
@@ -243,9 +186,9 @@ func run(state overseer.State) {
|
|||||||
|
|
||||||
if *profile {
|
if *profile {
|
||||||
go func() {
|
go func() {
|
||||||
router := http.NewServeMux()
|
router := mux.NewRouter()
|
||||||
router.Handle("/debug/pprof/", http.DefaultServeMux)
|
router.PathPrefix("/debug/pprof").Handler(http.DefaultServeMux)
|
||||||
router.Handle("/debug/fgprof", fgprof.Handler())
|
router.PathPrefix("/debug/fgprof").Handler(fgprof.Handler())
|
||||||
logger.Info("starting pprof and fgprof server on :18066 /debug/pprof and /debug/fgprof")
|
logger.Info("starting pprof and fgprof server on :18066 /debug/pprof and /debug/fgprof")
|
||||||
if err := http.ListenAndServe(":18066", router); err != nil {
|
if err := http.ListenAndServe(":18066", router); err != nil {
|
||||||
logger.Error(err, "error serving pprof and fgprof")
|
logger.Error(err, "error serving pprof and fgprof")
|
||||||
@@ -272,134 +215,87 @@ func run(state overseer.State) {
|
|||||||
handlers.SetArchiveMaxTimeout(*archiveTimeout)
|
handlers.SetArchiveMaxTimeout(*archiveTimeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build include and exclude detector sets for filtering on engine initialization.
|
// Build include and exclude detector filter sets.
|
||||||
// Exit if there was an error to inform the user of the misconfiguration.
|
var includeDetectorTypes, excludeDetectorTypes map[detectorspb.DetectorType]config.DetectorID
|
||||||
var includeDetectorSet, excludeDetectorSet map[config.DetectorID]struct{}
|
|
||||||
var detectorsWithCustomVerifierEndpoints map[config.DetectorID][]string
|
|
||||||
{
|
{
|
||||||
includeList, err := config.ParseDetectors(*includeDetectors)
|
includeList, err := config.ParseDetectors(*includeDetectors)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
// Exit if there was an error to inform the user of the misconfiguration.
|
||||||
logFatal(err, "invalid include list detector configuration")
|
logFatal(err, "invalid include list detector configuration")
|
||||||
}
|
}
|
||||||
excludeList, err := config.ParseDetectors(*excludeDetectors)
|
excludeList, err := config.ParseDetectors(*excludeDetectors)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
// Exit if there was an error to inform the user of the misconfiguration.
|
||||||
logFatal(err, "invalid exclude list detector configuration")
|
logFatal(err, "invalid exclude list detector configuration")
|
||||||
}
|
}
|
||||||
detectorsWithCustomVerifierEndpoints, err = config.ParseVerifierEndpoints(*verifiers)
|
includeDetectorTypes = detectorTypeToMap(includeList)
|
||||||
if err != nil {
|
excludeDetectorTypes = detectorTypeToMap(excludeList)
|
||||||
logFatal(err, "invalid verifier detector configuration")
|
|
||||||
}
|
|
||||||
includeDetectorSet = detectorTypeToSet(includeList)
|
|
||||||
excludeDetectorSet = detectorTypeToSet(excludeList)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify that all the user-provided detectors support the optional
|
|
||||||
// detector features.
|
|
||||||
{
|
|
||||||
if err, id := verifyDetectorsAreVersioner(includeDetectorSet); err != nil {
|
|
||||||
logFatal(err, "invalid include list detector configuration", "detector", id)
|
|
||||||
}
|
|
||||||
if err, id := verifyDetectorsAreVersioner(excludeDetectorSet); err != nil {
|
|
||||||
logFatal(err, "invalid exclude list detector configuration", "detector", id)
|
|
||||||
}
|
|
||||||
if err, id := verifyDetectorsAreVersioner(detectorsWithCustomVerifierEndpoints); err != nil {
|
|
||||||
logFatal(err, "invalid verifier detector configuration", "detector", id)
|
|
||||||
}
|
|
||||||
// Extra check for endpoint customization.
|
|
||||||
isEndpointCustomizer := engine.DefaultDetectorTypesImplementing[detectors.EndpointCustomizer]()
|
|
||||||
for id := range detectorsWithCustomVerifierEndpoints {
|
|
||||||
if _, ok := isEndpointCustomizer[id.ID]; !ok {
|
|
||||||
logFatal(
|
|
||||||
fmt.Errorf("endpoint provided but detector does not support endpoint customization"),
|
|
||||||
"invalid custom verifier endpoint detector configuration",
|
|
||||||
"detector", id,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
includeFilter := func(d detectors.Detector) bool {
|
includeFilter := func(d detectors.Detector) bool {
|
||||||
_, ok := getWithDetectorID(d, includeDetectorSet)
|
id, ok := includeDetectorTypes[d.Type()]
|
||||||
return ok
|
if id.Version == 0 {
|
||||||
}
|
return ok
|
||||||
excludeFilter := func(d detectors.Detector) bool {
|
|
||||||
_, ok := getWithDetectorID(d, excludeDetectorSet)
|
|
||||||
return !ok
|
|
||||||
}
|
|
||||||
// Abuse filter to cause a side-effect.
|
|
||||||
endpointCustomizer := func(d detectors.Detector) bool {
|
|
||||||
urls, ok := getWithDetectorID(d, detectorsWithCustomVerifierEndpoints)
|
|
||||||
if !ok {
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
id := config.GetDetectorID(d)
|
versionD, ok := d.(detectors.Versioner)
|
||||||
customizer, ok := d.(detectors.EndpointCustomizer)
|
|
||||||
if !ok {
|
if !ok {
|
||||||
// NOTE: We should never reach here due to validation above.
|
// Error: version provided but not a detectors.Versioner
|
||||||
logFatal(
|
logFatal(
|
||||||
fmt.Errorf("failed to configure a detector endpoint"),
|
fmt.Errorf("version provided but detector does not have a version"),
|
||||||
"the provided detector does not support endpoint configuration",
|
"invalid include list detector configuration",
|
||||||
"detector", id,
|
"detector", id,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// TODO: Add flag to ignore the default endpoint.
|
return versionD.Version() == id.Version
|
||||||
urls = append(urls, customizer.DefaultEndpoint())
|
}
|
||||||
if err := customizer.SetEndpoints(urls...); err != nil {
|
excludeFilter := func(d detectors.Detector) bool {
|
||||||
logFatal(err, "failed configuring custom endpoint for detector", "detector", id)
|
id, ok := excludeDetectorTypes[d.Type()]
|
||||||
|
if id.Version == 0 {
|
||||||
|
return !ok
|
||||||
}
|
}
|
||||||
logger.Info("configured detector with verification urls",
|
versionD, ok := d.(detectors.Versioner)
|
||||||
"detector", id, "urls", urls,
|
if !ok {
|
||||||
)
|
// Error: version provided but not a detectors.Versioner
|
||||||
return true
|
logFatal(
|
||||||
|
fmt.Errorf("version provided but detector does not have a version"),
|
||||||
|
"invalid exclude list detector configuration",
|
||||||
|
"detector", id,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return versionD.Version() != id.Version
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set how the engine will print its results.
|
e := engine.Start(ctx,
|
||||||
var printer engine.Printer
|
engine.WithConcurrency(*concurrency),
|
||||||
switch {
|
|
||||||
case *jsonLegacy:
|
|
||||||
printer = new(output.LegacyJSONPrinter)
|
|
||||||
case *jsonOut:
|
|
||||||
printer = new(output.JSONPrinter)
|
|
||||||
case *gitHubActionsFormat:
|
|
||||||
printer = new(output.GitHubActionsPrinter)
|
|
||||||
default:
|
|
||||||
printer = new(output.PlainPrinter)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !*jsonLegacy && !*jsonOut {
|
|
||||||
fmt.Fprintf(os.Stderr, "🐷🔑🐷 TruffleHog. Unearth your secrets. 🐷🔑🐷\n\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
e, err := engine.Start(ctx,
|
|
||||||
engine.WithConcurrency(uint8(*concurrency)),
|
|
||||||
engine.WithDecoders(decoders.DefaultDecoders()...),
|
engine.WithDecoders(decoders.DefaultDecoders()...),
|
||||||
engine.WithDetectors(engine.DefaultDetectors()...),
|
engine.WithDetectors(!*noVerification, engine.DefaultDetectors()...),
|
||||||
engine.WithDetectors(conf.Detectors...),
|
engine.WithDetectors(!*noVerification, conf.Detectors...),
|
||||||
engine.WithVerify(!*noVerification),
|
|
||||||
engine.WithFilterDetectors(includeFilter),
|
engine.WithFilterDetectors(includeFilter),
|
||||||
engine.WithFilterDetectors(excludeFilter),
|
engine.WithFilterDetectors(excludeFilter),
|
||||||
engine.WithFilterDetectors(endpointCustomizer),
|
|
||||||
engine.WithFilterUnverified(*filterUnverified),
|
engine.WithFilterUnverified(*filterUnverified),
|
||||||
engine.WithOnlyVerified(*onlyVerified),
|
|
||||||
engine.WithPrintAvgDetectorTime(*printAvgDetectorTime),
|
|
||||||
engine.WithPrinter(printer),
|
|
||||||
engine.WithFilterEntropy(*filterEntropy),
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
|
||||||
logFatal(err, "error initializing engine")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
var repoPath string
|
||||||
|
var remote bool
|
||||||
switch cmd {
|
switch cmd {
|
||||||
case gitScan.FullCommand():
|
case gitScan.FullCommand():
|
||||||
|
filter, err := common.FilterFromFiles(*gitScanIncludePaths, *gitScanExcludePaths)
|
||||||
|
if err != nil {
|
||||||
|
logFatal(err, "could not create filter")
|
||||||
|
}
|
||||||
|
repoPath, remote, err = git.PrepareRepoSinceCommit(ctx, *gitScanURI, *gitScanSinceCommit)
|
||||||
|
if err != nil || repoPath == "" {
|
||||||
|
logFatal(err, "error preparing git repo for scanning")
|
||||||
|
}
|
||||||
|
if remote {
|
||||||
|
defer os.RemoveAll(repoPath)
|
||||||
|
}
|
||||||
|
|
||||||
cfg := sources.GitConfig{
|
cfg := sources.GitConfig{
|
||||||
URI: *gitScanURI,
|
RepoPath: repoPath,
|
||||||
IncludePathsFile: *gitScanIncludePaths,
|
HeadRef: *gitScanBranch,
|
||||||
ExcludePathsFile: *gitScanExcludePaths,
|
BaseRef: *gitScanSinceCommit,
|
||||||
HeadRef: *gitScanBranch,
|
MaxDepth: *gitScanMaxDepth,
|
||||||
BaseRef: *gitScanSinceCommit,
|
Filter: filter,
|
||||||
MaxDepth: *gitScanMaxDepth,
|
|
||||||
Bare: *gitScanBare,
|
|
||||||
ExcludeGlobs: *gitScanExcludeGlobs,
|
|
||||||
}
|
}
|
||||||
if err = e.ScanGit(ctx, cfg); err != nil {
|
if err = e.ScanGit(ctx, cfg); err != nil {
|
||||||
logFatal(err, "Failed to scan Git.")
|
logFatal(err, "Failed to scan Git.")
|
||||||
@@ -414,19 +310,16 @@ func run(state overseer.State) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cfg := sources.GithubConfig{
|
cfg := sources.GithubConfig{
|
||||||
Endpoint: *githubScanEndpoint,
|
Endpoint: *githubScanEndpoint,
|
||||||
Token: *githubScanToken,
|
Token: *githubScanToken,
|
||||||
IncludeForks: *githubIncludeForks,
|
IncludeForks: *githubIncludeForks,
|
||||||
IncludeMembers: *githubIncludeMembers,
|
IncludeMembers: *githubIncludeMembers,
|
||||||
Concurrency: *concurrency,
|
Concurrency: *concurrency,
|
||||||
ExcludeRepos: *githubExcludeRepos,
|
ExcludeRepos: *githubExcludeRepos,
|
||||||
IncludeRepos: *githubIncludeRepos,
|
IncludeRepos: *githubIncludeRepos,
|
||||||
Repos: *githubScanRepos,
|
Repos: *githubScanRepos,
|
||||||
Orgs: *githubScanOrgs,
|
Orgs: *githubScanOrgs,
|
||||||
IncludeIssueComments: *githubScanIssueComments,
|
Filter: filter,
|
||||||
IncludePullRequestComments: *githubScanPRComments,
|
|
||||||
IncludeGistComments: *githubScanGistComments,
|
|
||||||
Filter: filter,
|
|
||||||
}
|
}
|
||||||
if err := e.ScanGitHub(ctx, cfg); err != nil {
|
if err := e.ScanGitHub(ctx, cfg); err != nil {
|
||||||
logFatal(err, "Failed to scan Github.")
|
logFatal(err, "Failed to scan Github.")
|
||||||
@@ -466,13 +359,10 @@ func run(state overseer.State) {
|
|||||||
}
|
}
|
||||||
case s3Scan.FullCommand():
|
case s3Scan.FullCommand():
|
||||||
cfg := sources.S3Config{
|
cfg := sources.S3Config{
|
||||||
Key: *s3ScanKey,
|
Key: *s3ScanKey,
|
||||||
Secret: *s3ScanSecret,
|
Secret: *s3ScanSecret,
|
||||||
SessionToken: *s3ScanSessionToken,
|
Buckets: *s3ScanBuckets,
|
||||||
Buckets: *s3ScanBuckets,
|
CloudCred: *s3ScanCloudEnv,
|
||||||
Roles: *s3ScanRoleArns,
|
|
||||||
CloudCred: *s3ScanCloudEnv,
|
|
||||||
MaxObjectSize: int64(*s3ScanMaxObjectSize),
|
|
||||||
}
|
}
|
||||||
if err := e.ScanS3(ctx, cfg); err != nil {
|
if err := e.ScanS3(ctx, cfg); err != nil {
|
||||||
logFatal(err, "Failed to scan S3.")
|
logFatal(err, "Failed to scan S3.")
|
||||||
@@ -493,68 +383,63 @@ func run(state overseer.State) {
|
|||||||
if err := e.ScanCircleCI(ctx, *circleCiScanToken); err != nil {
|
if err := e.ScanCircleCI(ctx, *circleCiScanToken); err != nil {
|
||||||
logFatal(err, "Failed to scan CircleCI.")
|
logFatal(err, "Failed to scan CircleCI.")
|
||||||
}
|
}
|
||||||
case travisCiScan.FullCommand():
|
}
|
||||||
if err := e.ScanTravisCI(ctx, *travisCiScanToken); err != nil {
|
// asynchronously wait for scanning to finish and cleanup
|
||||||
logFatal(err, "Failed to scan TravisCI.")
|
go e.Finish(ctx)
|
||||||
|
|
||||||
|
if !*jsonLegacy && !*jsonOut {
|
||||||
|
fmt.Fprintf(os.Stderr, "🐷🔑🐷 TruffleHog. Unearth your secrets. 🐷🔑🐷\n\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: this loop will terminate when the results channel is closed in
|
||||||
|
// e.Finish()
|
||||||
|
foundResults := false
|
||||||
|
for r := range e.ResultsChan() {
|
||||||
|
if *onlyVerified && !r.Verified {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
case gcsScan.FullCommand():
|
foundResults = true
|
||||||
cfg := sources.GCSConfig{
|
|
||||||
ProjectID: *gcsProjectID,
|
var err error
|
||||||
CloudCred: *gcsCloudEnv,
|
switch {
|
||||||
ServiceAccount: *gcsServiceAccount,
|
case *jsonLegacy:
|
||||||
WithoutAuth: *gcsWithoutAuth,
|
err = output.PrintLegacyJSON(ctx, &r)
|
||||||
ApiKey: *gcsAPIKey,
|
case *jsonOut:
|
||||||
IncludeBuckets: commaSeparatedToSlice(*gcsIncludeBuckets),
|
err = output.PrintJSON(&r)
|
||||||
ExcludeBuckets: commaSeparatedToSlice(*gcsExcludeBuckets),
|
default:
|
||||||
IncludeObjects: commaSeparatedToSlice(*gcsIncludeObjects),
|
err = output.PrintPlainOutput(&r)
|
||||||
ExcludeObjects: commaSeparatedToSlice(*gcsExcludeObjects),
|
|
||||||
Concurrency: *concurrency,
|
|
||||||
MaxObjectSize: int64(*gcsMaxObjectSize),
|
|
||||||
}
|
}
|
||||||
if err := e.ScanGCS(ctx, cfg); err != nil {
|
|
||||||
logFatal(err, "Failed to scan GCS.")
|
|
||||||
}
|
|
||||||
case dockerScan.FullCommand():
|
|
||||||
dockerConn := sourcespb.Docker{
|
|
||||||
Images: *dockerScanImages,
|
|
||||||
Credential: &sourcespb.Docker_DockerKeychain{
|
|
||||||
DockerKeychain: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
anyConn, err := anypb.New(&dockerConn)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logFatal(err, "Failed to marshal Docker connection")
|
logFatal(err, "error printing results")
|
||||||
}
|
|
||||||
if err := e.ScanDocker(ctx, anyConn); err != nil {
|
|
||||||
logFatal(err, "Failed to scan Docker.")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
logger.V(2).Info("finished scanning",
|
||||||
// Wait for all workers to finish.
|
"chunks", e.ChunksScanned(),
|
||||||
if err = e.Finish(ctx); err != nil {
|
"bytes", e.BytesScanned(),
|
||||||
logFatal(err, "engine failed to finish execution")
|
|
||||||
}
|
|
||||||
|
|
||||||
metrics := e.GetMetrics()
|
|
||||||
// Print results.
|
|
||||||
logger.Info("finished scanning",
|
|
||||||
"chunks", metrics.ChunksScanned,
|
|
||||||
"bytes", metrics.BytesScanned,
|
|
||||||
"verified_secrets", metrics.VerifiedSecretsFound,
|
|
||||||
"unverified_secrets", metrics.UnverifiedSecretsFound,
|
|
||||||
"scan_duration", metrics.ScanDuration.String(),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if *printAvgDetectorTime {
|
if *printAvgDetectorTime {
|
||||||
printAverageDetectorTime(e)
|
printAverageDetectorTime(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.HasFoundResults() && *fail {
|
if foundResults && *fail {
|
||||||
logger.V(2).Info("exiting with code 183 because results were found")
|
logger.V(2).Info("exiting with code 183 because results were found")
|
||||||
os.Exit(183)
|
os.Exit(183)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func printAverageDetectorTime(e *engine.Engine) {
|
||||||
|
fmt.Fprintln(os.Stderr, "Average detector time is the measurement of average time spent on each detector when results are returned.")
|
||||||
|
for detectorName, durations := range e.DetectorAvgTime() {
|
||||||
|
var total time.Duration
|
||||||
|
for _, d := range durations {
|
||||||
|
total += d
|
||||||
|
}
|
||||||
|
avgDuration := total / time.Duration(len(durations))
|
||||||
|
fmt.Fprintf(os.Stderr, "%s: %s\n", detectorName, avgDuration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// logFatalFunc returns a log.Fatal style function. Calling the returned
|
// logFatalFunc returns a log.Fatal style function. Calling the returned
|
||||||
// function will terminate the program without cleanup.
|
// function will terminate the program without cleanup.
|
||||||
func logFatalFunc(logger logr.Logger) func(error, string, ...any) {
|
func logFatalFunc(logger logr.Logger) func(error, string, ...any) {
|
||||||
@@ -568,67 +453,10 @@ func logFatalFunc(logger logr.Logger) func(error, string, ...any) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func commaSeparatedToSlice(s []string) []string {
|
func detectorTypeToMap(detectors []config.DetectorID) map[detectorspb.DetectorType]config.DetectorID {
|
||||||
var result []string
|
output := make(map[detectorspb.DetectorType]config.DetectorID, len(detectors))
|
||||||
for _, items := range s {
|
|
||||||
for _, item := range strings.Split(items, ",") {
|
|
||||||
item = strings.TrimSpace(item)
|
|
||||||
if item == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
result = append(result, item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func printAverageDetectorTime(e *engine.Engine) {
|
|
||||||
fmt.Fprintln(os.Stderr, "Average detector time is the measurement of average time spent on each detector when results are returned.")
|
|
||||||
for detectorName, duration := range e.GetDetectorsMetrics() {
|
|
||||||
fmt.Fprintf(os.Stderr, "%s: %s\n", detectorName, duration)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// detectorTypeToSet is a helper function to convert a slice of detector IDs into a set.
|
|
||||||
func detectorTypeToSet(detectors []config.DetectorID) map[config.DetectorID]struct{} {
|
|
||||||
out := make(map[config.DetectorID]struct{}, len(detectors))
|
|
||||||
for _, d := range detectors {
|
for _, d := range detectors {
|
||||||
out[d] = struct{}{}
|
output[d.ID] = d
|
||||||
}
|
}
|
||||||
return out
|
return output
|
||||||
}
|
|
||||||
|
|
||||||
// getWithDetectorID is a helper function to get a value from a map using a
|
|
||||||
// detector's ID. This function behaves like a normal map lookup, with an extra
|
|
||||||
// step of checking for the non-specific version of a detector.
|
|
||||||
func getWithDetectorID[T any](d detectors.Detector, data map[config.DetectorID]T) (T, bool) {
|
|
||||||
key := config.GetDetectorID(d)
|
|
||||||
// Check if the specific ID is provided.
|
|
||||||
if t, ok := data[key]; ok || key.Version == 0 {
|
|
||||||
return t, ok
|
|
||||||
}
|
|
||||||
// Check if the generic type is provided without a version.
|
|
||||||
// This means "all" versions of a type.
|
|
||||||
key.Version = 0
|
|
||||||
t, ok := data[key]
|
|
||||||
return t, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// verifyDetectorsAreVersioner checks all keys in a provided map to verify the
|
|
||||||
// provided type is actually a Versioner.
|
|
||||||
func verifyDetectorsAreVersioner[T any](data map[config.DetectorID]T) (error, config.DetectorID) {
|
|
||||||
isVersioner := engine.DefaultDetectorTypesImplementing[detectors.Versioner]()
|
|
||||||
for id := range data {
|
|
||||||
if id.Version == 0 {
|
|
||||||
// Version not provided.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if _, ok := isVersioner[id.ID]; ok {
|
|
||||||
// Version provided for a Versioner detector.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Version provided on a non-Versioner detector.
|
|
||||||
return fmt.Errorf("version provided but detector does not have a version"), id
|
|
||||||
}
|
|
||||||
return nil, config.DetectorID{}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
-24
@@ -1,24 +0,0 @@
|
|||||||
// Package cache provides an interface which can be implemented by different cache types.
|
|
||||||
package cache
|
|
||||||
|
|
||||||
// Cache is used to store key/value pairs.
|
|
||||||
type Cache interface {
|
|
||||||
// Set stores the given key/value pair.
|
|
||||||
Set(string, string)
|
|
||||||
// Get returns the value for the given key and a boolean indicating if the key was found.
|
|
||||||
Get(string) (string, bool)
|
|
||||||
// Exists returns true if the given key exists in the cache.
|
|
||||||
Exists(string) bool
|
|
||||||
// Delete the given key from the cache.
|
|
||||||
Delete(string)
|
|
||||||
// Clear all key/value pairs from the cache.
|
|
||||||
Clear()
|
|
||||||
// Count the number of key/value pairs in the cache.
|
|
||||||
Count() int
|
|
||||||
// Keys returns all keys in the cache.
|
|
||||||
Keys() []string
|
|
||||||
// Values returns all values in the cache.
|
|
||||||
Values() []string
|
|
||||||
// Contents returns all keys in the cache encoded as a string.
|
|
||||||
Contents() string
|
|
||||||
}
|
|
||||||
Vendored
-105
@@ -1,105 +0,0 @@
|
|||||||
package memory
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/patrickmn/go-cache"
|
|
||||||
|
|
||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
expirationInterval = 12 * time.Hour
|
|
||||||
purgeInterval = 13 * time.Hour
|
|
||||||
defaultExpiration = cache.DefaultExpiration
|
|
||||||
)
|
|
||||||
|
|
||||||
// Cache is a wrapper around the go-cache library.
|
|
||||||
type Cache struct {
|
|
||||||
c *cache.Cache
|
|
||||||
}
|
|
||||||
|
|
||||||
// New constructs a new in-memory cache.
|
|
||||||
func New() *Cache {
|
|
||||||
c := cache.New(expirationInterval, purgeInterval)
|
|
||||||
return &Cache{c: c}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewWithData constructs a new in-memory cache with existing data.
|
|
||||||
func NewWithData(ctx context.Context, data []string) *Cache {
|
|
||||||
ctx.Logger().V(3).Info("Loading cache", "num-items", len(data))
|
|
||||||
|
|
||||||
items := make(map[string]cache.Item, len(data))
|
|
||||||
for _, d := range data {
|
|
||||||
items[d] = cache.Item{Object: d, Expiration: int64(defaultExpiration)}
|
|
||||||
}
|
|
||||||
|
|
||||||
c := cache.NewFrom(expirationInterval, purgeInterval, items)
|
|
||||||
return &Cache{c: c}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set adds a key-value pair to the cache.
|
|
||||||
func (c *Cache) Set(key, value string) {
|
|
||||||
c.c.Set(key, value, defaultExpiration)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get returns the value for the given key.
|
|
||||||
func (c *Cache) Get(key string) (string, bool) {
|
|
||||||
res, ok := c.c.Get(key)
|
|
||||||
if !ok {
|
|
||||||
return "", ok
|
|
||||||
}
|
|
||||||
return res.(string), ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exists returns true if the given key exists in the cache.
|
|
||||||
func (c *Cache) Exists(key string) bool {
|
|
||||||
_, ok := c.c.Get(key)
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete removes the key-value pair from the cache.
|
|
||||||
func (c *Cache) Delete(key string) {
|
|
||||||
c.c.Delete(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear removes all key-value pairs from the cache.
|
|
||||||
func (c *Cache) Clear() {
|
|
||||||
c.c.Flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Count returns the number of key-value pairs in the cache.
|
|
||||||
func (c *Cache) Count() int {
|
|
||||||
return c.c.ItemCount()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keys returns all keys in the cache.
|
|
||||||
func (c *Cache) Keys() []string {
|
|
||||||
items := c.c.Items()
|
|
||||||
res := make([]string, 0, len(items))
|
|
||||||
for k := range items {
|
|
||||||
res = append(res, k)
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// Values returns all values in the cache.
|
|
||||||
func (c *Cache) Values() []string {
|
|
||||||
items := c.c.Items()
|
|
||||||
res := make([]string, 0, len(items))
|
|
||||||
for _, v := range items {
|
|
||||||
res = append(res, v.Object.(string))
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// Contents returns a comma-separated string containing all keys in the cache.
|
|
||||||
func (c *Cache) Contents() string {
|
|
||||||
items := c.c.Items()
|
|
||||||
res := make([]string, 0, len(items))
|
|
||||||
for k := range items {
|
|
||||||
res = append(res, k)
|
|
||||||
}
|
|
||||||
return strings.Join(res, ",")
|
|
||||||
}
|
|
||||||
Vendored
-169
@@ -1,169 +0,0 @@
|
|||||||
package memory
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
|
||||||
|
|
||||||
logContext "github.com/trufflesecurity/trufflehog/v3/pkg/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCache(t *testing.T) {
|
|
||||||
c := New()
|
|
||||||
|
|
||||||
// Test set and get.
|
|
||||||
c.Set("key1", "key1")
|
|
||||||
v, ok := c.Get("key1")
|
|
||||||
if !ok || v != "key1" {
|
|
||||||
t.Fatalf("Unexpected value for key1: %v, %v", v, ok)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test exists.
|
|
||||||
if !c.Exists("key1") {
|
|
||||||
t.Fatalf("Expected key1 to exist")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test the count.
|
|
||||||
if c.Count() != 1 {
|
|
||||||
t.Fatalf("Unexpected count: %d", c.Count())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test delete.
|
|
||||||
c.Delete("key1")
|
|
||||||
v, ok = c.Get("key1")
|
|
||||||
if ok || v != "" {
|
|
||||||
t.Fatalf("Unexpected value for key1 after delete: %v, %v", v, ok)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test clear.
|
|
||||||
c.Set("key10", "key10")
|
|
||||||
c.Clear()
|
|
||||||
v, ok = c.Get("key10")
|
|
||||||
if ok || v != "" {
|
|
||||||
t.Fatalf("Unexpected value for key10 after clear: %v, %v", v, ok)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test getting only the keys.
|
|
||||||
keys := []string{"key1", "key2", "key3"}
|
|
||||||
values := []string{"value1", "value2", "value3"}
|
|
||||||
for i, k := range keys {
|
|
||||||
c.Set(k, values[i])
|
|
||||||
}
|
|
||||||
k := c.Keys()
|
|
||||||
sort.Strings(keys)
|
|
||||||
sort.Strings(k)
|
|
||||||
if !cmp.Equal(keys, k) {
|
|
||||||
t.Fatalf("Unexpected keys: %v", k)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test getting only the values.
|
|
||||||
vals := c.Values()
|
|
||||||
sort.Strings(vals)
|
|
||||||
sort.Strings(values)
|
|
||||||
if !cmp.Equal(values, vals) {
|
|
||||||
t.Fatalf("Unexpected values: %v", vals)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test contents.
|
|
||||||
items := c.Contents()
|
|
||||||
sort.Strings(keys)
|
|
||||||
res := strings.Split(items, ",")
|
|
||||||
sort.Strings(res)
|
|
||||||
|
|
||||||
if len(keys) != len(res) {
|
|
||||||
t.Fatalf("Unexpected length of items: %d", len(res))
|
|
||||||
}
|
|
||||||
if !cmp.Equal(keys, res) {
|
|
||||||
t.Fatalf("Unexpected items: %v", res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCache_NewWithData(t *testing.T) {
|
|
||||||
c := NewWithData(logContext.Background(), []string{"key1", "key2", "key3"})
|
|
||||||
|
|
||||||
// Test the count.
|
|
||||||
if c.Count() != 3 {
|
|
||||||
t.Fatalf("Unexpected count: %d", c.Count())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test contents.
|
|
||||||
keys := []string{"key1", "key2", "key3"}
|
|
||||||
items := c.Contents()
|
|
||||||
sort.Strings(keys)
|
|
||||||
res := strings.Split(items, ",")
|
|
||||||
sort.Strings(res)
|
|
||||||
|
|
||||||
if len(keys) != len(res) {
|
|
||||||
t.Fatalf("Unexpected length of items: %d", len(res))
|
|
||||||
}
|
|
||||||
if !cmp.Equal(keys, res) {
|
|
||||||
t.Fatalf("Unexpected items: %v", res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupBenchmarks(b *testing.B) *Cache {
|
|
||||||
b.Helper()
|
|
||||||
|
|
||||||
c := New()
|
|
||||||
|
|
||||||
for i := 0; i < 500_000; i++ {
|
|
||||||
key := fmt.Sprintf("key%d", i)
|
|
||||||
c.Set(key, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkSet(b *testing.B) {
|
|
||||||
c := New()
|
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
key := fmt.Sprintf("key%d", i)
|
|
||||||
c.Set(key, key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkGet(b *testing.B) {
|
|
||||||
c := setupBenchmarks(b)
|
|
||||||
b.ResetTimer()
|
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
key := fmt.Sprintf("key%d", i)
|
|
||||||
c.Get(key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkDelete(b *testing.B) {
|
|
||||||
c := setupBenchmarks(b)
|
|
||||||
b.ResetTimer()
|
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
key := fmt.Sprintf("key%d", i)
|
|
||||||
c.Delete(key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkCount(b *testing.B) {
|
|
||||||
c := setupBenchmarks(b)
|
|
||||||
b.ResetTimer()
|
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
c.Count()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkContents(b *testing.B) {
|
|
||||||
c := setupBenchmarks(b)
|
|
||||||
b.ResetTimer()
|
|
||||||
|
|
||||||
var s string
|
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
s = c.Contents()
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = s
|
|
||||||
}
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
package cleantemp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/mitchellh/go-ps"
|
|
||||||
|
|
||||||
logContext "github.com/trufflesecurity/trufflehog/v3/pkg/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MkdirTemp returns a temporary directory path formatted as:
|
|
||||||
// trufflehog-<pid>-<randint>
|
|
||||||
func MkdirTemp() (string, error) {
|
|
||||||
pid := os.Getpid()
|
|
||||||
tmpdir := fmt.Sprintf("%s-%d-", "trufflehog", pid)
|
|
||||||
dir, err := os.MkdirTemp(os.TempDir(), tmpdir)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return dir, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CleanTemp is used to remove orphaned artifacts from aborted scans.
|
|
||||||
type CleanTemp interface {
|
|
||||||
// CleanTempDir removes orphaned directories from sources. ex: Git
|
|
||||||
CleanTempDir(ctx logContext.Context, dirName string, pid int) error
|
|
||||||
// CleanTempFiles removes orphaned files/artifacts from sources. ex: Artifactory
|
|
||||||
CleanTempFiles(ctx context.Context, fileName string, pid int) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only compile during startup.
|
|
||||||
var trufflehogRE = regexp.MustCompile(`^trufflehog-\d+-\d+$`)
|
|
||||||
|
|
||||||
// CleanTempDir removes orphaned temp directories that do not contain running PID values.
|
|
||||||
func CleanTempDir(ctx logContext.Context) error {
|
|
||||||
const defaultExecPath = "trufflehog"
|
|
||||||
executablePath, err := os.Executable()
|
|
||||||
if err != nil {
|
|
||||||
executablePath = defaultExecPath
|
|
||||||
}
|
|
||||||
execName := filepath.Base(executablePath)
|
|
||||||
|
|
||||||
// Finds other trufflehog PIDs that may be running
|
|
||||||
var pids []string
|
|
||||||
procs, err := ps.Processes()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error getting jobs PIDs: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, proc := range procs {
|
|
||||||
if proc.Executable() == execName {
|
|
||||||
pids = append(pids, strconv.Itoa(proc.Pid()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tempDir := os.TempDir()
|
|
||||||
dirs, err := os.ReadDir(tempDir)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error reading temp dir: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, dir := range dirs {
|
|
||||||
// Ensure that all directories match the pattern.
|
|
||||||
if trufflehogRE.MatchString(dir.Name()) {
|
|
||||||
// Mark these directories initially as ones that should be deleted.
|
|
||||||
shouldDelete := true
|
|
||||||
// If they match any live PIDs, mark as should not delete.
|
|
||||||
for _, pidval := range pids {
|
|
||||||
if strings.Contains(dir.Name(), fmt.Sprintf("-%s-", pidval)) {
|
|
||||||
shouldDelete = false
|
|
||||||
// break out so we can still delete directories even if no other Trufflehog processes are running.
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if shouldDelete {
|
|
||||||
dirPath := filepath.Join(tempDir, dir.Name())
|
|
||||||
if err := os.RemoveAll(dirPath); err != nil {
|
|
||||||
return fmt.Errorf("error deleting temp directory: %s", dirPath)
|
|
||||||
}
|
|
||||||
ctx.Logger().V(1).Info("Deleted directory", "directory", dirPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunCleanupLoop runs a loop that cleans up orphaned directories every 15 seconds
|
|
||||||
func RunCleanupLoop(ctx logContext.Context) {
|
|
||||||
if err := CleanTempDir(ctx); err != nil {
|
|
||||||
ctx.Logger().Error(err, "error cleaning up orphaned directories ")
|
|
||||||
}
|
|
||||||
|
|
||||||
const cleanupLoopInterval = 15 * time.Second
|
|
||||||
ticker := time.NewTicker(cleanupLoopInterval)
|
|
||||||
defer ticker.Stop()
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ticker.C:
|
|
||||||
if err := CleanTempDir(ctx); err != nil {
|
|
||||||
ctx.Logger().Error(err, "error cleaning up orphaned directories")
|
|
||||||
}
|
|
||||||
case <-ctx.Done():
|
|
||||||
ctx.Logger().Info("Cleanup loop exiting due to context cancellation")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
package cleantemp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/mitchellh/go-ps"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestExecName(t *testing.T) {
|
|
||||||
executablePath, err := os.Executable()
|
|
||||||
assert.Nil(t, err)
|
|
||||||
execName := filepath.Base(executablePath)
|
|
||||||
assert.Equal(t, "cleantemp.test", execName)
|
|
||||||
|
|
||||||
procs, err := ps.Processes()
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.NotEmpty(t, procs)
|
|
||||||
|
|
||||||
found := false
|
|
||||||
for _, proc := range procs {
|
|
||||||
if proc.Executable() == execName {
|
|
||||||
found = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.True(t, found)
|
|
||||||
}
|
|
||||||
@@ -10,20 +10,3 @@ func IsDone(ctx context.Context) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CancellableWrite blocks on writing the item to the channel but can be
|
|
||||||
// cancelled by the context. If both the context is cancelled and the channel
|
|
||||||
// write would succeed, either operation will be performed randomly.
|
|
||||||
func CancellableWrite[T any](ctx context.Context, ch chan<- T, item T) error {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done(): // priority to context cancellation
|
|
||||||
return ctx.Err()
|
|
||||||
default:
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return ctx.Err()
|
|
||||||
case ch <- item:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,120 +0,0 @@
|
|||||||
package glob
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/gobwas/glob"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Filter is a generic filter for excluding and including globs (limited
|
|
||||||
// regular expressions). Exclusion takes precedence if both include and exclude
|
|
||||||
// lists are provided.
|
|
||||||
type Filter struct {
|
|
||||||
exclude []glob.Glob
|
|
||||||
include []glob.Glob
|
|
||||||
}
|
|
||||||
|
|
||||||
type globFilterOpt func(*Filter) error
|
|
||||||
|
|
||||||
// WithExcludeGlobs adds exclude globs to the filter.
|
|
||||||
func WithExcludeGlobs(excludes ...string) globFilterOpt {
|
|
||||||
return func(f *Filter) error {
|
|
||||||
for _, exclude := range excludes {
|
|
||||||
g, err := glob.Compile(exclude)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid exclude glob %q: %w", exclude, err)
|
|
||||||
}
|
|
||||||
f.exclude = append(f.exclude, g)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithIncludeGlobs adds include globs to the filter.
|
|
||||||
func WithIncludeGlobs(includes ...string) globFilterOpt {
|
|
||||||
return func(f *Filter) error {
|
|
||||||
for _, include := range includes {
|
|
||||||
g, err := glob.Compile(include)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid include glob %q: %w", include, err)
|
|
||||||
}
|
|
||||||
f.include = append(f.include, g)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewGlobFilter creates a new Filter with the provided options.
|
|
||||||
func NewGlobFilter(opts ...globFilterOpt) (*Filter, error) {
|
|
||||||
filter := &Filter{}
|
|
||||||
for _, opt := range opts {
|
|
||||||
if err := opt(filter); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return filter, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ShouldInclude returns whether the object is in the include list or not in
|
|
||||||
// the exclude list (exclude taking precedence).
|
|
||||||
func (f *Filter) ShouldInclude(object string) bool {
|
|
||||||
if f == nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
exclude, include := len(f.exclude), len(f.include)
|
|
||||||
if exclude == 0 && include == 0 {
|
|
||||||
return true
|
|
||||||
} else if exclude > 0 && include == 0 {
|
|
||||||
return f.shouldIncludeFromExclude(object)
|
|
||||||
} else if exclude == 0 && include > 0 {
|
|
||||||
return f.shouldIncludeFromInclude(object)
|
|
||||||
} else {
|
|
||||||
if ok, err := f.shouldIncludeFromBoth(object); err == nil {
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
// Ambiguous case.
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// shouldIncludeFromExclude checks for explicitly excluded paths. This should
|
|
||||||
// only be called when the include list is empty.
|
|
||||||
func (f *Filter) shouldIncludeFromExclude(object string) bool {
|
|
||||||
for _, g := range f.exclude {
|
|
||||||
if g.Match(object) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// shouldIncludeFromInclude checks for explicitly included paths. This should
|
|
||||||
// only be called when the exclude list is empty.
|
|
||||||
func (f *Filter) shouldIncludeFromInclude(object string) bool {
|
|
||||||
for _, g := range f.include {
|
|
||||||
if g.Match(object) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// shouldIncludeFromBoth checks for either excluded or included paths. Exclusion
|
|
||||||
// takes precedence. If neither list contains the object, true is returned.
|
|
||||||
func (f *Filter) shouldIncludeFromBoth(object string) (bool, error) {
|
|
||||||
// Exclude takes precedence. If we find the object in the exclude list,
|
|
||||||
// we should not match.
|
|
||||||
for _, g := range f.exclude {
|
|
||||||
if g.Match(object) {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If we find the object in the include list, we should match.
|
|
||||||
for _, g := range f.include {
|
|
||||||
if g.Match(object) {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If we find it in neither, return an error to let the caller decide.
|
|
||||||
return false, fmt.Errorf("ambiguous match")
|
|
||||||
}
|
|
||||||
@@ -1,138 +0,0 @@
|
|||||||
package glob
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"pgregory.net/rapid"
|
|
||||||
)
|
|
||||||
|
|
||||||
type globTest struct {
|
|
||||||
input string
|
|
||||||
shouldInclude bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func testGlobs(t *testing.T, filter *Filter, tests ...globTest) {
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.input, func(t *testing.T) {
|
|
||||||
// Invert because mentally it's easier to say whether an
|
|
||||||
// input should be included.
|
|
||||||
assert.Equal(t, tt.shouldInclude, filter.ShouldInclude(tt.input))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGlobFilterExclude(t *testing.T) {
|
|
||||||
filter, err := NewGlobFilter(WithExcludeGlobs("foo", "bar*"))
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
testGlobs(t, filter,
|
|
||||||
globTest{"foo", false},
|
|
||||||
globTest{"bar", false},
|
|
||||||
globTest{"bara", false},
|
|
||||||
globTest{"barb", false},
|
|
||||||
globTest{"barbosa", false},
|
|
||||||
globTest{"foobar", true},
|
|
||||||
globTest{"food", true},
|
|
||||||
globTest{"anything else", true},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGlobFilterInclude(t *testing.T) {
|
|
||||||
filter, err := NewGlobFilter(WithIncludeGlobs("foo", "bar*"))
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
testGlobs(t, filter,
|
|
||||||
globTest{"foo", true},
|
|
||||||
globTest{"bar", true},
|
|
||||||
globTest{"bara", true},
|
|
||||||
globTest{"barb", true},
|
|
||||||
globTest{"barbosa", true},
|
|
||||||
globTest{"foobar", false},
|
|
||||||
globTest{"food", false},
|
|
||||||
globTest{"anything else", false},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGlobFilterEmpty(t *testing.T) {
|
|
||||||
filter, err := NewGlobFilter()
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
testGlobs(t, filter,
|
|
||||||
globTest{"foo", true},
|
|
||||||
globTest{"bar", true},
|
|
||||||
globTest{"bara", true},
|
|
||||||
globTest{"barb", true},
|
|
||||||
globTest{"barbosa", true},
|
|
||||||
globTest{"foobar", true},
|
|
||||||
globTest{"food", true},
|
|
||||||
globTest{"anything else", true},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGlobFilterExcludeInclude(t *testing.T) {
|
|
||||||
filter, err := NewGlobFilter(WithExcludeGlobs("/foo/bar/**"), WithIncludeGlobs("/foo/**"))
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
testGlobs(t, filter,
|
|
||||||
globTest{"/foo/a", true},
|
|
||||||
globTest{"/foo/b", true},
|
|
||||||
globTest{"/foo/c/d/e", true},
|
|
||||||
globTest{"/foo/bar/a", false},
|
|
||||||
globTest{"/foo/bar/b", false},
|
|
||||||
globTest{"/foo/bar/c/d/e", false},
|
|
||||||
globTest{"/any/other/path", false},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGlobFilterExcludePrecedence(t *testing.T) {
|
|
||||||
filter, err := NewGlobFilter(WithExcludeGlobs("foo"), WithIncludeGlobs("foo*"))
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
testGlobs(t, filter,
|
|
||||||
globTest{"foo", false},
|
|
||||||
globTest{"foobar", true},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGlobErrorContainsGlob(t *testing.T) {
|
|
||||||
invalidGlob := "[this is invalid because it doesn't close the capture group"
|
|
||||||
_, err := NewGlobFilter(WithExcludeGlobs(invalidGlob))
|
|
||||||
assert.Error(t, err)
|
|
||||||
assert.Contains(t, err.Error(), invalidGlob)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The filters in this test should be mutually exclusive because one includes
|
|
||||||
// and the other excludes the same glob.
|
|
||||||
func TestGlobInverse(t *testing.T) {
|
|
||||||
for _, glob := range []string{
|
|
||||||
"a",
|
|
||||||
"a*",
|
|
||||||
"a**",
|
|
||||||
"*a",
|
|
||||||
"**a",
|
|
||||||
"*",
|
|
||||||
} {
|
|
||||||
include, err := NewGlobFilter(WithIncludeGlobs(glob))
|
|
||||||
assert.NoError(t, err)
|
|
||||||
exclude, err := NewGlobFilter(WithExcludeGlobs(glob))
|
|
||||||
assert.NoError(t, err)
|
|
||||||
rapid.Check(t, func(t *rapid.T) {
|
|
||||||
input := rapid.String().Draw(t, "input")
|
|
||||||
a, b := include.ShouldInclude(input), exclude.ShouldInclude(input)
|
|
||||||
if a == b {
|
|
||||||
t.Fatalf("Filter(Include(%q)) == Filter(Exclude(%q)) == %v for input %q", glob, glob, a, input)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGlobDefaultFilters(t *testing.T) {
|
|
||||||
for _, filter := range []*Filter{nil, {}} {
|
|
||||||
rapid.Check(t, func(t *rapid.T) {
|
|
||||||
if !filter.ShouldInclude(rapid.String().Draw(t, "input")) {
|
|
||||||
t.Fatalf("filter %#v did not include input", filter)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+2
-26
@@ -3,7 +3,6 @@ package common
|
|||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"io"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -76,14 +75,6 @@ func PinnedCertPool() *x509.CertPool {
|
|||||||
return trustedCerts
|
return trustedCerts
|
||||||
}
|
}
|
||||||
|
|
||||||
type FakeTransport struct {
|
|
||||||
CreateResponse func(req *http.Request) (*http.Response, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t FakeTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
||||||
return t.CreateResponse(req)
|
|
||||||
}
|
|
||||||
|
|
||||||
type CustomTransport struct {
|
type CustomTransport struct {
|
||||||
T http.RoundTripper
|
T http.RoundTripper
|
||||||
}
|
}
|
||||||
@@ -100,21 +91,6 @@ func NewCustomTransport(T http.RoundTripper) *CustomTransport {
|
|||||||
return &CustomTransport{T}
|
return &CustomTransport{T}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ConstantResponseHttpClient(statusCode int, body string) *http.Client {
|
|
||||||
return &http.Client{
|
|
||||||
Timeout: DefaultResponseTimeout,
|
|
||||||
Transport: FakeTransport{
|
|
||||||
CreateResponse: func(req *http.Request) (*http.Response, error) {
|
|
||||||
return &http.Response{
|
|
||||||
Request: req,
|
|
||||||
Body: io.NopCloser(strings.NewReader(body)),
|
|
||||||
StatusCode: statusCode,
|
|
||||||
}, nil
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func PinnedRetryableHttpClient() *http.Client {
|
func PinnedRetryableHttpClient() *http.Client {
|
||||||
httpClient := retryablehttp.NewClient()
|
httpClient := retryablehttp.NewClient()
|
||||||
httpClient.Logger = nil
|
httpClient.Logger = nil
|
||||||
@@ -176,9 +152,9 @@ func SaneHttpClient() *http.Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SaneHttpClientTimeOut adds a custom timeout for some scanners
|
// SaneHttpClientTimeOut adds a custom timeout for some scanners
|
||||||
func SaneHttpClientTimeOut(timeout time.Duration) *http.Client {
|
func SaneHttpClientTimeOut(timeOutSeconds int64) *http.Client {
|
||||||
httpClient := &http.Client{}
|
httpClient := &http.Client{}
|
||||||
httpClient.Timeout = timeout
|
httpClient.Timeout = time.Second * time.Duration(timeOutSeconds)
|
||||||
httpClient.Transport = NewCustomTransport(nil)
|
httpClient.Transport = NewCustomTransport(nil)
|
||||||
return httpClient
|
return httpClient
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
package common
|
|
||||||
|
|
||||||
const (
|
|
||||||
// MetricsNamespace is the namespace for all metrics.
|
|
||||||
MetricsNamespace = "trufflehog"
|
|
||||||
// MetricsSubsystem is the subsystem for all metrics.
|
|
||||||
MetricsSubsystem = "scanner"
|
|
||||||
)
|
|
||||||
+1
-33
@@ -2,7 +2,6 @@ package common
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@@ -16,11 +15,7 @@ const RegexPattern = "0-9a-z"
|
|||||||
const AlphaNumPattern = "0-9a-zA-Z"
|
const AlphaNumPattern = "0-9a-zA-Z"
|
||||||
const HexPattern = "0-9a-f"
|
const HexPattern = "0-9a-f"
|
||||||
|
|
||||||
type RegexState struct {
|
//Custom Regex functions
|
||||||
compiledRegex *regexp.Regexp
|
|
||||||
}
|
|
||||||
|
|
||||||
// Custom Regex functions
|
|
||||||
func BuildRegex(pattern string, specialChar string, length int) string {
|
func BuildRegex(pattern string, specialChar string, length int) string {
|
||||||
return fmt.Sprintf(`\b([%s%s]{%s})\b`, pattern, specialChar, strconv.Itoa(length))
|
return fmt.Sprintf(`\b([%s%s]{%s})\b`, pattern, specialChar, strconv.Itoa(length))
|
||||||
}
|
}
|
||||||
@@ -42,30 +37,3 @@ func RangeValidation(rangeInput string) bool {
|
|||||||
func ToUpperCase(input string) string {
|
func ToUpperCase(input string) string {
|
||||||
return strings.ToUpper(input)
|
return strings.ToUpper(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r RegexState) Matches(data []byte) []string {
|
|
||||||
matches := r.compiledRegex.FindAllStringSubmatch(string(data), -1)
|
|
||||||
|
|
||||||
res := make([]string, 0, len(matches))
|
|
||||||
|
|
||||||
// trim off spaces and different quote types ('").
|
|
||||||
for i := range matches {
|
|
||||||
res = append(res, strings.Trim(matches[i][1], `"' )`))
|
|
||||||
}
|
|
||||||
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// UsernameRegexCheck constructs an username usernameRegex pattern from a given pattern of excluded characters.
|
|
||||||
func UsernameRegexCheck(pattern string) RegexState {
|
|
||||||
raw := fmt.Sprintf(`(?im)(?:user|usr)\S{0,40}?[:=\s]{1,3}[ '"=]{0,1}([^:%+v]{4,40})\b`, pattern)
|
|
||||||
|
|
||||||
return RegexState{regexp.MustCompile(raw)}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PasswordRegexCheck constructs an username usernameRegex pattern from a given pattern of excluded characters.
|
|
||||||
func PasswordRegexCheck(pattern string) RegexState {
|
|
||||||
raw := fmt.Sprintf(`(?im)(?:pass|password)\S{0,40}?[:=\s]{1,3}[ '"=]{0,1}([^:%+v]{4,40})`, pattern)
|
|
||||||
|
|
||||||
return RegexState{regexp.MustCompile(raw)}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,59 +0,0 @@
|
|||||||
package common
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"regexp"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
usernamePattern = `?()/\+=\s\n`
|
|
||||||
passwordPattern = `^<>;.*&|£\n\s`
|
|
||||||
usernameRegex = `(?im)(?:user|usr)\S{0,40}?[:=\s]{1,3}[ '"=]{0,1}([^:?()/\+=\s\n]{4,40})\b`
|
|
||||||
passwordRegex = `(?im)(?:pass|password)\S{0,40}?[:=\s]{1,3}[ '"=]{0,1}([^:^<>;.*&|£\n\s]{4,40})`
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestUsernameRegexCheck(t *testing.T) {
|
|
||||||
usernameRegexPat := UsernameRegexCheck(usernamePattern)
|
|
||||||
|
|
||||||
expectedRegexPattern := regexp.MustCompile(usernameRegex)
|
|
||||||
|
|
||||||
if usernameRegexPat.compiledRegex.String() != expectedRegexPattern.String() {
|
|
||||||
t.Errorf("\n got %v \n want %v", usernameRegexPat.compiledRegex, expectedRegexPattern)
|
|
||||||
}
|
|
||||||
|
|
||||||
testString := `username = "johnsmith123"
|
|
||||||
username='johnsmith123'
|
|
||||||
username:="johnsmith123"
|
|
||||||
username = johnsmith123
|
|
||||||
username=johnsmith123`
|
|
||||||
|
|
||||||
expectedStr := []string{"johnsmith123", "johnsmith123", "johnsmith123", "johnsmith123", "johnsmith123"}
|
|
||||||
|
|
||||||
usernameRegexMatches := usernameRegexPat.Matches([]byte(testString))
|
|
||||||
|
|
||||||
assert.Exactly(t, usernameRegexMatches, expectedStr)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPasswordRegexCheck(t *testing.T) {
|
|
||||||
passwordRegexPat := PasswordRegexCheck(passwordPattern)
|
|
||||||
|
|
||||||
expectedRegexPattern := regexp.MustCompile(passwordRegex)
|
|
||||||
assert.Equal(t, passwordRegexPat.compiledRegex, expectedRegexPattern)
|
|
||||||
|
|
||||||
testString := `password = "johnsmith123$!"
|
|
||||||
password='johnsmith123$!'
|
|
||||||
password:="johnsmith123$!"
|
|
||||||
password = johnsmith123$!
|
|
||||||
password=johnsmith123$!
|
|
||||||
PasswordAuthenticator(username, "johnsmith123$!")`
|
|
||||||
|
|
||||||
expectedStr := []string{"johnsmith123$!", "johnsmith123$!", "johnsmith123$!", "johnsmith123$!", "johnsmith123$!",
|
|
||||||
"johnsmith123$!"}
|
|
||||||
|
|
||||||
passwordRegexMatches := passwordRegexPat.Matches([]byte(testString))
|
|
||||||
|
|
||||||
assert.Exactly(t, passwordRegexMatches, expectedStr)
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -18,7 +18,8 @@ func Recover(ctx context.Context) {
|
|||||||
if eventID := sentry.CurrentHub().Recover(err); eventID != nil {
|
if eventID := sentry.CurrentHub().Recover(err); eventID != nil {
|
||||||
ctx.Logger().Info("panic captured", "event_id", *eventID)
|
ctx.Logger().Info("panic captured", "event_id", *eventID)
|
||||||
}
|
}
|
||||||
ctx.Logger().Error(fmt.Errorf("panic"), panicStack,
|
ctx.Logger().Error(fmt.Errorf("panic"), "recovered from panic",
|
||||||
|
"stack-trace", panicStack,
|
||||||
"recover", err,
|
"recover", err,
|
||||||
)
|
)
|
||||||
if !sentry.Flush(time.Second * 5) {
|
if !sentry.Flush(time.Second * 5) {
|
||||||
|
|||||||
@@ -1,16 +1,5 @@
|
|||||||
package common
|
package common
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"crypto/rand"
|
|
||||||
"encoding/base32"
|
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"math/big"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func AddStringSliceItem(item string, slice *[]string) {
|
func AddStringSliceItem(item string, slice *[]string) {
|
||||||
for _, i := range *slice {
|
for _, i := range *slice {
|
||||||
if i == item {
|
if i == item {
|
||||||
@@ -28,53 +17,3 @@ func RemoveStringSliceItem(item string, slice *[]string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ResponseContainsSubstring(reader io.ReadCloser, target string) (bool, error) {
|
|
||||||
scanner := bufio.NewScanner(reader)
|
|
||||||
for scanner.Scan() {
|
|
||||||
if strings.Contains(scanner.Text(), target) {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := scanner.Err(); err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
|
|
||||||
|
|
||||||
// RandomID returns a random string of the given length.
|
|
||||||
func RandomID(length int) string {
|
|
||||||
b := make([]rune, length)
|
|
||||||
for i := range b {
|
|
||||||
randInt, _ := rand.Int(rand.Reader, big.NewInt(int64(len(letters))))
|
|
||||||
b[i] = letters[randInt.Int64()]
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetAccountNumFromAWSID(AWSID string) (string, error) {
|
|
||||||
// Function to get the account number from an AWS ID (no verification required)
|
|
||||||
// Source: https://medium.com/@TalBeerySec/a-short-note-on-aws-key-id-f88cc4317489
|
|
||||||
if len(AWSID) < 4 {
|
|
||||||
return "", fmt.Errorf("AWSID is too short")
|
|
||||||
}
|
|
||||||
trimmedAWSID := AWSID[4:]
|
|
||||||
decodedBytes, err := base32.StdEncoding.WithPadding(base32.NoPadding).DecodeString(strings.ToUpper(trimmedAWSID))
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(decodedBytes) < 6 {
|
|
||||||
return "", fmt.Errorf("Decoded AWSID is too short")
|
|
||||||
}
|
|
||||||
|
|
||||||
data := make([]byte, 8)
|
|
||||||
copy(data[2:], decodedBytes[0:6])
|
|
||||||
z := binary.BigEndian.Uint64(data)
|
|
||||||
const mask uint64 = 0x7fffffffff80
|
|
||||||
accountNum := (z & mask) >> 7
|
|
||||||
return fmt.Sprintf("%012d", accountNum), nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
package common
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -76,60 +74,3 @@ func TestRemoveItem(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test ParseResponseForKeywords with a reader that contains the keyword and a reader that doesn't.
|
|
||||||
func TestParseResponseForKeywords(t *testing.T) {
|
|
||||||
testCases := []struct {
|
|
||||||
name string
|
|
||||||
input string
|
|
||||||
keyword string
|
|
||||||
expected bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Should find keyword",
|
|
||||||
input: "ey: abc",
|
|
||||||
keyword: "ey",
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Should not find keyword",
|
|
||||||
input: "fake response",
|
|
||||||
keyword: "ey",
|
|
||||||
expected: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Empty string",
|
|
||||||
input: "",
|
|
||||||
keyword: "ey",
|
|
||||||
expected: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Keyword at end",
|
|
||||||
input: "abc ey",
|
|
||||||
keyword: "ey",
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Keyword at start",
|
|
||||||
input: "ey abc",
|
|
||||||
keyword: "ey",
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
testReader := strings.NewReader(tc.input)
|
|
||||||
testReadCloser := io.NopCloser(testReader)
|
|
||||||
found, err := ResponseContainsSubstring(testReadCloser, tc.keyword)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if found != tc.expected {
|
|
||||||
t.Errorf("Expected %v, got %v", tc.expected, found)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+3
-47
@@ -2,60 +2,16 @@ package common
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
KB, MB, GB, TB, PB = 1e3, 1e6, 1e9, 1e12, 1e15
|
KB, MB, GB, TB, PB = 1e3, 1e6, 1e9, 1e12, 1e15
|
||||||
IgnoredExtensions = []string{
|
IGNORED_EXTENSIONS = []string{"mp4", "avi", "mpeg", "mpg", "mov", "wmv", "m4p", "swf", "mp2", "flv", "vob", "webm", "hdv", "3gp", "ogg", "mp3", "wav", "flac", "webp"}
|
||||||
// multimedia/containers
|
|
||||||
"mp4",
|
|
||||||
"avi",
|
|
||||||
"mpeg",
|
|
||||||
"mpg",
|
|
||||||
"mov",
|
|
||||||
"wmv",
|
|
||||||
"m4p",
|
|
||||||
"swf",
|
|
||||||
"mp2",
|
|
||||||
"flv",
|
|
||||||
"vob",
|
|
||||||
"webm",
|
|
||||||
"hdv",
|
|
||||||
"3gp",
|
|
||||||
"ogg",
|
|
||||||
"mp3",
|
|
||||||
"wav",
|
|
||||||
"flac",
|
|
||||||
"webp",
|
|
||||||
|
|
||||||
// images
|
|
||||||
"png",
|
|
||||||
"jpg",
|
|
||||||
"jpeg",
|
|
||||||
"gif",
|
|
||||||
"tiff",
|
|
||||||
|
|
||||||
// binaries
|
|
||||||
// These can theoretically contain secrets, but need decoding for users to make sense of them, and we don't have
|
|
||||||
// any such decoders right now.
|
|
||||||
"class",
|
|
||||||
"dll",
|
|
||||||
"xsb",
|
|
||||||
"jdo",
|
|
||||||
"jar",
|
|
||||||
"ear",
|
|
||||||
"war",
|
|
||||||
"jks",
|
|
||||||
"ser",
|
|
||||||
"idx",
|
|
||||||
"hprof",
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func SkipFile(filename string) bool {
|
func SkipFile(filename string) bool {
|
||||||
for _, ext := range IgnoredExtensions {
|
for _, ext := range IGNORED_EXTENSIONS {
|
||||||
if strings.TrimPrefix(filepath.Ext(filename), ".") == ext {
|
if filepath.Ext(filename) == ext {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
package common
|
|
||||||
|
|
||||||
import "testing"
|
|
||||||
|
|
||||||
func TestSkipFile(t *testing.T) {
|
|
||||||
type testCase struct {
|
|
||||||
file string
|
|
||||||
want bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a test case for each ignored extension.
|
|
||||||
testCases := make([]testCase, 0, len(IgnoredExtensions)+1)
|
|
||||||
for _, ext := range IgnoredExtensions {
|
|
||||||
testCases = append(testCases, testCase{
|
|
||||||
file: "test." + ext,
|
|
||||||
want: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a test case for a file that should not be skipped.
|
|
||||||
testCases = append(testCases, testCase{file: "test.txt", want: false})
|
|
||||||
|
|
||||||
for _, tt := range testCases {
|
|
||||||
t.Run(tt.file, func(t *testing.T) {
|
|
||||||
if got := SkipFile(tt.file); got != tt.want {
|
|
||||||
t.Errorf("SkipFile(%v) got %v, want %v", tt.file, got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkSkipFile(b *testing.B) {
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
SkipFile("test.mp4")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -31,15 +31,15 @@ func NewYAML(input []byte) (*Config, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Convert the structured YAML into detectors.
|
// Convert the structured YAML into detectors.
|
||||||
var d []detectors.Detector
|
var detectors []detectors.Detector
|
||||||
for _, detectorConfig := range messages.Detectors {
|
for _, detectorConfig := range messages.Detectors {
|
||||||
detector, err := custom_detectors.NewWebhookCustomRegex(detectorConfig)
|
detector, err := custom_detectors.NewWebhookCustomRegex(detectorConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
d = append(d, detector)
|
detectors = append(detectors, detector)
|
||||||
}
|
}
|
||||||
return &Config{
|
return &Config{
|
||||||
Detectors: d,
|
Detectors: detectors,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-53
@@ -2,12 +2,10 @@ package config
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
|
|
||||||
dpb "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
|
dpb "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -33,26 +31,11 @@ func init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DetectorID identifies a detector type and version. This struct is used as a
|
|
||||||
// way for users to identify detectors, whether unique or not. A DetectorID
|
|
||||||
// with Version = 0 indicates all possible versions of a detector.
|
|
||||||
type DetectorID struct {
|
type DetectorID struct {
|
||||||
ID dpb.DetectorType
|
ID dpb.DetectorType
|
||||||
Version int
|
Version int
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDetectorID extracts the DetectorID from a Detector.
|
|
||||||
func GetDetectorID(d detectors.Detector) DetectorID {
|
|
||||||
var version int
|
|
||||||
if v, ok := d.(detectors.Versioner); ok {
|
|
||||||
version = v.Version()
|
|
||||||
}
|
|
||||||
return DetectorID{
|
|
||||||
ID: d.Type(),
|
|
||||||
Version: version,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseDetectors parses user supplied string into a list of detectors types.
|
// ParseDetectors parses user supplied string into a list of detectors types.
|
||||||
// "all" will return the list of all available detectors. The input is comma
|
// "all" will return the list of all available detectors. The input is comma
|
||||||
// separated and may use the case-insensitive detector name defined in the
|
// separated and may use the case-insensitive detector name defined in the
|
||||||
@@ -66,15 +49,15 @@ func ParseDetectors(input string) ([]DetectorID, error) {
|
|||||||
if item == "" {
|
if item == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
allDetectors, ok := specialGroups[strings.ToLower(item)]
|
detectors, ok := specialGroups[strings.ToLower(item)]
|
||||||
if !ok {
|
if !ok {
|
||||||
var err error
|
var err error
|
||||||
allDetectors, err = asRange(item)
|
detectors, err = asRange(item)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, d := range allDetectors {
|
for _, d := range detectors {
|
||||||
if _, ok := seenDetector[d]; ok {
|
if _, ok := seenDetector[d]; ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -85,37 +68,6 @@ func ParseDetectors(input string) ([]DetectorID, error) {
|
|||||||
return output, nil
|
return output, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseDetector parses a user supplied string into a single DetectorID. Input
|
|
||||||
// is case-insensitive and either the detector name or ID may be used.
|
|
||||||
func ParseDetector(input string) (DetectorID, error) {
|
|
||||||
return asDetectorID(strings.TrimSpace(input))
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseVerifierEndpoints parses a map of user supplied verifier URLs. The
|
|
||||||
// input keys are detector IDs and the values are a comma separated list of
|
|
||||||
// URLs. The URLs are validated as HTTPS endpoints.
|
|
||||||
func ParseVerifierEndpoints(verifierURLs map[string]string) (map[DetectorID][]string, error) {
|
|
||||||
verifiers := make(map[DetectorID][]string, len(verifierURLs))
|
|
||||||
for detectorID, urls := range verifierURLs {
|
|
||||||
key, err := ParseDetector(detectorID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("invalid detector ID for verifier: %w", err)
|
|
||||||
}
|
|
||||||
verifierURLs := strings.Split(urls, ",")
|
|
||||||
for i, rawEndpoint := range verifierURLs {
|
|
||||||
rawEndpoint := strings.TrimSpace(rawEndpoint)
|
|
||||||
verifierURLs[i] = rawEndpoint
|
|
||||||
if endpoint, err := url.Parse(rawEndpoint); err != nil {
|
|
||||||
return nil, fmt.Errorf("invalid verifier url %q: %w", rawEndpoint, err)
|
|
||||||
} else if endpoint.Scheme != "https" {
|
|
||||||
return nil, fmt.Errorf("verifier url must be https: %q", rawEndpoint)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
verifiers[key] = append(verifiers[key], verifierURLs...)
|
|
||||||
}
|
|
||||||
return verifiers, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (id DetectorID) String() string {
|
func (id DetectorID) String() string {
|
||||||
name := dpb.DetectorType_name[int32(id.ID)]
|
name := dpb.DetectorType_name[int32(id.ID)]
|
||||||
if name == "" {
|
if name == "" {
|
||||||
@@ -187,8 +139,8 @@ func asRange(input string) ([]DetectorID, error) {
|
|||||||
return append(output, dtEnd), nil
|
return append(output, dtEnd), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// asDetectorID converts the case-insensitive input into a DetectorID. Name or
|
// asDetectorID converts the case-insensitive input into a DetectorID.
|
||||||
// ID may be used. Input is expected to be already trimmed of whitespace.
|
// Name or ID may be used.
|
||||||
func asDetectorID(input string) (DetectorID, error) {
|
func asDetectorID(input string) (DetectorID, error) {
|
||||||
if input == "" {
|
if input == "" {
|
||||||
return DetectorID{}, fmt.Errorf("empty detector")
|
return DetectorID{}, fmt.Errorf("empty detector")
|
||||||
|
|||||||
+52
-56
@@ -2,40 +2,45 @@ package context
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"os"
|
"fmt"
|
||||||
|
"runtime/debug"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-logr/logr"
|
"github.com/go-logr/logr"
|
||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// defaultLogger can be set via SetDefaultLogger.
|
// defaultLogger can be set via SetDefaultLogger.
|
||||||
// It is initialized to write to stderr. To disable, you can call
|
defaultLogger logr.Logger = logr.Discard()
|
||||||
// SetDefaultLogger with logr.Discard().
|
|
||||||
defaultLogger logr.Logger
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
|
||||||
defaultLogger, _ = log.New("context", log.WithConsoleSink(os.Stderr))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Context wraps context.Context and includes an additional Logger() method.
|
// Context wraps context.Context and includes an additional Logger() method.
|
||||||
type Context interface {
|
type Context interface {
|
||||||
context.Context
|
context.Context
|
||||||
Logger() logr.Logger
|
Logger() logr.Logger
|
||||||
|
Parent() context.Context
|
||||||
|
SetParent(ctx context.Context) Context
|
||||||
}
|
}
|
||||||
|
|
||||||
// CancelFunc and CancelCauseFunc are type aliases to allow use as if they are
|
// Parent returns the parent context.
|
||||||
// the same types as the standard library variants.
|
func (l logCtx) Parent() context.Context {
|
||||||
type CancelFunc = context.CancelFunc
|
return l.Context
|
||||||
type CancelCauseFunc = context.CancelCauseFunc
|
}
|
||||||
|
|
||||||
|
// SetParent sets the parent context on the context.
|
||||||
|
func (l logCtx) SetParent(ctx context.Context) Context {
|
||||||
|
l.Context = ctx
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
type CancelFunc context.CancelFunc
|
||||||
|
|
||||||
// logCtx implements Context.
|
// logCtx implements Context.
|
||||||
type logCtx struct {
|
type logCtx struct {
|
||||||
// Embed context.Context to get all methods for free.
|
// Embed context.Context to get all methods for free.
|
||||||
context.Context
|
context.Context
|
||||||
log logr.Logger
|
log logr.Logger
|
||||||
|
err *error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Logger returns a structured logger.
|
// Logger returns a structured logger.
|
||||||
@@ -43,6 +48,13 @@ func (l logCtx) Logger() logr.Logger {
|
|||||||
return l.log
|
return l.log
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l logCtx) Err() error {
|
||||||
|
if l.err != nil && *l.err != nil {
|
||||||
|
return *l.err
|
||||||
|
}
|
||||||
|
return l.Context.Err()
|
||||||
|
}
|
||||||
|
|
||||||
// Background returns context.Background with a default logger.
|
// Background returns context.Background with a default logger.
|
||||||
func Background() Context {
|
func Background() Context {
|
||||||
return logCtx{
|
return logCtx{
|
||||||
@@ -66,17 +78,7 @@ func WithCancel(parent Context) (Context, context.CancelFunc) {
|
|||||||
log: parent.Logger(),
|
log: parent.Logger(),
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
}
|
}
|
||||||
return lCtx, cancel
|
return captureCancelCallstack(lCtx, cancel)
|
||||||
}
|
|
||||||
|
|
||||||
// WithCancelCause returns context.WithCancelCause with the log object propagated.
|
|
||||||
func WithCancelCause(parent Context) (Context, context.CancelCauseFunc) {
|
|
||||||
ctx, cancel := context.WithCancelCause(parent)
|
|
||||||
lCtx := logCtx{
|
|
||||||
log: parent.Logger(),
|
|
||||||
Context: ctx,
|
|
||||||
}
|
|
||||||
return lCtx, cancel
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithDeadline returns context.WithDeadline with the log object propagated and
|
// WithDeadline returns context.WithDeadline with the log object propagated and
|
||||||
@@ -87,18 +89,7 @@ func WithDeadline(parent Context, d time.Time) (Context, context.CancelFunc) {
|
|||||||
log: parent.Logger().WithValues("deadline", d),
|
log: parent.Logger().WithValues("deadline", d),
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
}
|
}
|
||||||
return lCtx, cancel
|
return captureCancelCallstack(lCtx, cancel)
|
||||||
}
|
|
||||||
|
|
||||||
// WithDeadlineCause returns context.WithDeadlineCause with the log object
|
|
||||||
// propagated and the deadline added to the structured log values.
|
|
||||||
func WithDeadlineCause(parent Context, d time.Time, cause error) (Context, context.CancelFunc) {
|
|
||||||
ctx, cancel := context.WithDeadlineCause(parent, d, cause)
|
|
||||||
lCtx := logCtx{
|
|
||||||
log: parent.Logger().WithValues("deadline", d),
|
|
||||||
Context: ctx,
|
|
||||||
}
|
|
||||||
return lCtx, cancel
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithTimeout returns context.WithTimeout with the log object propagated and
|
// WithTimeout returns context.WithTimeout with the log object propagated and
|
||||||
@@ -109,23 +100,7 @@ func WithTimeout(parent Context, timeout time.Duration) (Context, context.Cancel
|
|||||||
log: parent.Logger().WithValues("timeout", timeout),
|
log: parent.Logger().WithValues("timeout", timeout),
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
}
|
}
|
||||||
return lCtx, cancel
|
return captureCancelCallstack(lCtx, cancel)
|
||||||
}
|
|
||||||
|
|
||||||
// WithTimeoutCause returns context.WithTimeoutCause with the log object
|
|
||||||
// propagated and the timeout added to the structured log values.
|
|
||||||
func WithTimeoutCause(parent Context, timeout time.Duration, cause error) (Context, context.CancelFunc) {
|
|
||||||
ctx, cancel := context.WithTimeoutCause(parent, timeout, cause)
|
|
||||||
lCtx := logCtx{
|
|
||||||
log: parent.Logger().WithValues("timeout", timeout),
|
|
||||||
Context: ctx,
|
|
||||||
}
|
|
||||||
return lCtx, cancel
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cause returns the context.Cause of the context.
|
|
||||||
func Cause(ctx context.Context) error {
|
|
||||||
return context.Cause(ctx)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithValue returns context.WithValue with the log object propagated and
|
// WithValue returns context.WithValue with the log object propagated and
|
||||||
@@ -170,9 +145,30 @@ func AddLogger(parent context.Context) Context {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetupDefaultLogger sets the package-level global default logger that will be
|
// SetupDefaultLogger sets the package-level global default logger that will be
|
||||||
// used for Background and TODO contexts. On startup, the default logger will
|
// used for Background and TODO contexts.
|
||||||
// be configured to output logs to stderr. Use logr.Discard() to disable all
|
|
||||||
// logs from Contexts.
|
|
||||||
func SetDefaultLogger(l logr.Logger) {
|
func SetDefaultLogger(l logr.Logger) {
|
||||||
defaultLogger = l
|
defaultLogger = l
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// captureCancelCallstack is a helper function to capture the callstack where
|
||||||
|
// the cancel function was first called.
|
||||||
|
func captureCancelCallstack(ctx logCtx, f context.CancelFunc) (Context, context.CancelFunc) {
|
||||||
|
if ctx.err == nil {
|
||||||
|
var err error
|
||||||
|
ctx.err = &err
|
||||||
|
}
|
||||||
|
return ctx, func() {
|
||||||
|
// We must check Err() before calling f() since f() sets the error.
|
||||||
|
// If there's already an error, do nothing special.
|
||||||
|
if ctx.Err() != nil {
|
||||||
|
f()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
f()
|
||||||
|
// Set the error with the stacktrace if the err pointer is non-nil.
|
||||||
|
*ctx.err = fmt.Errorf(
|
||||||
|
"%w (canceled at %v\n%s)",
|
||||||
|
ctx.Err(), time.Now(), string(debug.Stack()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+39
-12
@@ -3,7 +3,6 @@ package context
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -159,7 +158,7 @@ func TestWithValues(t *testing.T) {
|
|||||||
assert.NotContains(t, logs[6], `what does this do?`)
|
assert.NotContains(t, logs[6], `what does this do?`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDefaultLogger(t *testing.T) {
|
func TestDiscardLogger(t *testing.T) {
|
||||||
var panicked bool
|
var panicked bool
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
@@ -171,17 +170,45 @@ func TestDefaultLogger(t *testing.T) {
|
|||||||
ctx.Logger().Info("this shouldn't panic")
|
ctx.Logger().Info("this shouldn't panic")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRace(t *testing.T) {
|
func TestErrCallstack(t *testing.T) {
|
||||||
ctx, cancel := WithCancel(Background())
|
c, cancel := WithCancel(Background())
|
||||||
go cancel()
|
ctx := c.(logCtx)
|
||||||
go func() { _ = ctx.Err() }()
|
|
||||||
cancel()
|
cancel()
|
||||||
_ = ctx.Err()
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
assert.Contains(t, ctx.Err().Error(), "TestErrCallstack")
|
||||||
|
case <-time.After(1 * time.Second):
|
||||||
|
assert.Fail(t, "context should be done")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCause(t *testing.T) {
|
func TestErrCallstackTimeout(t *testing.T) {
|
||||||
ctx, cancel := WithCancelCause(Background())
|
ctx, cancel := WithTimeout(Background(), 10*time.Millisecond)
|
||||||
err := fmt.Errorf("oh no")
|
defer cancel()
|
||||||
cancel(err)
|
|
||||||
assert.Equal(t, err, Cause(ctx))
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
// Deadline exceeded errors will not have a callstack from the cancel
|
||||||
|
// function.
|
||||||
|
assert.NotContains(t, ctx.Err().Error(), "TestErrCallstackTimeout")
|
||||||
|
case <-time.After(1 * time.Second):
|
||||||
|
assert.Fail(t, "context should be done")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestErrCallstackTimeoutCancel(t *testing.T) {
|
||||||
|
ctx, cancel := WithTimeout(Background(), 10*time.Millisecond)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
err = ctx.Err()
|
||||||
|
case <-time.After(1 * time.Second):
|
||||||
|
assert.Fail(t, "context should be done")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calling cancel after deadline exceeded should not overwrite the original
|
||||||
|
// error.
|
||||||
|
cancel()
|
||||||
|
assert.Equal(t, err, ctx.Err())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,20 +20,20 @@ import (
|
|||||||
// for poorly defined regexps.
|
// for poorly defined regexps.
|
||||||
const maxTotalMatches = 100
|
const maxTotalMatches = 100
|
||||||
|
|
||||||
// CustomRegexWebhook is a CustomRegex with webhook validation that is
|
// customRegexWebhook is a CustomRegex with webhook validation that is
|
||||||
// guaranteed to be valid (assuming the data is not changed after
|
// guaranteed to be valid (assuming the data is not changed after
|
||||||
// initialization).
|
// initialization).
|
||||||
type CustomRegexWebhook struct {
|
type customRegexWebhook struct {
|
||||||
*custom_detectorspb.CustomRegex
|
*custom_detectorspb.CustomRegex
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure the Scanner satisfies the interface at compile time.
|
// Ensure the Scanner satisfies the interface at compile time.
|
||||||
var _ detectors.Detector = (*CustomRegexWebhook)(nil)
|
var _ detectors.Detector = (*customRegexWebhook)(nil)
|
||||||
|
|
||||||
// NewWebhookCustomRegex initializes and validates a CustomRegexWebhook. An
|
// NewWebhookCustomRegex initializes and validates a customRegexWebhook. An
|
||||||
// unexported type is intentionally returned here to ensure the values have
|
// unexported type is intentionally returned here to ensure the values have
|
||||||
// been validated.
|
// been validated.
|
||||||
func NewWebhookCustomRegex(pb *custom_detectorspb.CustomRegex) (*CustomRegexWebhook, error) {
|
func NewWebhookCustomRegex(pb *custom_detectorspb.CustomRegex) (*customRegexWebhook, error) {
|
||||||
// TODO: Return all validation errors.
|
// TODO: Return all validation errors.
|
||||||
if err := ValidateKeywords(pb.Keywords); err != nil {
|
if err := ValidateKeywords(pb.Keywords); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -52,12 +52,12 @@ func NewWebhookCustomRegex(pb *custom_detectorspb.CustomRegex) (*CustomRegexWebh
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Copy only necessary data out of pb.
|
// TODO: Copy only necessary data out of pb.
|
||||||
return &CustomRegexWebhook{pb}, nil
|
return &customRegexWebhook{pb}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var httpClient = common.SaneHttpClient()
|
var httpClient = common.SaneHttpClient()
|
||||||
|
|
||||||
func (c *CustomRegexWebhook) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
|
func (c *customRegexWebhook) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
|
||||||
dataStr := string(data)
|
dataStr := string(data)
|
||||||
regexMatches := make(map[string][][]string, len(c.GetRegex()))
|
regexMatches := make(map[string][][]string, len(c.GetRegex()))
|
||||||
|
|
||||||
@@ -94,22 +94,19 @@ func (c *CustomRegexWebhook) FromData(ctx context.Context, verify bool, data []b
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ignore any errors and collect as many of the results as we can.
|
if err := g.Wait(); err != nil {
|
||||||
_ = g.Wait()
|
return nil, err
|
||||||
|
}
|
||||||
close(resultsCh)
|
close(resultsCh)
|
||||||
|
|
||||||
for result := range resultsCh {
|
for result := range resultsCh {
|
||||||
// NOTE: I don't believe this is being set anywhere else, hence the map assignment.
|
|
||||||
result.ExtraData = map[string]string{
|
|
||||||
"name": c.GetName(),
|
|
||||||
}
|
|
||||||
results = append(results, result)
|
results = append(results, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
return results, nil
|
return results, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CustomRegexWebhook) createResults(ctx context.Context, match map[string][]string, verify bool, results chan<- detectors.Result) error {
|
func (c *customRegexWebhook) createResults(ctx context.Context, match map[string][]string, verify bool, results chan<- detectors.Result) error {
|
||||||
if common.IsDone(ctx) {
|
if common.IsDone(ctx) {
|
||||||
// TODO: Log we're possibly leaving out results.
|
// TODO: Log we're possibly leaving out results.
|
||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
@@ -121,7 +118,6 @@ func (c *CustomRegexWebhook) createResults(ctx context.Context, match map[string
|
|||||||
}
|
}
|
||||||
result := detectors.Result{
|
result := detectors.Result{
|
||||||
DetectorType: detectorspb.DetectorType_CustomRegex,
|
DetectorType: detectorspb.DetectorType_CustomRegex,
|
||||||
DetectorName: c.GetName(),
|
|
||||||
Raw: []byte(raw),
|
Raw: []byte(raw),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,7 +176,7 @@ func (c *CustomRegexWebhook) createResults(ctx context.Context, match map[string
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CustomRegexWebhook) Keywords() []string {
|
func (c *customRegexWebhook) Keywords() []string {
|
||||||
return c.GetKeywords()
|
return c.GetKeywords()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,6 +241,6 @@ func permutateMatches(regexMatches map[string][][]string) []map[string][]string
|
|||||||
return matches
|
return matches
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CustomRegexWebhook) Type() detectorspb.DetectorType {
|
func (c *customRegexWebhook) Type() detectorspb.DetectorType {
|
||||||
return detectorspb.DetectorType_CustomRegex
|
return detectorspb.DetectorType_CustomRegex
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ func TestCustomDetectorsParsing(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFromData_InvalidRegEx(t *testing.T) {
|
func TestFromData_InvalidRegEx(t *testing.T) {
|
||||||
c := &CustomRegexWebhook{
|
c := &customRegexWebhook{
|
||||||
&custom_detectorspb.CustomRegex{
|
&custom_detectorspb.CustomRegex{
|
||||||
Name: "Internal bi tool",
|
Name: "Internal bi tool",
|
||||||
Keywords: []string{"secret_v1_", "pat_v2_"},
|
Keywords: []string{"secret_v1_", "pat_v2_"},
|
||||||
|
|||||||
+48
-99
@@ -3,127 +3,76 @@ package decoders
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
|
|
||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/sources"
|
"github.com/trufflesecurity/trufflehog/v3/pkg/sources"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type Base64 struct{}
|
||||||
Base64 struct{}
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
b64Charset = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/-_=")
|
b64Charset = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=")
|
||||||
b64EndChars = "+/-_="
|
b64EndChars = "+/="
|
||||||
// Given characters are mostly ASCII, we can use a simple array to map.
|
|
||||||
b64CharsetMapping [128]bool
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func getSubstringsOfCharacterSet(data []byte, charset []byte, threshold int) []string {
|
||||||
// Build an array of all the characters in the base64 charset.
|
count := 0
|
||||||
for _, char := range b64Charset {
|
substrings := []string{}
|
||||||
b64CharsetMapping[char] = true
|
letters := strings.Builder{}
|
||||||
|
if len(data) == 0 {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
for _, char := range string(data) {
|
||||||
|
if bytes.ContainsRune(charset, char) {
|
||||||
|
letters.WriteRune(char)
|
||||||
|
count++
|
||||||
|
} else {
|
||||||
|
if count > threshold {
|
||||||
|
substrings = appendB64Substring(letters, substrings)
|
||||||
|
}
|
||||||
|
letters.Reset()
|
||||||
|
count = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if count > threshold && len(letters.String()) > 0 {
|
||||||
|
substrings = appendB64Substring(letters, substrings)
|
||||||
|
}
|
||||||
|
|
||||||
|
return substrings
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Base64) FromChunk(chunk *sources.Chunk) *DecodableChunk {
|
func appendB64Substring(letters strings.Builder, substrings []string) []string {
|
||||||
decodableChunk := &DecodableChunk{Chunk: chunk, DecoderType: detectorspb.DecoderType_BASE64}
|
|
||||||
encodedSubstrings := getSubstringsOfCharacterSet(chunk.Data, 20, b64CharsetMapping, b64EndChars)
|
substring := strings.TrimLeft(letters.String(), b64EndChars)
|
||||||
decodedSubstrings := make(map[string][]byte)
|
// handle key=value
|
||||||
|
if strings.Contains(strings.TrimRight(substring, b64EndChars), "=") {
|
||||||
|
split := strings.SplitN(substring, "=", 2)
|
||||||
|
substrings = append(substrings, split[len(split)-1])
|
||||||
|
} else {
|
||||||
|
substrings = append(substrings, substring)
|
||||||
|
}
|
||||||
|
return substrings
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Base64) FromChunk(chunk *sources.Chunk) *sources.Chunk {
|
||||||
|
|
||||||
|
encodedSubstrings := getSubstringsOfCharacterSet(chunk.Data, b64Charset, 20)
|
||||||
|
decodedSubstrings := map[string][]byte{}
|
||||||
|
|
||||||
for _, str := range encodedSubstrings {
|
for _, str := range encodedSubstrings {
|
||||||
dec, err := base64.StdEncoding.DecodeString(str)
|
dec, err := base64.StdEncoding.DecodeString(str)
|
||||||
if err == nil {
|
|
||||||
if len(dec) > 0 {
|
|
||||||
decodedSubstrings[str] = dec
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
dec, err = base64.RawURLEncoding.DecodeString(str)
|
|
||||||
if err == nil && len(dec) > 0 {
|
if err == nil && len(dec) > 0 {
|
||||||
decodedSubstrings[str] = dec
|
decodedSubstrings[str] = dec
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(decodedSubstrings) > 0 {
|
if len(decodedSubstrings) > 0 {
|
||||||
var result bytes.Buffer
|
for substring, dec := range decodedSubstrings {
|
||||||
result.Grow(len(chunk.Data))
|
chunk.Data = bytes.Replace(chunk.Data, []byte(substring), dec, 1)
|
||||||
|
|
||||||
start := 0
|
|
||||||
for _, encoded := range encodedSubstrings {
|
|
||||||
if decoded, ok := decodedSubstrings[encoded]; ok {
|
|
||||||
end := bytes.Index(chunk.Data[start:], []byte(encoded))
|
|
||||||
if end != -1 {
|
|
||||||
result.Write(chunk.Data[start : start+end])
|
|
||||||
result.Write(decoded)
|
|
||||||
start += end + len(encoded)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
result.Write(chunk.Data[start:])
|
return chunk
|
||||||
chunk.Data = result.Bytes()
|
|
||||||
return decodableChunk
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSubstringsOfCharacterSet(data []byte, threshold int, charsetMapping [128]bool, endChars string) []string {
|
|
||||||
if len(data) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
count := 0
|
|
||||||
substringsCount := 0
|
|
||||||
|
|
||||||
// Determine the number of substrings that will be returned.
|
|
||||||
// Pre-allocate the slice to avoid reallocations.
|
|
||||||
for _, char := range data {
|
|
||||||
if char < 128 && charsetMapping[char] {
|
|
||||||
count++
|
|
||||||
} else {
|
|
||||||
if count > threshold {
|
|
||||||
substringsCount++
|
|
||||||
}
|
|
||||||
count = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if count > threshold {
|
|
||||||
substringsCount++
|
|
||||||
}
|
|
||||||
|
|
||||||
count = 0
|
|
||||||
start := 0
|
|
||||||
substrings := make([]string, 0, substringsCount)
|
|
||||||
|
|
||||||
for i, char := range data {
|
|
||||||
if char < 128 && charsetMapping[char] {
|
|
||||||
if count == 0 {
|
|
||||||
start = i
|
|
||||||
}
|
|
||||||
count++
|
|
||||||
} else {
|
|
||||||
if count > threshold {
|
|
||||||
substrings = appendB64Substring(data, start, count, substrings, endChars)
|
|
||||||
}
|
|
||||||
count = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if count > threshold {
|
|
||||||
substrings = appendB64Substring(data, start, count, substrings, endChars)
|
|
||||||
}
|
|
||||||
|
|
||||||
return substrings
|
|
||||||
}
|
|
||||||
|
|
||||||
func appendB64Substring(data []byte, start, count int, substrings []string, endChars string) []string {
|
|
||||||
substring := bytes.TrimLeft(data[start:start+count], endChars)
|
|
||||||
if idx := bytes.IndexByte(bytes.TrimRight(substring, endChars), '='); idx != -1 {
|
|
||||||
substrings = append(substrings, string(substring[idx+1:]))
|
|
||||||
} else {
|
|
||||||
substrings = append(substrings, string(substring))
|
|
||||||
}
|
|
||||||
return substrings
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ import (
|
|||||||
|
|
||||||
func TestBase64_FromChunk(t *testing.T) {
|
func TestBase64_FromChunk(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
|
name string
|
||||||
chunk *sources.Chunk
|
chunk *sources.Chunk
|
||||||
want *sources.Chunk
|
want *sources.Chunk
|
||||||
name string
|
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "only b64 chunk",
|
name: "only b64 chunk",
|
||||||
@@ -69,60 +69,6 @@ func TestBase64_FromChunk(t *testing.T) {
|
|||||||
but only this encapsulated secret should be decoded.`),
|
but only this encapsulated secret should be decoded.`),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "b64-url-safe: only b64 chunk",
|
|
||||||
chunk: &sources.Chunk{
|
|
||||||
Data: []byte(`bG9uZ2VyLWVuY29kZWQtc2VjcmV0LXRlc3Q`),
|
|
||||||
},
|
|
||||||
want: &sources.Chunk{
|
|
||||||
Data: []byte(`longer-encoded-secret-test`),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "b64-url-safe: mixed content",
|
|
||||||
chunk: &sources.Chunk{
|
|
||||||
Data: []byte(`token: bG9uZ2VyLWVuY29kZWQtc2VjcmV0LXRlc3Q`),
|
|
||||||
},
|
|
||||||
want: &sources.Chunk{
|
|
||||||
Data: []byte(`token: longer-encoded-secret-test`),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "b64-url-safe: env var (looks like all b64 decodable but has `=` in the middle)",
|
|
||||||
chunk: &sources.Chunk{
|
|
||||||
Data: []byte(`some-encoded-secret=dGVzdHNlY3JldA`),
|
|
||||||
},
|
|
||||||
want: &sources.Chunk{
|
|
||||||
Data: []byte(`some-encoded-secret=testsecret`),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "b64-url-safe: has longer b64 inside",
|
|
||||||
chunk: &sources.Chunk{
|
|
||||||
Data: []byte(`some-encoded-secret="bG9uZ2VyLWVuY29kZWQtc2VjcmV0LXRlc3Q"`),
|
|
||||||
},
|
|
||||||
want: &sources.Chunk{
|
|
||||||
Data: []byte(`some-encoded-secret="longer-encoded-secret-test"`),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "b64-url-safe: hyphen url b64",
|
|
||||||
chunk: &sources.Chunk{
|
|
||||||
Data: []byte(`dHJ1ZmZsZWhvZz4-ZmluZHMtc2VjcmV0cw`),
|
|
||||||
},
|
|
||||||
want: &sources.Chunk{
|
|
||||||
Data: []byte(`trufflehog>>finds-secrets`),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "b64-url-safe: underscore url b64",
|
|
||||||
chunk: &sources.Chunk{
|
|
||||||
Data: []byte(`YjY0dXJsc2FmZS10ZXN0LXNlY3JldC11bmRlcnNjb3Jlcz8_`),
|
|
||||||
},
|
|
||||||
want: &sources.Chunk{
|
|
||||||
Data: []byte(`b64urlsafe-test-secret-underscores??`),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
@@ -144,29 +90,13 @@ func TestBase64_FromChunk(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkFromChunkSmall(b *testing.B) {
|
func BenchmarkFromChunk(benchmark *testing.B) {
|
||||||
d := Base64{}
|
d := Base64{}
|
||||||
data := detectors.MustGetBenchmarkData()["small"]
|
for name, data := range detectors.MustGetBenchmarkData() {
|
||||||
|
benchmark.Run(name, func(b *testing.B) {
|
||||||
for n := 0; n < b.N; n++ {
|
for n := 0; n < b.N; n++ {
|
||||||
d.FromChunk(&sources.Chunk{Data: data})
|
d.FromChunk(&sources.Chunk{Data: data})
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
|
||||||
func BenchmarkFromChunkMedium(b *testing.B) {
|
|
||||||
d := Base64{}
|
|
||||||
data := detectors.MustGetBenchmarkData()["medium"]
|
|
||||||
|
|
||||||
for n := 0; n < b.N; n++ {
|
|
||||||
d.FromChunk(&sources.Chunk{Data: data})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkFromChunkLarge(b *testing.B) {
|
|
||||||
d := Base64{}
|
|
||||||
data := detectors.MustGetBenchmarkData()["big"]
|
|
||||||
|
|
||||||
for n := 0; n < b.N; n++ {
|
|
||||||
d.FromChunk(&sources.Chunk{Data: data})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,18 @@
|
|||||||
package decoders
|
package decoders
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
|
|
||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/sources"
|
"github.com/trufflesecurity/trufflehog/v3/pkg/sources"
|
||||||
)
|
)
|
||||||
|
|
||||||
func DefaultDecoders() []Decoder {
|
func DefaultDecoders() []Decoder {
|
||||||
return []Decoder{
|
return []Decoder{
|
||||||
// UTF8 must be first for duplicate detection
|
|
||||||
&UTF8{},
|
&UTF8{},
|
||||||
&Base64{},
|
&Base64{},
|
||||||
&UTF16{},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DecodableChunk is a chunk that includes the type of decoder used.
|
|
||||||
// This allows us to avoid a type assertion on each decoder.
|
|
||||||
type DecodableChunk struct {
|
|
||||||
*sources.Chunk
|
|
||||||
DecoderType detectorspb.DecoderType
|
|
||||||
}
|
|
||||||
|
|
||||||
type Decoder interface {
|
type Decoder interface {
|
||||||
FromChunk(chunk *sources.Chunk) *DecodableChunk
|
FromChunk(chunk *sources.Chunk) *sources.Chunk
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fuzz is an entrypoint for go-fuzz, which is an AFL-style fuzzing tool.
|
// Fuzz is an entrypoint for go-fuzz, which is an AFL-style fuzzing tool.
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
package decoders
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/binary"
|
|
||||||
"unicode/utf8"
|
|
||||||
|
|
||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
|
|
||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/sources"
|
|
||||||
)
|
|
||||||
|
|
||||||
type UTF16 struct{}
|
|
||||||
|
|
||||||
func (d *UTF16) FromChunk(chunk *sources.Chunk) *DecodableChunk {
|
|
||||||
if chunk == nil || len(chunk.Data) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
decodableChunk := &DecodableChunk{Chunk: chunk, DecoderType: detectorspb.DecoderType_UTF16}
|
|
||||||
if utf16Data, err := utf16ToUTF8(chunk.Data); err == nil {
|
|
||||||
if len(utf16Data) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
chunk.Data = utf16Data
|
|
||||||
return decodableChunk
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// utf16ToUTF8 converts a byte slice containing UTF-16 encoded data to a UTF-8 encoded byte slice.
|
|
||||||
func utf16ToUTF8(b []byte) ([]byte, error) {
|
|
||||||
var bufBE, bufLE bytes.Buffer
|
|
||||||
for i := 0; i < len(b)-1; i += 2 {
|
|
||||||
if r := rune(binary.BigEndian.Uint16(b[i:])); b[i] == 0 && utf8.ValidRune(r) {
|
|
||||||
if isValidByte(byte(r)) {
|
|
||||||
bufBE.WriteRune(r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if r := rune(binary.LittleEndian.Uint16(b[i:])); b[i+1] == 0 && utf8.ValidRune(r) {
|
|
||||||
if isValidByte(byte(r)) {
|
|
||||||
bufLE.WriteRune(r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return append(bufLE.Bytes(), bufBE.Bytes()...), nil
|
|
||||||
}
|
|
||||||
Binary file not shown.
@@ -1,94 +0,0 @@
|
|||||||
package decoders
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/sources"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestUTF16Decoder(t *testing.T) {
|
|
||||||
testCases := []struct {
|
|
||||||
name string
|
|
||||||
input []byte
|
|
||||||
expected []byte
|
|
||||||
expectNil bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Valid UTF-16LE input",
|
|
||||||
input: []byte{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",
|
|
||||||
input: []byte{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!"),
|
|
||||||
expected: nil,
|
|
||||||
expectNil: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Invalid UTF-16 input (odd length)",
|
|
||||||
input: []byte{72, 0, 101, 0, 108, 0, 108, 0, 111, 0, 32, 0, 87, 0, 111, 0, 114, 0, 108, 0, 0},
|
|
||||||
expected: []byte("Hello Worl"),
|
|
||||||
expectNil: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
chunk := &sources.Chunk{Data: tc.input}
|
|
||||||
decoder := &UTF16{}
|
|
||||||
decodedChunk := decoder.FromChunk(chunk)
|
|
||||||
|
|
||||||
if tc.expectNil {
|
|
||||||
if decodedChunk != nil {
|
|
||||||
t.Errorf("Expected nil, got chunk with data: %v", decodedChunk.Data)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if decodedChunk == nil {
|
|
||||||
t.Errorf("Expected chunk with data, got nil")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !bytes.Equal(decodedChunk.Data, tc.expected) {
|
|
||||||
t.Errorf("Expected decoded data: %s, got: %s", tc.expected, decodedChunk.Data)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDLL(t *testing.T) {
|
|
||||||
data, err := os.ReadFile("utf16_test.dll")
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Failed to read test data: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
chunk := &sources.Chunk{Data: data}
|
|
||||||
decoder := &UTF16{}
|
|
||||||
decodedChunk := decoder.FromChunk(chunk)
|
|
||||||
if decodedChunk == nil {
|
|
||||||
t.Errorf("Expected chunk with data, got nil")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !bytes.Contains(decodedChunk.Data, []byte("aws_secret_access_key")) {
|
|
||||||
t.Errorf("Expected chunk to have aws_secret_access_key")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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++ {
|
|
||||||
_, _ = utf16ToUTF8(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+14
-29
@@ -4,56 +4,41 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
|
|
||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/sources"
|
"github.com/trufflesecurity/trufflehog/v3/pkg/sources"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UTF8 struct{}
|
type UTF8 struct{}
|
||||||
|
|
||||||
func (d *UTF8) FromChunk(chunk *sources.Chunk) *DecodableChunk {
|
func (d *UTF8) FromChunk(chunk *sources.Chunk) *sources.Chunk {
|
||||||
if chunk == nil || len(chunk.Data) == 0 {
|
if chunk == nil || len(chunk.Data) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
decodableChunk := &DecodableChunk{Chunk: chunk, DecoderType: detectorspb.DecoderType_PLAIN}
|
|
||||||
|
|
||||||
if !utf8.Valid(chunk.Data) {
|
if !utf8.Valid(chunk.Data) {
|
||||||
chunk.Data = extractSubstrings(chunk.Data)
|
chunk.Data = extractSubstrings(chunk.Data)
|
||||||
return decodableChunk
|
return chunk
|
||||||
}
|
}
|
||||||
|
|
||||||
return decodableChunk
|
return chunk
|
||||||
}
|
}
|
||||||
|
|
||||||
// extractSubstrings performs similarly to the strings binutil,
|
// extractSubstrings performs similarly to the strings binutil,
|
||||||
// extacting contigous portions of printable characters that we care
|
// extacting contigous portions of printable characters that we care
|
||||||
// about from some bytes
|
// about from some bytes
|
||||||
func extractSubstrings(b []byte) []byte {
|
func extractSubstrings(b []byte) []byte {
|
||||||
|
fields := bytes.FieldsFunc(b, func(r rune) bool {
|
||||||
|
// https://www.rapidtables.com/code/text/ascii-table.html
|
||||||
|
// split on anything that is not ascii space through tilde
|
||||||
|
return !(r > 31 && r < 127)
|
||||||
|
})
|
||||||
|
|
||||||
field := make([]byte, len(b))
|
keep := [][]byte{}
|
||||||
fieldLen := 0
|
for _, field := range fields {
|
||||||
buf := &bytes.Buffer{}
|
// Remove fields shorter than 6 characters.
|
||||||
for i, c := range b {
|
if bts := bytes.TrimSpace(field); len(bts) > 5 {
|
||||||
if isValidByte(c) {
|
keep = append(keep, bts)
|
||||||
field[fieldLen] = c
|
|
||||||
fieldLen++
|
|
||||||
} else {
|
|
||||||
if fieldLen > 5 {
|
|
||||||
buf.Write(field[:fieldLen])
|
|
||||||
}
|
|
||||||
fieldLen = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
if i == len(b)-1 && fieldLen > 5 {
|
|
||||||
buf.Write(field[:fieldLen])
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return buf.Bytes()
|
return bytes.Join(keep, []byte("\n"))
|
||||||
}
|
|
||||||
|
|
||||||
func isValidByte(c byte) bool {
|
|
||||||
// https://www.rapidtables.com/code/text/ascii-table.html
|
|
||||||
// split on anything that is not ascii space through tilde
|
|
||||||
return c > 31 && c < 127
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/kylelemons/godebug/pretty"
|
"github.com/kylelemons/godebug/pretty"
|
||||||
|
|
||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/sources"
|
"github.com/trufflesecurity/trufflehog/v3/pkg/sources"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -108,7 +108,6 @@ func BenchmarkFromData(benchmark *testing.B) {
|
|||||||
s := Scanner{}
|
s := Scanner{}
|
||||||
for name, data := range detectors.MustGetBenchmarkData() {
|
for name, data := range detectors.MustGetBenchmarkData() {
|
||||||
benchmark.Run(name, func(b *testing.B) {
|
benchmark.Run(name, func(b *testing.B) {
|
||||||
b.ResetTimer()
|
|
||||||
for n := 0; n < b.N; n++ {
|
for n := 0; n < b.N; n++ {
|
||||||
_, err := s.FromData(ctx, false, data)
|
_, err := s.FromData(ctx, false, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -108,7 +108,6 @@ func BenchmarkFromData(benchmark *testing.B) {
|
|||||||
s := Scanner{}
|
s := Scanner{}
|
||||||
for name, data := range detectors.MustGetBenchmarkData() {
|
for name, data := range detectors.MustGetBenchmarkData() {
|
||||||
benchmark.Run(name, func(b *testing.B) {
|
benchmark.Run(name, func(b *testing.B) {
|
||||||
b.ResetTimer()
|
|
||||||
for n := 0; n < b.N; n++ {
|
for n := 0; n < b.N; n++ {
|
||||||
_, err := s.FromData(ctx, false, data)
|
_, err := s.FromData(ctx, false, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -108,7 +108,6 @@ func BenchmarkFromData(benchmark *testing.B) {
|
|||||||
s := Scanner{}
|
s := Scanner{}
|
||||||
for name, data := range detectors.MustGetBenchmarkData() {
|
for name, data := range detectors.MustGetBenchmarkData() {
|
||||||
benchmark.Run(name, func(b *testing.B) {
|
benchmark.Run(name, func(b *testing.B) {
|
||||||
b.ResetTimer()
|
|
||||||
for n := 0; n < b.N; n++ {
|
for n := 0; n < b.N; n++ {
|
||||||
_, err := s.FromData(ctx, false, data)
|
_, err := s.FromData(ctx, false, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -108,7 +108,6 @@ func BenchmarkFromData(benchmark *testing.B) {
|
|||||||
s := Scanner{}
|
s := Scanner{}
|
||||||
for name, data := range detectors.MustGetBenchmarkData() {
|
for name, data := range detectors.MustGetBenchmarkData() {
|
||||||
benchmark.Run(name, func(b *testing.B) {
|
benchmark.Run(name, func(b *testing.B) {
|
||||||
b.ResetTimer()
|
|
||||||
for n := 0; n < b.N; n++ {
|
for n := 0; n < b.N; n++ {
|
||||||
_, err := s.FromData(ctx, false, data)
|
_, err := s.FromData(ctx, false, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -108,7 +108,6 @@ func BenchmarkFromData(benchmark *testing.B) {
|
|||||||
s := Scanner{}
|
s := Scanner{}
|
||||||
for name, data := range detectors.MustGetBenchmarkData() {
|
for name, data := range detectors.MustGetBenchmarkData() {
|
||||||
benchmark.Run(name, func(b *testing.B) {
|
benchmark.Run(name, func(b *testing.B) {
|
||||||
b.ResetTimer()
|
|
||||||
for n := 0; n < b.N; n++ {
|
for n := 0; n < b.N; n++ {
|
||||||
_, err := s.FromData(ctx, false, data)
|
_, err := s.FromData(ctx, false, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -109,7 +109,6 @@ func BenchmarkFromData(benchmark *testing.B) {
|
|||||||
s := Scanner{}
|
s := Scanner{}
|
||||||
for name, data := range detectors.MustGetBenchmarkData() {
|
for name, data := range detectors.MustGetBenchmarkData() {
|
||||||
benchmark.Run(name, func(b *testing.B) {
|
benchmark.Run(name, func(b *testing.B) {
|
||||||
b.ResetTimer()
|
|
||||||
for n := 0; n < b.N; n++ {
|
for n := 0; n < b.N; n++ {
|
||||||
_, err := s.FromData(ctx, false, data)
|
_, err := s.FromData(ctx, false, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -109,7 +109,6 @@ func BenchmarkFromData(benchmark *testing.B) {
|
|||||||
s := Scanner{}
|
s := Scanner{}
|
||||||
for name, data := range detectors.MustGetBenchmarkData() {
|
for name, data := range detectors.MustGetBenchmarkData() {
|
||||||
benchmark.Run(name, func(b *testing.B) {
|
benchmark.Run(name, func(b *testing.B) {
|
||||||
b.ResetTimer()
|
|
||||||
for n := 0; n < b.N; n++ {
|
for n := 0; n < b.N; n++ {
|
||||||
_, err := s.FromData(ctx, false, data)
|
_, err := s.FromData(ctx, false, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -110,7 +110,6 @@ func BenchmarkFromData(benchmark *testing.B) {
|
|||||||
s := Scanner{}
|
s := Scanner{}
|
||||||
for name, data := range detectors.MustGetBenchmarkData() {
|
for name, data := range detectors.MustGetBenchmarkData() {
|
||||||
benchmark.Run(name, func(b *testing.B) {
|
benchmark.Run(name, func(b *testing.B) {
|
||||||
b.ResetTimer()
|
|
||||||
for n := 0; n < b.N; n++ {
|
for n := 0; n < b.N; n++ {
|
||||||
s.FromData(ctx, false, data)
|
s.FromData(ctx, false, data)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,7 +109,6 @@ func BenchmarkFromData(benchmark *testing.B) {
|
|||||||
s := Scanner{}
|
s := Scanner{}
|
||||||
for name, data := range detectors.MustGetBenchmarkData() {
|
for name, data := range detectors.MustGetBenchmarkData() {
|
||||||
benchmark.Run(name, func(b *testing.B) {
|
benchmark.Run(name, func(b *testing.B) {
|
||||||
b.ResetTimer()
|
|
||||||
for n := 0; n < b.N; n++ {
|
for n := 0; n < b.N; n++ {
|
||||||
_, err := s.FromData(ctx, false, data)
|
_, err := s.FromData(ctx, false, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/kylelemons/godebug/pretty"
|
"github.com/kylelemons/godebug/pretty"
|
||||||
|
|
||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
|
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
|
||||||
|
|
||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
|
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
|
||||||
@@ -109,7 +108,6 @@ func BenchmarkFromData(benchmark *testing.B) {
|
|||||||
s := Scanner{}
|
s := Scanner{}
|
||||||
for name, data := range detectors.MustGetBenchmarkData() {
|
for name, data := range detectors.MustGetBenchmarkData() {
|
||||||
benchmark.Run(name, func(b *testing.B) {
|
benchmark.Run(name, func(b *testing.B) {
|
||||||
b.ResetTimer()
|
|
||||||
for n := 0; n < b.N; n++ {
|
for n := 0; n < b.N; n++ {
|
||||||
_, err := s.FromData(ctx, false, data)
|
_, err := s.FromData(ctx, false, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -55,9 +55,6 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
|||||||
Raw: []byte(resMatch),
|
Raw: []byte(resMatch),
|
||||||
RawV2: []byte(resMatch + resIdMatch),
|
RawV2: []byte(resMatch + resIdMatch),
|
||||||
}
|
}
|
||||||
s1.ExtraData = map[string]string{
|
|
||||||
"rotation_guide": "https://howtorotate.com/docs/tutorials/airbrake/",
|
|
||||||
}
|
|
||||||
|
|
||||||
if verify {
|
if verify {
|
||||||
payload := strings.NewReader(`{"environment":"production","username":"john","email":"john@smith.com","repository":"https://github.com/airbrake/airbrake","revision":"38748467ea579e7ae64f7815452307c9d05e05c5","version":"v2.0"}`)
|
payload := strings.NewReader(`{"environment":"production","username":"john","email":"john@smith.com","repository":"https://github.com/airbrake/airbrake","revision":"38748467ea579e7ae64f7815452307c9d05e05c5","version":"v2.0"}`)
|
||||||
|
|||||||
@@ -109,7 +109,6 @@ func BenchmarkFromData(benchmark *testing.B) {
|
|||||||
s := Scanner{}
|
s := Scanner{}
|
||||||
for name, data := range detectors.MustGetBenchmarkData() {
|
for name, data := range detectors.MustGetBenchmarkData() {
|
||||||
benchmark.Run(name, func(b *testing.B) {
|
benchmark.Run(name, func(b *testing.B) {
|
||||||
b.ResetTimer()
|
|
||||||
for n := 0; n < b.N; n++ {
|
for n := 0; n < b.N; n++ {
|
||||||
_, err := s.FromData(ctx, false, data)
|
_, err := s.FromData(ctx, false, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -45,9 +45,6 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
|||||||
DetectorType: detectorspb.DetectorType_AirbrakeUserKey,
|
DetectorType: detectorspb.DetectorType_AirbrakeUserKey,
|
||||||
Raw: []byte(resMatch),
|
Raw: []byte(resMatch),
|
||||||
}
|
}
|
||||||
s1.ExtraData = map[string]string{
|
|
||||||
"rotation_guide": "https://howtorotate.com/docs/tutorials/airbrake/",
|
|
||||||
}
|
|
||||||
|
|
||||||
if verify {
|
if verify {
|
||||||
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.airbrake.io/api/v4/projects?key="+resMatch, nil)
|
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.airbrake.io/api/v4/projects?key="+resMatch, nil)
|
||||||
|
|||||||
@@ -108,7 +108,6 @@ func BenchmarkFromData(benchmark *testing.B) {
|
|||||||
s := Scanner{}
|
s := Scanner{}
|
||||||
for name, data := range detectors.MustGetBenchmarkData() {
|
for name, data := range detectors.MustGetBenchmarkData() {
|
||||||
benchmark.Run(name, func(b *testing.B) {
|
benchmark.Run(name, func(b *testing.B) {
|
||||||
b.ResetTimer()
|
|
||||||
for n := 0; n < b.N; n++ {
|
for n := 0; n < b.N; n++ {
|
||||||
_, err := s.FromData(ctx, false, data)
|
_, err := s.FromData(ctx, false, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -108,7 +108,6 @@ func BenchmarkFromData(benchmark *testing.B) {
|
|||||||
s := Scanner{}
|
s := Scanner{}
|
||||||
for name, data := range detectors.MustGetBenchmarkData() {
|
for name, data := range detectors.MustGetBenchmarkData() {
|
||||||
benchmark.Run(name, func(b *testing.B) {
|
benchmark.Run(name, func(b *testing.B) {
|
||||||
b.ResetTimer()
|
|
||||||
for n := 0; n < b.N; n++ {
|
for n := 0; n < b.N; n++ {
|
||||||
_, err := s.FromData(ctx, false, data)
|
_, err := s.FromData(ctx, false, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package airtableapikey
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
@@ -21,9 +20,8 @@ var _ detectors.Detector = (*Scanner)(nil)
|
|||||||
var (
|
var (
|
||||||
client = common.SaneHttpClient()
|
client = common.SaneHttpClient()
|
||||||
|
|
||||||
appPat = regexp.MustCompile(`(app[a-zA-Z0-9_-]{14})`) // could be part of url
|
appPat = regexp.MustCompile(`(app[a-zA-Z0-9_-]{14})`) // could be part of url
|
||||||
keyPat = regexp.MustCompile(`\b(key[a-zA-Z0-9_-]{14})\b`)
|
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.
|
// Keywords are used for efficiently pre-filtering chunks.
|
||||||
@@ -32,31 +30,17 @@ func (s Scanner) Keywords() []string {
|
|||||||
return []string{"airtable"}
|
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.
|
// 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) {
|
func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
|
||||||
dataStr := string(data)
|
dataStr := string(data)
|
||||||
|
|
||||||
appMatches := appPat.FindAllStringSubmatch(dataStr, -1)
|
appMatches := appPat.FindAllStringSubmatch(dataStr, -1)
|
||||||
keyMatches := keyPat.FindAllStringSubmatch(dataStr, -1)
|
keyMatches := keyPat.FindAllStringSubmatch(dataStr, -1)
|
||||||
personalKeyMatches := personalPat.FindAllStringSubmatch(dataStr, -1)
|
|
||||||
|
|
||||||
if len(keyMatches) == 0 {
|
|
||||||
keyMatches = personalKeyMatches
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, keyMatch := range keyMatches {
|
for _, keyMatch := range keyMatches {
|
||||||
if len(keyMatch) != 2 {
|
if len(keyMatch) != 2 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
keyRes := strings.TrimSpace(keyMatch[1])
|
keyRes := strings.TrimSpace(keyMatch[1])
|
||||||
|
|
||||||
for _, appMatch := range appMatches {
|
for _, appMatch := range appMatches {
|
||||||
@@ -83,15 +67,6 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
|||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
if res.StatusCode >= 200 && res.StatusCode < 300 {
|
if res.StatusCode >= 200 && res.StatusCode < 300 {
|
||||||
s1.Verified = true
|
s1.Verified = true
|
||||||
} else if res.StatusCode == 403 {
|
|
||||||
var resp response
|
|
||||||
if err = json.NewDecoder(res.Body).Decode(&resp); err == nil {
|
|
||||||
// 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.
|
|
||||||
s1.Verified = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if detectors.IsKnownFalsePositive(keyRes, detectors.DefaultFalsePositives, true) {
|
if detectors.IsKnownFalsePositive(keyRes, detectors.DefaultFalsePositives, true) {
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -9,10 +9,9 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/kylelemons/godebug/pretty"
|
||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
|
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
|
||||||
|
|
||||||
"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/common"
|
||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
|
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
|
||||||
)
|
)
|
||||||
@@ -100,9 +99,8 @@ func TestAirtableApiKey_FromChunk(t *testing.T) {
|
|||||||
}
|
}
|
||||||
got[i].Raw = nil
|
got[i].Raw = nil
|
||||||
}
|
}
|
||||||
ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "RawV2", "verificationError")
|
if diff := pretty.Compare(got, tt.want); diff != "" {
|
||||||
if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
|
t.Errorf("AirtableApiKey.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
|
||||||
t.Errorf("Airtable.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -113,7 +111,6 @@ func BenchmarkFromData(benchmark *testing.B) {
|
|||||||
s := Scanner{}
|
s := Scanner{}
|
||||||
for name, data := range detectors.MustGetBenchmarkData() {
|
for name, data := range detectors.MustGetBenchmarkData() {
|
||||||
benchmark.Run(name, func(b *testing.B) {
|
benchmark.Run(name, func(b *testing.B) {
|
||||||
b.ResetTimer()
|
|
||||||
for n := 0; n < b.N; n++ {
|
for n := 0; n < b.N; n++ {
|
||||||
_, err := s.FromData(ctx, false, data)
|
_, err := s.FromData(ctx, false, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -108,7 +108,6 @@ func BenchmarkFromData(benchmark *testing.B) {
|
|||||||
s := Scanner{}
|
s := Scanner{}
|
||||||
for name, data := range detectors.MustGetBenchmarkData() {
|
for name, data := range detectors.MustGetBenchmarkData() {
|
||||||
benchmark.Run(name, func(b *testing.B) {
|
benchmark.Run(name, func(b *testing.B) {
|
||||||
b.ResetTimer()
|
|
||||||
for n := 0; n < b.N; n++ {
|
for n := 0; n < b.N; n++ {
|
||||||
_, err := s.FromData(ctx, false, data)
|
_, err := s.FromData(ctx, false, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -1,89 +0,0 @@
|
|||||||
package alchemy
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"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
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"alchemy"}) + `\b([0-9a-zA-Z]{23}_[0-9a-zA-Z]{8})\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{"alchemy"}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FromData will find and optionally verify Alchemy 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)
|
|
||||||
|
|
||||||
for _, match := range matches {
|
|
||||||
if len(match) != 2 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
resMatch := strings.TrimSpace(match[1])
|
|
||||||
|
|
||||||
s1 := detectors.Result{
|
|
||||||
DetectorType: detectorspb.DetectorType_Alchemy,
|
|
||||||
Raw: []byte(resMatch),
|
|
||||||
}
|
|
||||||
|
|
||||||
if verify {
|
|
||||||
client := s.client
|
|
||||||
if client == nil {
|
|
||||||
client = defaultClient
|
|
||||||
}
|
|
||||||
req, err := http.NewRequestWithContext(ctx, "GET", "https://eth-mainnet.g.alchemy.com/v2/"+resMatch+"/getNFTs/?owner=vitalik.eth", nil)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
res, err := client.Do(req)
|
|
||||||
if err == nil {
|
|
||||||
defer res.Body.Close()
|
|
||||||
if res.StatusCode >= 200 && res.StatusCode < 300 {
|
|
||||||
s1.Verified = true
|
|
||||||
} else if res.StatusCode == 401 {
|
|
||||||
// The secret is determinately not verified (nothing to do)
|
|
||||||
} else {
|
|
||||||
err = fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
|
|
||||||
s1.SetVerificationError(err, resMatch)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
s1.SetVerificationError(err, resMatch)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This function will check false positives for common test words, but also it will make sure the key appears 'random' enough to be a real key.
|
|
||||||
if !s1.Verified && detectors.IsKnownFalsePositive(resMatch, detectors.DefaultFalsePositives, true) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
results = append(results, s1)
|
|
||||||
}
|
|
||||||
|
|
||||||
return results, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s Scanner) Type() detectorspb.DetectorType {
|
|
||||||
return detectorspb.DetectorType_Alchemy
|
|
||||||
}
|
|
||||||
@@ -1,162 +0,0 @@
|
|||||||
//go:build detectors
|
|
||||||
// +build detectors
|
|
||||||
|
|
||||||
package alchemy
|
|
||||||
|
|
||||||
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 TestAlchemy_FromChunk(t *testing.T) {
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
|
||||||
defer cancel()
|
|
||||||
testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("could not get test secrets from GCP: %s", err)
|
|
||||||
}
|
|
||||||
secret := testSecrets.MustGetField("ALCHEMY")
|
|
||||||
inactiveSecret := testSecrets.MustGetField("ALCHEMY_INACTIVE")
|
|
||||||
|
|
||||||
type args struct {
|
|
||||||
ctx context.Context
|
|
||||||
data []byte
|
|
||||||
verify bool
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
s Scanner
|
|
||||||
args args
|
|
||||||
want []detectors.Result
|
|
||||||
wantErr bool
|
|
||||||
wantVerificationErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "found, verified",
|
|
||||||
s: Scanner{},
|
|
||||||
args: args{
|
|
||||||
ctx: context.Background(),
|
|
||||||
data: []byte(fmt.Sprintf("You can find a alchemy secret %s within", secret)),
|
|
||||||
verify: true,
|
|
||||||
},
|
|
||||||
want: []detectors.Result{
|
|
||||||
{
|
|
||||||
DetectorType: detectorspb.DetectorType_Alchemy,
|
|
||||||
Verified: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
wantErr: false,
|
|
||||||
wantVerificationErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "found, unverified",
|
|
||||||
s: Scanner{},
|
|
||||||
args: args{
|
|
||||||
ctx: context.Background(),
|
|
||||||
data: []byte(fmt.Sprintf("You can find a alchemy 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_Alchemy,
|
|
||||||
Verified: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
wantErr: false,
|
|
||||||
wantVerificationErr: 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,
|
|
||||||
wantVerificationErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "found, would be verified if not for timeout",
|
|
||||||
s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
|
|
||||||
args: args{
|
|
||||||
ctx: context.Background(),
|
|
||||||
data: []byte(fmt.Sprintf("You can find a alchemy secret %s within", secret)),
|
|
||||||
verify: true,
|
|
||||||
},
|
|
||||||
want: []detectors.Result{
|
|
||||||
{
|
|
||||||
DetectorType: detectorspb.DetectorType_Alchemy,
|
|
||||||
Verified: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
wantErr: false,
|
|
||||||
wantVerificationErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "found, verified but unexpected api surface",
|
|
||||||
s: Scanner{client: common.ConstantResponseHttpClient(404, "")},
|
|
||||||
args: args{
|
|
||||||
ctx: context.Background(),
|
|
||||||
data: []byte(fmt.Sprintf("You can find a alchemy secret %s within", secret)),
|
|
||||||
verify: true,
|
|
||||||
},
|
|
||||||
want: []detectors.Result{
|
|
||||||
{
|
|
||||||
DetectorType: detectorspb.DetectorType_Alchemy,
|
|
||||||
Verified: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
wantErr: false,
|
|
||||||
wantVerificationErr: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
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("Alchemy.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])
|
|
||||||
}
|
|
||||||
if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
|
|
||||||
t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError")
|
|
||||||
if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
|
|
||||||
t.Errorf("Alchemy.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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -108,7 +108,6 @@ func BenchmarkFromData(benchmark *testing.B) {
|
|||||||
s := Scanner{}
|
s := Scanner{}
|
||||||
for name, data := range detectors.MustGetBenchmarkData() {
|
for name, data := range detectors.MustGetBenchmarkData() {
|
||||||
benchmark.Run(name, func(b *testing.B) {
|
benchmark.Run(name, func(b *testing.B) {
|
||||||
b.ResetTimer()
|
|
||||||
for n := 0; n < b.N; n++ {
|
for n := 0; n < b.N; n++ {
|
||||||
_, err := s.FromData(ctx, false, data)
|
_, err := s.FromData(ctx, false, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -109,7 +109,6 @@ func BenchmarkFromData(benchmark *testing.B) {
|
|||||||
s := Scanner{}
|
s := Scanner{}
|
||||||
for name, data := range detectors.MustGetBenchmarkData() {
|
for name, data := range detectors.MustGetBenchmarkData() {
|
||||||
benchmark.Run(name, func(b *testing.B) {
|
benchmark.Run(name, func(b *testing.B) {
|
||||||
b.ResetTimer()
|
|
||||||
for n := 0; n < b.N; n++ {
|
for n := 0; n < b.N; n++ {
|
||||||
_, err := s.FromData(ctx, false, data)
|
_, err := s.FromData(ctx, false, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -108,7 +108,6 @@ func BenchmarkFromData(benchmark *testing.B) {
|
|||||||
s := Scanner{}
|
s := Scanner{}
|
||||||
for name, data := range detectors.MustGetBenchmarkData() {
|
for name, data := range detectors.MustGetBenchmarkData() {
|
||||||
benchmark.Run(name, func(b *testing.B) {
|
benchmark.Run(name, func(b *testing.B) {
|
||||||
b.ResetTimer()
|
|
||||||
for n := 0; n < b.N; n++ {
|
for n := 0; n < b.N; n++ {
|
||||||
_, err := s.FromData(ctx, false, data)
|
_, err := s.FromData(ctx, false, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -109,7 +109,6 @@ func BenchmarkFromData(benchmark *testing.B) {
|
|||||||
s := Scanner{}
|
s := Scanner{}
|
||||||
for name, data := range detectors.MustGetBenchmarkData() {
|
for name, data := range detectors.MustGetBenchmarkData() {
|
||||||
benchmark.Run(name, func(b *testing.B) {
|
benchmark.Run(name, func(b *testing.B) {
|
||||||
b.ResetTimer()
|
|
||||||
for n := 0; n < b.N; n++ {
|
for n := 0; n < b.N; n++ {
|
||||||
_, err := s.FromData(ctx, false, data)
|
_, err := s.FromData(ctx, false, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ package alibaba
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
"crypto/rand"
|
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
@@ -38,9 +38,10 @@ func (s Scanner) Keywords() []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func randString(n int) string {
|
func randString(n int) string {
|
||||||
|
rand.Seed(time.Now().UnixNano())
|
||||||
const alphanum = "0123456789abcdefghijklmnopqrstuvwxyz"
|
const alphanum = "0123456789abcdefghijklmnopqrstuvwxyz"
|
||||||
var bytes = make([]byte, n)
|
var bytes = make([]byte, n)
|
||||||
_, _ = rand.Read(bytes)
|
rand.Read(bytes)
|
||||||
for i, b := range bytes {
|
for i, b := range bytes {
|
||||||
bytes[i] = alphanum[b%byte(len(alphanum))]
|
bytes[i] = alphanum[b%byte(len(alphanum))]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,7 +96,6 @@ func TestAlibaba_FromChunk(t *testing.T) {
|
|||||||
t.Fatalf("no raw secret present: \n %+v", got[i])
|
t.Fatalf("no raw secret present: \n %+v", got[i])
|
||||||
}
|
}
|
||||||
got[i].Raw = nil
|
got[i].Raw = nil
|
||||||
got[i].RawV2 = nil
|
|
||||||
}
|
}
|
||||||
if diff := pretty.Compare(got, tt.want); diff != "" {
|
if diff := pretty.Compare(got, tt.want); diff != "" {
|
||||||
t.Errorf("Alibaba.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
|
t.Errorf("Alibaba.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
|
||||||
@@ -110,7 +109,6 @@ func BenchmarkFromData(benchmark *testing.B) {
|
|||||||
s := Scanner{}
|
s := Scanner{}
|
||||||
for name, data := range detectors.MustGetBenchmarkData() {
|
for name, data := range detectors.MustGetBenchmarkData() {
|
||||||
benchmark.Run(name, func(b *testing.B) {
|
benchmark.Run(name, func(b *testing.B) {
|
||||||
b.ResetTimer()
|
|
||||||
for n := 0; n < b.N; n++ {
|
for n := 0; n < b.N; n++ {
|
||||||
_, err := s.FromData(ctx, false, data)
|
_, err := s.FromData(ctx, false, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -108,7 +108,6 @@ func BenchmarkFromData(benchmark *testing.B) {
|
|||||||
s := Scanner{}
|
s := Scanner{}
|
||||||
for name, data := range detectors.MustGetBenchmarkData() {
|
for name, data := range detectors.MustGetBenchmarkData() {
|
||||||
benchmark.Run(name, func(b *testing.B) {
|
benchmark.Run(name, func(b *testing.B) {
|
||||||
b.ResetTimer()
|
|
||||||
for n := 0; n < b.N; n++ {
|
for n := 0; n < b.N; n++ {
|
||||||
_, err := s.FromData(ctx, false, data)
|
_, err := s.FromData(ctx, false, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -108,7 +108,6 @@ func BenchmarkFromData(benchmark *testing.B) {
|
|||||||
s := Scanner{}
|
s := Scanner{}
|
||||||
for name, data := range detectors.MustGetBenchmarkData() {
|
for name, data := range detectors.MustGetBenchmarkData() {
|
||||||
benchmark.Run(name, func(b *testing.B) {
|
benchmark.Run(name, func(b *testing.B) {
|
||||||
b.ResetTimer()
|
|
||||||
for n := 0; n < b.N; n++ {
|
for n := 0; n < b.N; n++ {
|
||||||
_, err := s.FromData(ctx, false, data)
|
_, err := s.FromData(ctx, false, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -109,7 +109,6 @@ func BenchmarkFromData(benchmark *testing.B) {
|
|||||||
s := Scanner{}
|
s := Scanner{}
|
||||||
for name, data := range detectors.MustGetBenchmarkData() {
|
for name, data := range detectors.MustGetBenchmarkData() {
|
||||||
benchmark.Run(name, func(b *testing.B) {
|
benchmark.Run(name, func(b *testing.B) {
|
||||||
b.ResetTimer()
|
|
||||||
for n := 0; n < b.N; n++ {
|
for n := 0; n < b.N; n++ {
|
||||||
_, err := s.FromData(ctx, false, data)
|
_, err := s.FromData(ctx, false, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -108,7 +108,6 @@ func BenchmarkFromData(benchmark *testing.B) {
|
|||||||
s := Scanner{}
|
s := Scanner{}
|
||||||
for name, data := range detectors.MustGetBenchmarkData() {
|
for name, data := range detectors.MustGetBenchmarkData() {
|
||||||
benchmark.Run(name, func(b *testing.B) {
|
benchmark.Run(name, func(b *testing.B) {
|
||||||
b.ResetTimer()
|
|
||||||
for n := 0; n < b.N; n++ {
|
for n := 0; n < b.N; n++ {
|
||||||
_, err := s.FromData(ctx, false, data)
|
_, err := s.FromData(ctx, false, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user